Class: Redis::Lock

Inherits:
BaseObject show all
Defined in:
lib/redis/lock.rb

Overview

Class representing a lock. This functions like a proxy class, in that you can say @object.lock_name { block } to use the lock and also directly, but it is better to use the lock :foo class method in your class to define a lock.

Defined Under Namespace

Classes: LockTimeout

Instance Attribute Summary

Attributes inherited from BaseObject

#key, #options

Instance Method Summary collapse

Methods inherited from BaseObject

#allow_expiration, #as_json, #redis, #set_expiration, #to_hash, #to_json

Methods included from Helpers::CoreCommands

#delete, #exists, #exists?, #expire, #expireat, #marshal, #move, #persist, #rename, #renamenx, #serializer, #ttl, #type, #unmarshal

Constructor Details

#initialize(key, *args) ⇒ Lock

Returns a new instance of Lock.


14
15
16
17
18
19
# File 'lib/redis/lock.rb', line 14

def initialize(key, *args)
  super(key, *args)
  @options[:timeout] ||= 5
  @options[:init] = false if @options[:init].nil? # default :init to false
  redis.setnx(key, @options[:start]) unless @options[:start] == 0 || @options[:init] === false
end

Instance Method Details

#generate_expirationObject

Return expiration in milliseconds


70
71
72
# File 'lib/redis/lock.rb', line 70

def generate_expiration
  ((@options[:expiration].nil? ? 1 : @options[:expiration].to_f) * 1000).to_i
end

#lockObject

Get the lock and execute the code block. Any other code that needs the lock (on any server) will spin waiting for the lock up to the :timeout that was specified when the lock was defined.

Raises:

  • (ArgumentError)

28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/redis/lock.rb', line 28

def lock
  raise ArgumentError, 'Block not given' unless block_given?
  expiration_ms = generate_expiration
  expiration_s  = expiration_ms / 1000.0
  end_time = nil
  try_until_timeout do
    end_time = Time.now.to_i + expiration_s
    # Set a NX record and use the Redis expiration mechanism.
    # Empty value because the presence of it is enough to lock
    # `px` only except an Integer in millisecond
    break if redis.set(key, nil, px: expiration_ms, nx: true)

    # Backward compatibility code
    # TODO: remove at the next major release for performance
    unless @options[:expiration].nil?
      old_expiration = redis.get(key).to_f

      # Check it was not an empty string with `zero?` and
      # the expiration time is passed.
      if !old_expiration.zero? && old_expiration < Time.now.to_f
        expiration_ms = generate_expiration
        expiration_s  = expiration_ms / 1000.0
        end_time = Time.now.to_i + expiration_s
        break if redis.set(key, nil, px: expiration_ms)
      end
    end
  end
  begin
    yield
  ensure
    # We need to be careful when cleaning up the lock key.  If we took a really long
    # time for some reason, and the lock expired, someone else may have it, and
    # it's not safe for us to remove it.  Check how much time has passed since we
    # wrote the lock key and only delete it if it hasn't expired (or we're not using
    # lock expiration)
    if @options[:expiration].nil? || end_time > Time.now.to_f
      redis.del(key)
    end
  end
end

#valueObject


21
22
23
# File 'lib/redis/lock.rb', line 21

def value
  nil
end