Module: BatchKit::Lockable
- Included in:
- Runnable
- Defined in:
- lib/batch-kit/lockable.rb
Overview
Defines lockable behaviour, which can be added to any batch process. This behavior allows a process to define a named lock that it needs exclusively during execution. When the process is about to be executed, it will first attempt to obtain the named lock. If it is successful, execution will proceed as normal, and on completion of processing (whether succesful or otherwise), the lock will be released. If the lock is already held by another process, the requesting process will block and wait for the lock to become available. The process will only wait as long as lock_wait_timeout; if the lock has not become availabe in that time period, a LockTimeout exception will be thrown, and processing will not take place.
Instance Method Summary collapse
-
#lock(lock_name, lock_timeout, lock_wait_timeout = nil) ⇒ Object
Attempts to obtain the named lock
lock_name
. -
#unlock(lock_name) ⇒ Object
Release a lock held by this object.
-
#with_lock(lock_name, lock_timeout, lock_wait_timeout = nil) ⇒ Object
Obtains the requested
lock_name
, then yields to the supplied block.
Instance Method Details
#lock(lock_name, lock_timeout, lock_wait_timeout = nil) ⇒ Object
Attempts to obtain the named lock lock_name
. If the lock is already held by another process, this method blocks until one of the following occurs:
-
the lock is released by the process that currently holds it
-
the lock expires, by reaching it’s timeout period
-
the
lock_wait_timeout
period is reached.
Lock management is managed via the event publishing system; subscribers to the ‘lock?’ event indicate whether a lock is available by their response to the event. A value of false indicates the lock is currently held; a response of true indicates the lock has been granted.
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 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 |
# File 'lib/batch-kit/lockable.rb', line 44 def lock(lock_name, lock_timeout, lock_wait_timeout = nil) unless lock_timeout && lock_timeout.is_a?(Fixnum) && lock_timeout > 0 raise ArgumentError, "Invalid lock_timeout; must be > 0" end unless lock_wait_timeout.nil? || (lock_wait_timeout.is_a?(Fixnum) && lock_wait_timeout >= 0) raise ArgumentError, "Invalid lock_wait_timeout; must be nil or >= 0" end unless Events.has_subscribers?(self, 'lock?') if self.respond_to?(:log) log.warn "No lock manager available; proceeding without locking" end return end lock_wait_timeout ||= lock_timeout lock_expire_time = nil wait_expire_time = Time.now + lock_wait_timeout if lock_wait_timeout > 0 # Loop waiting for lock if not available begin Timeout.timeout(lock_wait_timeout) do i = 0 loop do lock_holder = {} lock_expire_time = Events.publish(self, 'lock?', lock_name, lock_timeout, lock_holder) break if lock_expire_time if i == 0 Events.publish(self, 'lock_held', lock_name, lock_holder[:lock_holder], lock_holder[:lock_expires_at]) Events.publish(self, 'lock_wait', lock_name, wait_expire_time) end sleep 1 i += 1 end Events.publish(self, 'locked', lock_name, lock_expire_time) end rescue Timeout::Error Events.publish(self, 'lock_wait_timeout', lock_name, wait_expire_time) raise Timeout::Error, "Timed out waiting for lock '#{lock_name}' to become available" end else # No waiting for lock to become free lock_holder = {} if lock_expire_time = Events.publish(self, 'lock?', lock_name, lock_timeout, lock_holder) Events.publish(self, 'locked', lock_name, lock_expire_time) else Events.publish(self, 'lock_held', lock_name, lock_holder[:lock_holder], lock_holder[:lock_expires_at]) Events.publish(self, 'lock_wait_timeout', lock_name, wait_expire_time) raise Timeout::Error, "Lock '#{lock_name}' is already in use" end end end |
#unlock(lock_name) ⇒ Object
Release a lock held by this object.
103 104 105 106 107 108 109 110 |
# File 'lib/batch-kit/lockable.rb', line 103 def unlock(lock_name) unless Events.has_subscribers?(self, 'unlock?') return end if Events.publish(self, 'unlock?', lock_name) Events.publish(self, 'unlocked', lock_name) end end |
#with_lock(lock_name, lock_timeout, lock_wait_timeout = nil) ⇒ Object
Obtains the requested lock_name
, then yields to the supplied block. Ensures the lock is released when the block ends or raises an error.
127 128 129 130 131 132 133 134 |
# File 'lib/batch-kit/lockable.rb', line 127 def with_lock(lock_name, lock_timeout, lock_wait_timeout = nil) self.lock(lock_name, lock_timeout, lock_wait_timeout) begin yield ensure self.unlock(lock_name) end end |