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 collapse

Instance Method Summary collapse

Methods inherited from BaseObject

expiration_filter, #redis, #set_expiration

Constructor Details

#initialize(key, *args) ⇒ Lock

Returns a new instance of Lock.



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

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 Attribute Details

#keyObject (readonly)

Returns the value of attribute key.



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

def key
  @key
end

#optionsObject (readonly)

Returns the value of attribute options.



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

def options
  @options
end

Instance Method Details

#clearObject Also known as: delete

Clear the lock. Should only be needed if there’s a server crash or some other event that gets locks in a stuck state.



24
25
26
# File 'lib/redis/lock.rb', line 24

def clear
  redis.del(key)
end

#generate_expirationObject



80
81
82
# File 'lib/redis/lock.rb', line 80

def generate_expiration
  @options[:expiration].nil? ? 1 : (Time.now + @options[:expiration].to_f + 1).to_f
end

#lock(&block) ⇒ Object

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:



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
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/redis/lock.rb', line 32

def lock(&block)
  start = Time.now
  gotit = false
  expiration = nil
  while Time.now - start < @options[:timeout]
    expiration = generate_expiration
    # Use the expiration as the value of the lock.
    gotit = redis.setnx(key, expiration)
    break if gotit

    # Lock is being held.  Now check to see if it's expired (if we're using
    # lock expiration).
    # See "Handling Deadlocks" section on http://redis.io/commands/setnx
    if !@options[:expiration].nil?
      old_expiration = redis.get(key).to_f

      if old_expiration < Time.now.to_f
        # If it's expired, use GETSET to update it.
        expiration = generate_expiration
        old_expiration = redis.getset(key, expiration).to_f

        # Since GETSET returns the old value of the lock, if the old expiration
        # is still in the past, we know no one else has expired the locked
        # and we now have it.
        if old_expiration < Time.now.to_f
          gotit = true
          break
        end
      end
    end

    sleep 0.1
  end
  raise LockTimeout, "Timeout on lock #{key} exceeded #{@options[:timeout]} sec" unless gotit
  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? || expiration > Time.now.to_f
      redis.del(key)
    end
  end
end