class Redstruct::Lock
Implementation of a simple binary lock (locked/not locked), with option to block and wait for the lock. Uses two redis structures: a string for the lease, and a list for blocking operations.
Constants
- DEFAULT_EXPIRY
The default expiry on the underlying redis keys, in seconds; can be between 0 and 1 as a float for milliseconds
- DEFAULT_TIMEOUT
The default timeout when blocking, in seconds
Attributes
@return [Float, Integer] the expiry of the underlying redis structure in seconds
@return [String] the resource name (or ID of the lock)
@return [Integer] if greater than 0, will block until timeout is reached or the lock is acquired
@return [String] the current token
Public Class Methods
@param [String] resource the name of the resource to be locked (or ID) @param [Integer] expiry in seconds; to prevent infinite locking, you should pass a minimum expiry; you can pass 0 if you want to control it yourself @param [Integer] timeout in seconds; if > 0, will block when trying to obtain the lock; if 0, blocks indefinitely; if nil, does not block
Redstruct::Factory::Object::new
# File lib/redstruct/lock.rb, line 37 def initialize(resource, expiry: DEFAULT_EXPIRY, timeout: DEFAULT_TIMEOUT, **options) super(**options) @resource = resource @token = nil @expiry = expiry @acquired = Redstruct::Utils::AtomicCounter.new @timeout = case timeout when nil then nil when Float::INFINITY then 0 else timeout.to_i end factory = @factory.factory(@resource) @lease = factory.string('lease') @tokens = factory.list('tokens') end
Public Instance Methods
Attempts to acquire the lock. First attempts to grab the lease (a redis string). If the current token is already the lease token, the lock is considered acquired. If there is no current lease, then sets it to the current token. If there is a current lease that is not the current token, then:
1) If this not a blocking lock (see Lock#blocking?), return false 2) If this is a blocking lock, block and wait for the next token to be pushed on the tokens list 3) If a token was pushed, set it as our token and refresh the expiry
@return [Boolean] True if acquired, false otherwise
# File lib/redstruct/lock.rb, line 93 def acquire acquired = false token = non_blocking_acquire token = blocking_acquire if token.nil? && blocking? unless token.nil? @lease.expire(@expiry) @token = token @acquired.increment acquired = true end return acquired end
Whether or not the lock will block when attempting to acquire it @return [Boolean]
# File lib/redstruct/lock.rb, line 81 def blocking? return !@timeout.nil? end
Deletes all traces of this lock @return [Boolean] true if deleted, false otherwise
# File lib/redstruct/lock.rb, line 59 def delete return coerce_bool(delete_script(keys: [@lease.key, @tokens.key])) end
Executes the given block if the lock can be acquired @yield Block to be executed if the lock is acquired
# File lib/redstruct/lock.rb, line 65 def locked Thread.handle_interrupt(Exception => :never) do begin if acquire Thread.handle_interrupt(Exception => :immediate) do yield end end ensure release end end end
Releases the lock only if the current token is the value of the lease. If the lock is a blocking lock (see Lock#blocking?
), push the next token on the tokens list. @return [Boolean] True if released, false otherwise
# File lib/redstruct/lock.rb, line 113 def release return false if @token.nil? released = true if @acquired.decrement.zero? keys = [@lease.key, @tokens.key] argv = [@token, generate_token, (@expiry.to_f * 1000).floor] released = coerce_bool(release_script(keys: keys, argv: argv)) @token = nil end return released end
Private Instance Methods
# File lib/redstruct/lock.rb, line 138 def blocking_acquire return @tokens.pop(timeout: @timeout) end
# File lib/redstruct/lock.rb, line 197 def generate_token return SecureRandom.uuid end
Redstruct::Factory::Object#inspectable_attributes
# File lib/redstruct/lock.rb, line 201 def inspectable_attributes super.merge(expiry: @expiry, blocking: blocking?) end
# File lib/redstruct/lock.rb, line 131 def non_blocking_acquire keys = [@lease.key, @tokens.key] argv = [@token || generate_token] return acquire_script(keys: keys, argv: argv) end