Skip to content

Commit d127929

Browse files
laixintaoandymccurdy
authored andcommitted
Lock.extend() can now replace the lock's existing TTL with a new value
Lock.extend() now has a new option, `replace_ttl`. When False (the default), Lock.extend() adds the `additional_time` to the lock's existing TTL. When replace_ttl=True, the lock's existing TTL is replaced with the value of `additional_time`.
1 parent f2f470e commit d127929

File tree

3 files changed

+46
-14
lines changed

3 files changed

+46
-14
lines changed

CHANGES

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@
2222
a roundtrip to the server by not having to call UNWATCH within
2323
Pipeline.reset(). Thanks @nickgaya, #1299/#1302
2424
* Add the KEEPTTL option for the SET command. Thanks @laixintao #1304/#1280
25+
* Lock.extend() now has a new option, `replace_ttl`. When False (the
26+
default), Lock.extend() adds the `additional_time` to the lock's existing
27+
TTL. When replace_ttl=True, the lock's existing TTL is replaced with
28+
the value of `additional_time`.
2529
* 3.4.1
2630
* Move the username argument in the Redis and Connection classes to the
2731
end of the argument list. This helps those poor souls that specify all

redis/lock.py

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class Lock(object):
1919
lua_reacquire = None
2020

2121
# KEYS[1] - lock name
22-
# ARGS[1] - token
22+
# ARGV[1] - token
2323
# return 1 if the lock was released, otherwise 0
2424
LUA_RELEASE_SCRIPT = """
2525
local token = redis.call('get', KEYS[1])
@@ -31,8 +31,10 @@ class Lock(object):
3131
"""
3232

3333
# KEYS[1] - lock name
34-
# ARGS[1] - token
35-
# ARGS[2] - additional milliseconds
34+
# ARGV[1] - token
35+
# ARGV[2] - additional milliseconds
36+
# ARGV[3] - "0" if the additional time should be added to the lock's
37+
# existing ttl or "1" if the existing ttl should be replaced
3638
# return 1 if the locks time was extended, otherwise 0
3739
LUA_EXTEND_SCRIPT = """
3840
local token = redis.call('get', KEYS[1])
@@ -46,13 +48,18 @@ class Lock(object):
4648
if expiration < 0 then
4749
return 0
4850
end
49-
redis.call('pexpire', KEYS[1], expiration + ARGV[2])
51+
52+
local newttl = ARGV[2]
53+
if ARGV[3] == "0" then
54+
newttl = ARGV[2] + expiration
55+
end
56+
redis.call('pexpire', KEYS[1], newttl)
5057
return 1
5158
"""
5259

5360
# KEYS[1] - lock name
54-
# ARGS[1] - token
55-
# ARGS[2] - milliseconds
61+
# ARGV[1] - token
62+
# ARGV[2] - milliseconds
5663
# return 1 if the locks time was reacquired, otherwise 0
5764
LUA_REACQUIRE_SCRIPT = """
5865
local token = redis.call('get', KEYS[1])
@@ -231,26 +238,39 @@ def do_release(self, expected_token):
231238
raise LockNotOwnedError("Cannot release a lock"
232239
" that's no longer owned")
233240

234-
def extend(self, additional_time):
241+
def extend(self, additional_time, replace_ttl=False):
235242
"""
236243
Adds more time to an already acquired lock.
237244
238245
``additional_time`` can be specified as an integer or a float, both
239246
representing the number of seconds to add.
247+
248+
``replace_ttl`` if False (the default), add `additional_time` to
249+
the lock's existing ttl. If True, replace the lock's ttl with
250+
`additional_time`.
240251
"""
241252
if self.local.token is None:
242253
raise LockError("Cannot extend an unlocked lock")
243254
if self.timeout is None:
244255
raise LockError("Cannot extend a lock with no timeout")
245-
return self.do_extend(additional_time)
256+
return self.do_extend(additional_time, replace_ttl)
246257

247-
def do_extend(self, additional_time):
258+
def do_extend(self, additional_time, replace_ttl):
248259
additional_time = int(additional_time * 1000)
249-
if not bool(self.lua_extend(keys=[self.name],
250-
args=[self.local.token, additional_time],
251-
client=self.redis)):
252-
raise LockNotOwnedError("Cannot extend a lock that's"
253-
" no longer owned")
260+
if not bool(
261+
self.lua_extend(
262+
keys=[self.name],
263+
args=[
264+
self.local.token,
265+
additional_time,
266+
replace_ttl and "1" or "0"
267+
],
268+
client=self.redis,
269+
)
270+
):
271+
raise LockNotOwnedError(
272+
"Cannot extend a lock that's" " no longer owned"
273+
)
254274
return True
255275

256276
def reacquire(self):

tests/test_lock.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,14 @@ def test_extend_lock(self, r):
149149
assert 16000 < r.pttl('foo') <= 20000
150150
lock.release()
151151

152+
def test_extend_lock_replace_ttl(self, r):
153+
lock = self.get_lock(r, 'foo', timeout=10)
154+
assert lock.acquire(blocking=False)
155+
assert 8000 < r.pttl('foo') <= 10000
156+
assert lock.extend(10, replace_ttl=True)
157+
assert 8000 < r.pttl('foo') <= 10000
158+
lock.release()
159+
152160
def test_extend_lock_float(self, r):
153161
lock = self.get_lock(r, 'foo', timeout=10.0)
154162
assert lock.acquire(blocking=False)

0 commit comments

Comments
 (0)