@@ -420,7 +420,12 @@ def lock(self, name, timeout=None, sleep=0.1):
420420 when the lock is in blocking mode and another client is currently
421421 holding the lock.
422422 """
423- return Lock (self , name , timeout = timeout , sleep = sleep )
423+ from distutils .version import StrictVersion
424+ version = self .info ()['redis_version' ]
425+ if StrictVersion (version ) < StrictVersion ('2.6.0' ):
426+ return Lock (self , name , timeout = timeout , sleep = sleep )
427+ else :
428+ return LuaLock (self , name , timeout = timeout , sleep = sleep )
424429
425430 def pubsub (self , shard_hint = None ):
426431 """
@@ -2211,3 +2216,94 @@ def release(self):
22112216 self .acquired_until = None
22122217 if delete_lock :
22132218 self .redis .delete (self .name )
2219+
2220+
2221+ class LuaLock (object ):
2222+ """
2223+ Like Lock, but using key expiration and LUA for transactions.
2224+
2225+ Backward compatible with Lock,
2226+ except for not keeping released locks in redis.
2227+
2228+ NOTE: Requires Redis 2.6+
2229+ """
2230+ # KEYS[1] - lock name, ARGV[1] - timeout, ARGV[2] - timeout at
2231+ LUA_LOCK_ACQUIRE_SCRIPT = """
2232+ local ret = redis.call('setnx', KEYS[1], ARGV[2])
2233+ if (ret == 1) then
2234+ redis.call('expire', KEYS[1], ARGV[1])
2235+ return true
2236+ end
2237+ return false
2238+ """
2239+ LUA_LOCK_RELEASE_SCRIPT = """
2240+ if (redis.call('get', KEYS[1])) then
2241+ redis.call('del', KEYS[1])
2242+ return true
2243+ end
2244+ return false
2245+ """
2246+ LUA_LOCK_ACQUIRE = None
2247+ LUA_LOCK_RELEASE = None
2248+
2249+ def __init__ (self , redis , name , timeout = None , sleep = 0.1 ):
2250+ """
2251+ Create a new Lock instance named ``name`` using the Redis client
2252+ supplied by ``redis``.
2253+
2254+ ``timeout`` indicates a maximum life for the lock.
2255+ By default, it will remain locked until release() is called.
2256+
2257+ ``sleep`` indicates the amount of time to sleep per loop iteration
2258+ when the lock is in blocking mode and another client is currently
2259+ holding the lock.
2260+ """
2261+ self .redis = redis
2262+ self .name = name
2263+ self .acquired_until = None
2264+ self .timeout = timeout
2265+ self .sleep = sleep
2266+ if self .timeout and self .sleep > self .timeout :
2267+ raise LockError ("'sleep' must be less than 'timeout'" )
2268+ # If the Acquire/Release scripts are not in redis- load them
2269+ if not callable (LuaLock .LUA_LOCK_ACQUIRE ):
2270+ LuaLock .LUA_LOCK_ACQUIRE = redis .register_script (
2271+ LuaLock .LUA_LOCK_ACQUIRE_SCRIPT )
2272+ if not callable (LuaLock .LUA_LOCK_RELEASE ):
2273+ LuaLock .LUA_LOCK_RELEASE = redis .register_script (
2274+ LuaLock .LUA_LOCK_RELEASE_SCRIPT )
2275+
2276+ def __enter__ (self ):
2277+ return self .acquire ()
2278+
2279+ def __exit__ (self , exc_type , exc_value , traceback ):
2280+ self .release ()
2281+
2282+ def acquire (self , blocking = True ):
2283+ """
2284+ Use Redis to hold a shared, distributed lock named ``name``.
2285+ Returns True once the lock is acquired.
2286+
2287+ If ``blocking`` is False, always return immediately. If the lock
2288+ was acquired, return True, otherwise return False.
2289+ """
2290+ sleep = self .sleep
2291+ timeout = self .timeout if self .timeout else Lock .LOCK_FOREVER
2292+ while 1 :
2293+ timeout_at = mod_time .time () + self .timeout if self .timeout \
2294+ else Lock .LOCK_FOREVER
2295+ if LuaLock .LUA_LOCK_ACQUIRE (keys = [self .name ],
2296+ args = [int (timeout ), timeout_at ]):
2297+ self .acquired_until = timeout_at
2298+ return True
2299+ if not blocking :
2300+ return False
2301+ mod_time .sleep (sleep )
2302+
2303+ def release (self ):
2304+ """
2305+ Releases the already acquired lock
2306+ """
2307+ if not LuaLock .LUA_LOCK_RELEASE (keys = [self .name ]):
2308+ raise ValueError ("Cannot release an unlocked lock" )
2309+ self .acquired_until = None
0 commit comments