lib/redis/lock.rb in redis-objects-0.2.2 vs lib/redis/lock.rb in redis-objects-0.2.3
- old
+ new
@@ -29,19 +29,55 @@
# (on any server) will spin waiting for the lock up to the :timeout
# that was specified when the lock was defined.
def lock(&block)
start = Time.now
gotit = false
+ expiration = nil
while Time.now - start < @options[:timeout]
- gotit = redis.setnx(key, 1)
+ 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://code.google.com/p/redis/wiki/SetnxCommand
+ 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
- redis.del(key)
+ # 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
+
+ def generate_expiration
+ @options[:expiration].nil? ? 1 : (Time.now + @options[:expiration].to_f + 1).to_f
end
end
end
\ No newline at end of file