Repository navigation
🐛 fix(soft): clean up a marker whose identity read failed - #869
Merged
gaborbernat merged 2 commits intoOct 28, 2026
Merged
Conversation
A SoftFileLock acquisition creates its marker with O_EXCL and then reads the new file's identity, which the rollback needs to recognize its own marker while cleaning up. When that read failed transiently - the EIO a network or FUSE mount reports, the same one UnixFileLock already tolerates - the rollback had no identity to work with. On Windows the marker cleanup after the close is identity-checked, so it did nothing and the empty marker stayed on the path while no instance held the lock. Nothing ever reclaims one: it is not the strict sentinel, it is empty so its owner cannot be probed, and an unparsable lock is only self-healed once `_marker_seen` has watched it unchanged for MALFORMED_LOCK_AGE_THRESHOLD - a state the acquiring instance never reaches, and each new contender starts the watch over. The result is a lock file that blocks the path for good: every later acquisition times out reporting "an owner that recorded no PID", which points at a holder that does not exist. On Unix the same failure left the marker for the 2-second malformed-marker grace instead of releasing the path immediately. The rollback now retries the read while it still owns the descriptor - the capture is `os.fstat` on a descriptor this attempt opened itself, so a second attempt normally answers - and passes the result to both cleanup paths. `remove_own_file` accepts an already-read `os.stat_result`, so Unix no longer repeats a read that just failed. Tests: `test_transient_identity_failure_still_cleans_the_marker` covers both the identity read and the underlying `os.fstat`, asserting the failed acquisition leaves the path free for the next contender. It fails on the base source (marker still on disk) and passes with the fix. Signed-off-by: simpleqt <2015900818@qq.com>
The rollback already owns the descriptor it created the marker through, so reading the identity again there is enough to recognize the marker. That replaces the saved stat result, the retry helper and the optional identity argument on remove_own_file, which refetches the stat itself on Unix. The test patches only os.fstat, the boundary that fails, and delegates every later call to the real function, so pytest's capture keeps working under the patch. The identity helper is a tuple read that cannot fail on its own, so the test drops that case, along with the contender acquisition that checked a second behavior after the assertion.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A
SoftFileLockacquisition that failed while reading its new marker's identity left an empty marker on the path. On Windows nothing reclaims such a marker: a contender never breaks an empty one, so every later acquisition on that path timed out, with the defaulttimeout=-1forever. The read fails when a network or FUSE mount reports a transientEIO, or when an interrupt lands during the call.The rollback now reads the identity again through the descriptor it still owns before it closes it. This attempt created the marker with
O_EXCLand never published it, so the retry describes the file this process made; the unlink still checks that identity against the path, so a successor's marker is never removed. A read that fails twice keeps the previous behavior and leaves the marker to stale handling.