class MasterLock::RedisLock

RedisLock implements a mutex in Redis according to the strategy documented at redis.io/commands/SET#patterns. The lock has a string identifier and when acquired will be registered to an owner, also identified by a string. Locks have an expiration time, after which they will be released automatically so that unexpected failures do not result in locks getting stuck.

Constants

DEFAULT_SLEEP_INTERVAL

Attributes

key[R]

@return [String] the unique identifier for the locked resource

owner[R]

@return [String] the identity of the owner acquiring the lock

redis[R]

@return [Redis] the Redis connection used to manage lock

ttl[R]

@return [Fixnum] the lifetime of the lock in seconds

Public Class Methods

new( redis:, key:, owner:, ttl:, sleep_interval: DEFAULT_SLEEP_INTERVAL ) click to toggle source
# File lib/master_lock/redis_lock.rb, line 25
def initialize(
  redis:,
  key:,
  owner:,
  ttl:,
  sleep_interval: DEFAULT_SLEEP_INTERVAL
)
  @redis = redis
  @key = key
  @owner = owner
  @ttl = ttl
  @sleep_interval = sleep_interval
end

Public Instance Methods

acquire(timeout:) click to toggle source

Attempt to acquire the lock. If the lock is already held, this will attempt multiple times to acquire the lock until the timeout period is up.

@param [Fixnum] how long to wait to acquire the lock before failing @return [Boolean] whether the lock was acquired successfully

# File lib/master_lock/redis_lock.rb, line 44
def acquire(timeout:)
  timeout_time = Time.now + timeout
  loop do
    locked = redis.set(redis_key, owner, nx: true, px: ttl_ms)
    return true if locked
    return false if Time.now >= timeout_time
    sleep(@sleep_interval)
  end
end
extend() click to toggle source

Extend the expiration time of the lock if still held by this owner. If the lock is no longer held by the owner, this method will fail and return false. The lock lifetime is extended by the configured ttl.

@return [Boolean] whether the lock was extended successfully

# File lib/master_lock/redis_lock.rb, line 59
def extend
  result = eval_script(
    RedisScripts::EXTEND_SCRIPT,
    RedisScripts::EXTEND_SCRIPT_HASH,
    keys: [redis_key],
    argv: [owner, ttl_ms]
  )
  result != 0
end
release() click to toggle source

Release the lock if still held by this owner. If the lock is no longer held by the owner, this method will fail and return false.

@return [Boolean] whether the lock was released successfully

# File lib/master_lock/redis_lock.rb, line 73
def release
  result = eval_script(
    RedisScripts::RELEASE_SCRIPT,
    RedisScripts::RELEASE_SCRIPT_HASH,
    keys: [redis_key],
    argv: [owner]
  )
  result != 0
end

Private Instance Methods

eval_script(script, script_hash, keys:, argv:) click to toggle source
# File lib/master_lock/redis_lock.rb, line 89
def eval_script(script, script_hash, keys:, argv:)
  begin
    redis.evalsha(script_hash, keys: keys, argv: argv)
  rescue Redis::CommandError
    redis.eval(script, keys: keys, argv: argv)
  end
end
redis_key() click to toggle source
# File lib/master_lock/redis_lock.rb, line 97
def redis_key
  "#{MasterLock.config.key_prefix}:#{key}"
end
ttl_ms() click to toggle source
# File lib/master_lock/redis_lock.rb, line 85
def ttl_ms
  (ttl * 1000).to_i
end