Class: RedisLock
- Inherits:
-
Object
- Object
- RedisLock
- Defined in:
- lib/redis_lock.rb
Constant Summary collapse
- VERSION =
Gem version
"1.2.0"- DEFAULT_ATTRS =
Original defaults
{ redis: nil, # Redis instance with defaults key: 'RedisLock::default', # Redis key to store the lock autorelease: 10.0, # seconds to expire retry: true, # false to only try to acquire once retry_timeout: 10.0, # max number of seconds to keep doing retries if the lock is not available retry_sleep: 0.1 # seconds to sleep before the nex retry }
Instance Attribute Summary collapse
-
#acquired_token ⇒ Object
if the lock was successfully acquired, this is the token used to identify the lock.
-
#last_acquire_retries ⇒ Object
info about how many times had to retry to acquire the lock on the last call to acquire.
Class Method Summary collapse
-
.acquire(opts = {}, &block) ⇒ Object
Acquire a lock.
-
.configure {|@@config| ... } ⇒ Object
Configure defaults.
-
.configure_restore_defaults ⇒ Object
Restore original defaults.
Instance Method Summary collapse
-
#acquire ⇒ Object
Try to acquire the lock.
-
#acquired? ⇒ Boolean
Check if last lock acquisition was successful.
-
#initialize(opts = {}) ⇒ RedisLock
constructor
A new instance of RedisLock.
-
#release ⇒ Object
Release the lock.
Constructor Details
#initialize(opts = {}) ⇒ RedisLock
Returns a new instance of RedisLock.
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
# File 'lib/redis_lock.rb', line 55 def initialize(opts={}) # Check if options are valid allowed_opts = DEFAULT_ATTRS.keys invalid_opts = opts.keys - allowed_opts raise ArgumentError.new("Invalid options: #{invalid_opts.inspect}. Please use one of #{allowed_opts.inspect} ") unless invalid_opts.empty? # Set attributes from options or defaults self.redis = opts[:redis] || @@config.redis || Redis.new self.redis = Redis.new(redis) if redis.is_a? Hash # allow to use Redis options instead of a redis instance self.key = opts[:key] || @@config.key self.autorelease = opts[:autorelease] || @@config.autorelease self.retry = opts.include?(:retry) ? opts[:retry] : @@config.retry self.retry_timeout = opts[:retry_timeout] || @@config.retry_timeout self.retry_sleep = opts[:retry_sleep] || @@config.retry_sleep end |
Instance Attribute Details
#acquired_token ⇒ Object
if the lock was successfully acquired, this is the token used to identify the lock. False otherwise.
23 24 25 |
# File 'lib/redis_lock.rb', line 23 def acquired_token @acquired_token end |
#last_acquire_retries ⇒ Object
info about how many times had to retry to acquire the lock on the last call to acquire. First try counts as 0
24 25 26 |
# File 'lib/redis_lock.rb', line 24 def last_acquire_retries @last_acquire_retries end |
Class Method Details
.acquire(opts = {}, &block) ⇒ Object
Acquire a lock. Use options to override defaults. This method makes sure to release the lock as soon as the block is finalized.
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
# File 'lib/redis_lock.rb', line 39 def self.acquire(opts={}, &block) if block.arity != 1 raise ArgumentError.new('Expected lock parameter in block. Example: RedisLock.acquire(opts){|lock| do_stuff if lock.acquired? }') end lock = RedisLock.new(opts) if lock.acquire begin block.call(lock) # lock.acquired? => true ensure lock.release # Exception => release early end else block.call(lock) # lock.acquired? => false end end |
.configure {|@@config| ... } ⇒ Object
Configure defaults
33 34 35 |
# File 'lib/redis_lock.rb', line 33 def self.configure yield @@config end |
.configure_restore_defaults ⇒ Object
Restore original defaults
27 28 29 |
# File 'lib/redis_lock.rb', line 27 def self.configure_restore_defaults @@config = Struct.new(*DEFAULT_ATTRS.keys).new(*DEFAULT_ATTRS.values) end |
Instance Method Details
#acquire ⇒ Object
Try to acquire the lock. Retrun true on success, false on failure (someone else has the lock)
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
# File 'lib/redis_lock.rb', line 74 def acquire @first_try_time ||= Time.now @token ||= SecureRandom.uuid # token is used to make sure that we own the lock when releasing it @retries ||= 0 # Lock using a redis key, if not exists (NX) with an expiration time (EX). # NOTE that the NX and EX options are not supported by REDIS versions older than 2.6.12 # See lock pattern: http://redis.io/commands/SET expire = (autorelease * 1000).to_i # to milliseconds if redis.set(key, @token, nx: true, px: expire) self.acquired_token = @token # assign acquired_token else self.acquired_token = nil # clear acquired_token, to make the acquired? method return false # Wait and try again if retry option is set and didn't timeout if self.retry and (Time.now - @first_try_time) < retry_timeout sleep retry_sleep # wait @retries += 1 return acquire # and try again end end self.last_acquire_retries = @retries @retries = nil # reset retries @first_try_time = nil # reset timestamp return self.acquired? end |
#acquired? ⇒ Boolean
Check if last lock acquisition was successful. Note that it doesn’t track autorelease, if the lock is naturally expired, this value will still be true.
130 131 132 |
# File 'lib/redis_lock.rb', line 130 def acquired? !!self.acquired_token # acquired_token is only set on success end |
#release ⇒ Object
Release the lock. Returns a Symbol with the status of the operation:
* :success if properly released
* :already_released if the lock was already released or expired (other process could be using it now)
* :not_acquired if the lock was not acquired (no release action was made because it was not needed)
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
# File 'lib/redis_lock.rb', line 109 def release if acquired? if redis.respond_to? :eval # if eval command is available, run a lua script because is a faster way to remove the key script = 'if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return nil end' ret = redis.eval(script, [key], [self.acquired_token]) else # i.e. MockRedis doesn't have eval ret = if redis.get(key) == self.acquired_token then redis.del(key) else nil end end self.acquired_token = nil # cleanup acquired token if ret == nil :already_released else :success end else :not_acquired end end |