fix(watch): claim the pidfile before displacing the previous holder (#595) - #803
Merged
Merged
Conversation
…ch event came from
Merged
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.
Declared reviewers: 1
Refs #595, #758, #797.
Landing on
integration/remote. Head3041cefa10fd37338c8a690a6073d21be2377483.What was wrong
scripts/watch.shsignalled the previous holder of a pidfile before claiming the slot:The guard at 333 exists so a departing watcher does not erase its successor's record. It is sound only if the successor's write cannot land between the read at 323 and the remove at 333 — and signalling first is precisely what schedules it there. The successor never writes again, so the slot stays empty for the rest of that watcher's life.
The fix is an order, not a check
Decide who to displace where that is already decided, and send the signal after the claim. The predecessor's own read then cannot see its own pid, so the guard's condition is false and it removes nothing.
The guard's comment already described this order as the one in force — "a successor watcher overwrites
$PIDFILEwith its own pid before killing us". The code did the opposite. That comment is why the ordering was never questioned, so it is corrected here rather than left to mislead.Tightening the comparison would not close it: read-check-remove is three steps whatever the comparison is. The order removes the interleaving; a better check only narrows it.
The control records the order that happened; it does not impose one
Three versions of this control were wrong before this one, and the first two were wrong in the same way.
Under either implementation the events happen in whatever order they happen. The fix's whole content is which order that is, and a recorded order cannot be lost to load.
Each event carries the pid that wrote it. The first version of this recorded only the words — and both watchers claim the slot, the predecessor when it starts, so
grep -n '^claim' | head -1found the predecessor's claim and the assertion passed on the broken code. The mutation found that; the assertions bind toclaim <successor>,signal <successor>,read <predecessor>now, and a missing term is an explicit failure rather than a vacuous pass.The test then waits for the predecessor to be gone — a condition, not a duration — because its remove is the last thing it does.
Mutations
kill "$prev_pid"restored inside the takeover blockrefutereddensclaim < signalreddensThe first was green against the first version of the ordering test, whose
refutewas anchored to the start of a line while the restoredkillsits after acasepattern. Unanchored now, with the reason beside it.What this does not claim
That the CI failures on
ubuntu-latest 4/4were this race. The evidence is strong and circumstantial: at the timeout the run directory holds.watch-start.<sid>.<successor-pid>(written at 389) and the filter file (251), and not the pidfile (253) written between them, while the successor is alive and still the same process. The filter file is the natural control — same writer, one line apart, removed by an owner recorded inside the file (#766) rather than by read-check-remove. Only the deletable-by-a-departing-predecessor one was deleted.Nobody observed the removal itself, and the write at 253 has no error handling. See #595.
Two other explanations were tested and dropped: the poll window (widened 3s → 10s in #797, still red) and load (still red on an otherwise idle CI queue).
Measurements, at this head
The CI green is the weaker half. That assertion fails intermittently, so one green is a coin landing the right way up; the strong evidence is the mutation table above, where reverting the fix reddens a control that does not depend on timing. An earlier head was also green while carrying the false-green control described above — a reason not to count greens.
Drift
Measured empty rather than structural: the destination advanced and did not touch this surface. Re-measured immediately before landing.
Not in this PR
#797, the instrument that produced the diagnosis, stays open and is not landed on the strength of this.