class RedisGetlock

Constants

EXPIRE
INTERVAL
TIMEOUT
VERSION

Attributes

expire[R]
interval[R]
key[R]
logger[R]
redis[R]
timeout[R]
uuid[R]

Public Class Methods

new(redis:, key:, logger: nil, timeout: TIMEOUT, expire: EXPIRE, interval: INTERVAL) click to toggle source
# File lib/redis_getlock.rb, line 15
def initialize(redis:, key:, logger: nil, timeout: TIMEOUT, expire: EXPIRE, interval: INTERVAL)
  @redis = redis
  @key = key
  @logger = logger
  @timeout = timeout
  @expire = expire
  @interval = interval
  @uuid = SecureRandom.uuid
end

Public Instance Methods

lock() click to toggle source
# File lib/redis_getlock.rb, line 25
def lock
  logger.info { "#{log_head}Wait #{timeout < 0 ? '' : "#{timeout} sec "}to acquire a redis lock '#{key}'" } if logger
  if set_options_available?
    locked = lock_with_set_options
  else
    locked = lock_without_set_options
  end
  @thr.terminate if @thr and @thr.alive?
  if locked
    @thr = Thread.new(&method(:keeplock))
    logger.info { "#{log_head}Acquired a redis lock '#{key}'" } if logger
    true
  else
    logger.info { "#{log_head}Timeout to acquire a redis lock '#{key}'" } if logger
    false
  end
end
locked?() click to toggle source
# File lib/redis_getlock.rb, line 75
def locked?
  redis.exists(key)
end
self_locked?() click to toggle source
# File lib/redis_getlock.rb, line 79
def self_locked?
  locked? && uuid == JSON.parse(redis.get(key))['uuid']
end
synchronize() { || ... } click to toggle source
# File lib/redis_getlock.rb, line 83
def synchronize(&block)
  raise LockError unless lock
  begin
    yield
  ensure
    unlock
  end
end
try_lock() click to toggle source
# File lib/redis_getlock.rb, line 43
def try_lock
  if set_options_available?
    locked = try_lock_with_set_options
  else
    locked = try_lock_without_set_options
  end
  @thr.terminate if @thr and @thr.alive?
  if locked
    @thr = Thread.new(&method(:keeplock))
    logger.info { "#{log_head}Acquired a redis lock '#{key}'" } if logger
    true
  else
    logger.info { "#{log_head}A redis lock is already acquired '#{key}'" } if logger
    false
  end
end
unlock() click to toggle source
# File lib/redis_getlock.rb, line 60
def unlock
  @thr.terminate if @thr and @thr.alive?
  if self_locked?
    redis.del(key)
    logger.info { "#{log_head}Released a redis lock '#{key}'" } if logger
    true
  elsif locked?
    logger.info { "#{log_head}Failed to release a redis lock since somebody else locked '#{key}'" } if logger
    false
  else
    logger.info { "#{log_head}Redis lock did not exist '#{key}'" } if logger
    true
  end
end

Private Instance Methods

keeplock() click to toggle source
# File lib/redis_getlock.rb, line 156
def keeplock
  loop do
    current = Time.now.to_f
    payload = {uuid: uuid, expire_at: (current + expire).to_s}.to_json
    redis.setex(key, expire, payload) # extend expiration
    sleep interval
  end
end
lock_with_set_options() click to toggle source

redis >= 2.6.12 ref. redis.io/commands/set

# File lib/redis_getlock.rb, line 106
def lock_with_set_options
  started = Time.now.to_f
  loop do
    locked = try_lock_with_set_options
    return true if locked
    break if timeout >= 0 and (Time.now.to_f - started) >= timeout
    sleep interval
  end
  false
end
lock_without_set_options() click to toggle source

redis < 2.6.12 ref. redis.io/commands/setnx

# File lib/redis_getlock.rb, line 119
def lock_without_set_options
  started = Time.now.to_f
  loop do
    locked = try_lock_without_set_options
    return true if locked
    break if timeout >= 0 and (Time.now.to_f - started) >= timeout
    sleep interval
  end
  false
end
log_head() click to toggle source
# File lib/redis_getlock.rb, line 94
def log_head
  "PID-#{::Process.pid} TID-#{::Thread.current.object_id.to_s(36)}: "
end
set_options_available?() click to toggle source
# File lib/redis_getlock.rb, line 98
def set_options_available?
  return @set_options_avialble unless @set_options_avialble.nil?
  major, minor, patch = redis.info['redis_version'].split('.').map(&:to_i)
  @set_options_avialble = major > 2 || (major == 2 && minor > 7) || (major == 2 && minor == 6 && patch >= 12)
end
try_lock_with_set_options() click to toggle source

redis >= 2.6.12 ref. redis.io/commands/set

# File lib/redis_getlock.rb, line 132
def try_lock_with_set_options
  current = Time.now.to_f
  payload = {uuid: uuid, expire_at: (current + expire).to_s}.to_json
  return true if redis.set(key, payload, {nx: true, ex: expire}) # key does not exist
  false
end
try_lock_without_set_options() click to toggle source

redis < 2.6.12 ref. redis.io/commands/setnx

# File lib/redis_getlock.rb, line 141
def try_lock_without_set_options
  current = Time.now.to_f
  payload = {uuid: uuid, expire_at: (current + expire).to_s}.to_json
  if redis.setnx(key, payload) # key does not exist
    redis.expire(key, expire)
    return true # acquire lock
  end
  previous = JSON.parse(redis.get(key))
  if previous['expire_at'].to_f < current # key exists, but previous
    compared = redis.getset(key, paylod)
    return true if previous['expire_at'] == compared['expire_at'] # acquire lock
  end
  false
end