Released redis_getlock gem to provide distributed locking using redis. This is useful, for example, when you want to run only one worker among multiple workers deployed on multiple servers. 

Unlike other implementations avilable, this gem ensures releasing orphaned lock shortly.


How It Works

This gem basically works as http://redis.io/commands/set describes for redis >= 2.6.12, and http://redis.io/commands/setnx describes for redis < 2.6.12.

Simple ruby codes which http://redis.io/commands/set describes are as follows:

loop do
  break if redis.set(key, 'anystring', {nx: true, ex: expire})
  sleep 1
end
puts 'get lock'
begin
  # do a job
ensure
  redis.del(key) # unlock
end

The problem here is the value of expire. The expiration time expire is necessary so that a lock will eventually be released even if a process is crashed or killed by SIGKILL before deleting the key. However, how long should we set if we are uncertain how long a job takes?

This gem takes a following approach to resolve this problem.

  1. Expiration time is set to 2 (default) seconds
  2. Extend the lock in each 1 (default) sencond interval invoking another thread

This way ensures to release orphaned lock in 2 seconds, and we are released from caring of the value of expire!!

Simple ruby codes to explain how this gem works are as follows:

loop do
  break if redis.set(key, 'anystring', {nx: true, ex: 2})
  sleep 1
end
puts 'get lock'
thr = Thread.new do
  loop do
    redis.expire(key, 2)
    sleep 1
  end
end
begin
  # do a job
ensure
  thr.terminate
  redis.del(key) # unlock
end


Installation

Add this line to your application's Gemfile:

gem 'redis_getlock'

And then execute:

$ bundle

Or install it yourself as:

$ gem install redis_getlock


Usage

Similarly with ruby standard library mutex, following methods are available:

  • lock
    • Attempts to grab the lock and waits if it isn’t available.
  • locked?
    • Returns true if this lock is currently held by some.
  • synchronize {}
    • Obtains a lock, runs the block, and releases the lock when the block completes.
  • unlock
    • Releases the lock.


Example

require 'redis'
require 'redis_getlock'

redis = Redis.new # Redis.new(options)
mutex = RedisGetlock.new(redis: redis, key: 'lock_key')

mutex.lock
begin
  puts 'get lock'
ensure
  mutex.unlock
end

mutex.synchronize do
  puts 'get lock'
end