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

expiry[R]

@return [Float, Integer] the expiry of the underlying redis structure in seconds

resource[R]

@return [String] the resource name (or ID of the lock)

timeout[R]

@return [Integer] if greater than 0, will block until timeout is reached or the lock is acquired

token[R]

@return [String] the current token

Public Class Methods

new(resource, expiry: DEFAULT_EXPIRY, timeout: DEFAULT_TIMEOUT, **options) click to toggle source

@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

Calls superclass method 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

acquire() click to toggle source

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
blocking?() click to toggle source

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
delete() click to toggle source

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
locked() { || ... } click to toggle source

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
release() click to toggle source

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

blocking_acquire() click to toggle source
# File lib/redstruct/lock.rb, line 138
def blocking_acquire
  return @tokens.pop(timeout: @timeout)
end
generate_token() click to toggle source
# File lib/redstruct/lock.rb, line 197
def generate_token
  return SecureRandom.uuid
end
inspectable_attributes() click to toggle source
# File lib/redstruct/lock.rb, line 201
def inspectable_attributes
  super.merge(expiry: @expiry, blocking: blocking?)
end
non_blocking_acquire() click to toggle source
# 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