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