Repository navigation
Fix update-lock flake: unlock explicitly on drop - #1643
Mikola Lysenko (mikolalysenko) wants to merge 2 commits into
Conversation
A flock belongs to the open file description. When any thread forks while an UpdateLock is held, the child holds a copy of the lock fd until its exec closes it (O_CLOEXEC), so dropping the guard by closing our fd alone can leave the lock held for that window. A re-acquire in the window sees InProgress. This evicted #1591 from the merge queue: the coverage job's update_lock_is_exclusive_and_released_on_drop panicked on the re-acquire after drop(first), while other core tests spawned processes in parallel. Drop now calls flock(LOCK_UN), which releases the lock for every copy of the description. Add a regression test that drops and re-acquires the lock 1000 times while two threads spawn processes: without the fix it reports the lock still held 150-600 times per run. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014UCnqJcb1P5Mid6Bf13dJJ
|
bugbot run Generated by Claude Code |
|
Labeled Ready for review (burn-down agent).
Generated by Claude Code |
From /code-review on this PR: - The new spawn-stress test now stops its spawner threads and restores SOCKET_UPDATE_STATE_DIR from a Drop guard, so a panic can't leave fork traffic or a dangling state dir behind for later tests. - It waits until a process has actually been spawned before measuring, so it can't pass vacuously on a host where spawning fails. - concurrent_update_lock_held released its externally held update.lock by close alone, the same fork-window race this PR fixes in UpdateLock; unlock explicitly before the drop. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014UCnqJcb1P5Mid6Bf13dJJ
|
bugbot run Generated by Claude Code |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 1028c9e. Configure here.
|
Ready for review re-confirmed at the new head (burn-down agent).
Generated by Claude Code |
Final review brief (final reviewer agent)What it does. Risk: low. Two files, about 15 lines of production code: a best-effort unlock right before the close that already released the lock. No API change. The unlock uses fs2's Look here
Verified
Changes I made: none. Open questions: none blocking. As the PR description says, Auto-merge is armed, so approving sends this straight to the merge queue. Generated by Claude Code |
Problem
update::swap::tests::update_lock_is_exclusive_and_released_on_dropflakes. On 2026-10-20 at 18:13 UTC it evicted #1591 from the merge queue: thecoveragejob panicked atcrates/socket-patch-core/src/update/swap.rs:467, theunwrap()on the re-acquire afterdrop(first), which returnedErr(InProgress). The other 6547 core tests passed. The same code passedcoveragein #1617's queue run (39471359983) and on #1591's own CI, so the failure has nothing to do with #1591's diff. Each eviction like this costs a full queue rebuild for every entry behind it.Root cause
A
flockbelongs to the open file description, not to the fd. While any other thread forks (core tests spawngit, package managers and so on in parallel), the child holds a copy of the lock fd until itsexeccloses it (O_CLOEXEC).UpdateLockreleased the lock only by closing its own fd, so if a child was inside that fork-to-exec window when the guard dropped, the lock stayed held and the next acquire sawInProgress. Coverage instrumentation makes that window longer, so thecoveragejob hits it most.It's a real bug in the guard as well, not just the test. In production the window is microseconds and nothing re-acquires inside it, though.
Fix
impl Drop for UpdateLockcallsFileExt::unlock(flock(LOCK_UN)) before the fd closes.LOCK_UNon any copy of the description releases the lock for all copies, so a child that is still forking can no longer keep the lock. On Windows it's the matchingUnlockFile. It's best effort: the close that follows still releases the lock if the unlock fails.New regression test
update_lock_released_on_drop_while_other_threads_spawn(unix only, in the sameupdate_state_dir_envserial group): two threads spawntruein a loop while the test acquires and drops the lock 1000 times.Proof
update_lockgroup passed 30 of 30 loops, andupdate::(88 tests) passes.cargo clippy -p socket-patch-core --all-features -- -D warnings(the CI invocation) is clean.rustfmtran on the touched file only.Where tests run
No test was removed or moved. The new test runs wherever core lib tests already run (test and coverage jobs).
/code-review high
Fixed in 1028c9e:
Not fixed here:
Generated by Claude Code
Note
Medium Risk
Changes self-update concurrency locking behavior on Unix; the fix is narrowly scoped to release semantics and is covered by new stress and e2e tests.
Overview
Fixes a flock release bug where dropping
UpdateLockonly closed the fd, so a concurrently forked child could keep the lock until exec and the next acquire would seeupdate_in_progress.UpdateLocknow callsFileExt::unlockinDropbefore the fd closes, soLOCK_UNreleases the advisory lock for all descriptor copies. The e2econcurrent_update_lock_heldtest unlocks its externally heldupdate.lockthe same way before retrying an update.Adds a Unix regression test that spawns processes from background threads while acquiring and dropping the lock 1000 times, with cleanup on panic so parallel tests are not polluted.
Reviewed by Cursor Bugbot for commit 1028c9e. Configure here.
Generated by Claude Code