Skip to content

Commit c7a1816

Browse files
author
Nati CT
committed
Added a locking object with key expiry and LUA transactions
1 parent b2101d3 commit c7a1816

File tree

1 file changed

+97
-1
lines changed

1 file changed

+97
-1
lines changed

redis/client.py

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)