Class: EY::Snaplock

Inherits:
Object
  • Object
show all
Defined in:
lib/ey_snaplock.rb,
lib/ey_snaplock/timer.rb,
lib/ey_snaplock/version.rb,
lib/ey_snaplock/database.rb,
lib/ey_snaplock/database/mysql.rb,
lib/ey_snaplock/database/postgresql9.rb

Defined Under Namespace

Classes: Database, Timer

Constant Summary collapse

REQUEST_TIMEOUT =
15
PER_DATABASE_TIMEOUT =
5
LONG_RUNNING_QUERY_RETRIES =
3
VERSION =
'0.1.1'

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(callback_uri = nil, *database_uris) ⇒ Snaplock

Returns a new instance of Snaplock.



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/ey_snaplock.rb', line 17

def initialize(callback_uri = nil, *database_uris)
  if callback_uri.nil?
    $stderr.puts "Usage: ey-snaplock CALLBACK_URI [DATABASE_URI] [DATABASE_URI] ..."
    abort "Error: CALLBACK_URI is required."
  end

  callback = request(callback_uri)
  callback = timeout(&callback)
  callback = sync_filesystem(&callback)

  database_uris.each do |database_uri| # it'd be nice to parallelize these database locks when we have more than one
    callback = database_lock(database_uri, PER_DATABASE_TIMEOUT, &callback)
  end
  @callback = callback
end

Class Method Details

.call(*argv) ⇒ Object



12
13
14
15
# File 'lib/ey_snaplock.rb', line 12

def self.call(*argv)
  new(*argv).call
  exit 0
end

Instance Method Details

#callObject



33
34
35
36
37
38
39
40
# File 'lib/ey_snaplock.rb', line 33

def call
  success, code, body = @callback.call
  if success
    exit
  else
    abort "Error: Received unsuccessful response #{code} from callback:\n#{body}"
  end
end

#clean_up_old_log_files(logs_to_keep = 10) ⇒ Object



113
114
115
116
# File 'lib/ey_snaplock.rb', line 113

def clean_up_old_log_files(logs_to_keep = 10)
  ordinal = logs_to_keep.succ.to_s
  system("ls -r /var/log/ey-snaplock.*.log | tail -n +#{ordinal} | xargs -I@ rm @")
end

#database_lock(database_uri, timeout, &block) ⇒ Object



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/ey_snaplock.rb', line 71

def database_lock(database_uri, timeout, &block)
  database = Database.for(database_uri)
  with_locked_db = block

  if database.respond_to?(:has_long_running_queries?)
    log_file = '/var/log/ey-snaplock.' + Time.now.strftime("%Y-%m-%dT%H-%M-%S") + '.log'
    remaining_retries = LONG_RUNNING_QUERY_RETRIES

    while database.has_long_running_queries?(PER_DATABASE_TIMEOUT) && remaining_retries >= 0
      if remaining_retries == 0
        database.dump_process_list(log_file)
        clean_up_old_log_files
        $stderr.puts "Aborting Snaplock due to long running queries.  Process list dumped to #{log_file}."
        exit 1
      else
        remaining_retries -= 1
        sleep PER_DATABASE_TIMEOUT
      end
    end

  end

  lambda do
    lock_filename = database.lock_filename
    file = File.open(lock_filename, File::RDWR|File::EXCL|File::CREAT) rescue nil
    if !file && File.mtime(lock_filename) < (Time.now - (20 * 60 * 60))
      $stdout.puts "Cleared stale database lock: #{lock_filename}"
      file = File.open(lock_filename, File::RDWR|File::EXCL)
    elsif !file
      $stderr.puts "Failed to acquire database lock: #{lock_filename}"
      exit 1
    end
    file.write(Process.pid.to_s)
    file.close
    begin
      database.with_lock(timeout, &with_locked_db)
    ensure
      File.delete(lock_filename) if File.exists?(lock_filename)
    end
  end
end

#request(uri) ⇒ Object



42
43
44
45
46
47
48
49
50
51
# File 'lib/ey_snaplock.rb', line 42

def request(uri)
  uri = URI.parse(uri)
  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = true if uri.scheme == "https"

  lambda do
    response = http.request_post(post_path_for_uri(uri), "", "Content-Type" => "application/x-www-form-urlencoded")
    [(200...300).include?(response.code.to_i), response.code.to_i, response.body]
  end
end

#sync_filesystem(&block) ⇒ Object



64
65
66
67
68
69
# File 'lib/ey_snaplock.rb', line 64

def sync_filesystem(&block)
  lambda do
    `sync && sync && sync`
    yield
  end
end

#timeoutObject



53
54
55
56
57
58
59
60
61
62
# File 'lib/ey_snaplock.rb', line 53

def timeout
  lambda do
    begin
      EY::Snaplock::Timer.timeout(REQUEST_TIMEOUT) { yield }
    rescue Timeout::Error
      $stderr.puts "Timeout Exceeded: Callback request took longer than #{REQUEST_TIMEOUT} seconds."
      raise
    end
  end
end