Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c55e6f3812
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| def test_reacquire_lock_zero_timeout_keeps_lock_persistent(self, r): | ||
| # issue #4279: reacquiring a persistent (timeout=0) lock used to | ||
| # delete the key via pexpire(0) while reporting success. | ||
| lock = self.get_lock(r, "foo", timeout=0) |
There was a problem hiding this comment.
Put the new tests in a class with get_lock
These new test methods are under TestLockClassSelection, which does not define or inherit the get_lock helper from TestLock; when pytest reaches this line it raises AttributeError before the regression is exercised. Move both zero-timeout tests into TestLock or instantiate with r.lock(..., lock_class=Lock) so the suite can pass and the coverage is effective.
Useful? React with 👍 / 👎.
| if ARGV[2] ~= '0' then | ||
| redis.call('pexpire', KEYS[1], ARGV[2]) | ||
| end |
There was a problem hiding this comment.
Mirror the zero-timeout fix into asyncio
This patch fixes only the synchronous lock script; redis/asyncio/lock.py still calls pexpire unconditionally in its LUA_REACQUIRE_SCRIPT, so await client.lock("foo", timeout=0).reacquire() will still delete a persistent lock while returning success for asyncio users. Please apply the same guard and add the async regression coverage to keep the mirrored lock implementations aligned.
AGENTS.md reference: AGENTS.md:L146-L149
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.
Reviewed by Cursor Bugbot for commit c55e6f3. Configure here.
| -- it must only re-verify ownership, never delete the key via pexpire(0) | ||
| if ARGV[2] ~= '0' then | ||
| redis.call('pexpire', KEYS[1], ARGV[2]) | ||
| end |
There was a problem hiding this comment.
Async reacquire still deletes locks
High Severity
The sync LUA_REACQUIRE_SCRIPT now skips pexpire when the TTL is 0, but the async lock still always runs pexpire. Calling reacquire() on an asyncio Lock with timeout=0 still deletes the key while returning success—the same failure this PR aims to fix. Project guidance requires sync and async lock behavior to stay aligned.
Reviewed by Cursor Bugbot for commit c55e6f3. Configure here.
| with pytest.raises(LockError): | ||
| lock.extend(10, replace_ttl=True) | ||
| assert r.exists("foo") == 1 | ||
| lock.release() |
There was a problem hiding this comment.
Regression tests call missing helper
Medium Severity
The new regression tests live on TestLockClassSelection and call self.get_lock, but that helper exists only on TestLock. These cases fail with AttributeError instead of exercising the fix, so the claimed coverage for issue #4279 never actually runs.
Reviewed by Cursor Bugbot for commit c55e6f3. Configure here.
|
Hey @Mukller, thank you for your contribution! Thanks for both the report in #4279 and this patch. We are going to move forward with #4282 for this fix, which you have already approved. It enforces the same invariant inside the scripts but covers more of it: One note for the record: the two tests added here were appended to Your Closing this one in favor of #4282. |


Fixes #4279
Root cause
A
Lock(timeout=0)acquires viaSET key token NXwithout PX — the key is persistent by design ("hold until released"). ButLUA_REACQUIRE_SCRIPTunconditionally ranredis.call('pexpire', KEYS[1], ARGV[2]), anddo_reacquire()passedtimeout = int(0 * 1000) == 0.PEXPIRE key 0deletes the key immediately, soreacquire()returnedTruewhile silently destroying the lock.(The
extend()path turned out to be already protected: its Lua script readsPTTLfirst and bails with0when it's negative, which surfaces as a loudLockNotOwnedErrorinstead of deleting anything — covered by an added regression test regardless.)Changes
LUA_REACQUIRE_SCRIPT: skip thePEXPIREwhen the requested TTL is"0"— reacquiring a persistent lock now only re-verifies ownership and keeps the key without expiry.Testing
Two regression tests mirroring the issue repro:
test_reacquire_lock_zero_timeout_keeps_lock_persistent— reacquire must keep the key alive and persistent (PTTL == -1), andrelease()must still fully remove it.test_extend_replace_ttl_on_zero_timeout_lock_raises_not_delete— pins the existing safe behavior ofextend(..., replace_ttl=True)against regressions.These live in
tests/test_lock.pyalongside the rest of the lock suite (they need the redis test service, same as the other cases there).Note
Medium Risk
Corrects distributed lock TTL handling so reacquire no longer deletes persistent locks; a small Lua change with high impact if wrong.
Overview
Fixes
reacquire()deleting persistenttimeout=0locks.LUA_REACQUIRE_SCRIPTused to always callPEXPIREwith the timeout, andPEXPIRE 0immediately removes the key while still returning success.Reacquire now skips expiry when the TTL argument is
"0", so ownership is re-checked and the key stays without TTL.Adds regression tests for that reacquire path and for
extend(..., replace_ttl=True)on a zero-timeout lock (already raises, must not delete). Note: those tests were added onTestLockClassSelection, which has noget_lockhelper (that lives onTestLock).Reviewed by Cursor Bugbot for commit c55e6f3. Bugbot is set up for automated code reviews on this repo. Configure here.