test: remove watcher lock timing flake - #2876
Open
karotkriss wants to merge 1 commit into
Open
Conversation
…artup tests/fm-watcher-lock.test.sh passed in isolation but failed intermittently under full-suite and ambient concurrent load. bin/fm-watch-arm.sh computes its confirmation deadline immediately after forking the real child watcher, so the child's entire fork, exec, lock acquisition and beacon publication has to land inside that wall clock. Two cases shrank that budget to one second, leaving a two-second window for work measured at 3.1-4.9s under CPU oversubscription, so the arm honestly reported "FAILED - no live watcher with a fresh beacon" and their premises collapsed. A third case ran on the production budget, but its child must also execute a registered check before exiting: measured at 1.9-2.3s idle and 9.1-13.1s under load, against an 11s budget. The two cases that must confirm a real child now hold the arm to production's own budget instead of a shrunken fixture one, the immediate-wake case gets an explicit budget with headroom over its measured loaded cost, and the two waits for the arm's typed failure are sized off the largest production default rather than a fixed eight seconds. No bin/ change and no default behavior change: the lock's fail-closed semantics, SIGSTOP handling, stale-heartbeat detection and the arm's typed failures are untouched. Verified 4/4 green at 3x CPU oversubscription (loadavg 75-80) after 3/3 red before the change, and CONTRIBUTING.md records the convention.
Confidence Score: 5/5The PR appears safe to merge; no concrete changed-code defect or security issue was identified. The test-only timing changes preserve watcher fail-closed behavior, return early on successful confirmation, and provide bounded waits that cover the platform-specific production deadlines. Reviews (1): Last reviewed commit: "test(watcher): stop fixture confirmation..." | Re-trigger Greptile |
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.
Intent
Fix the load-sensitive flake in tests/fm-watcher-lock.test.sh (GitHub issue #2844): the suite passes in isolation every time but fails intermittently under full-suite / ambient concurrent load, which taxes every full-suite run and blocks widening test parallelism.
Required approach (investigation first): reproduce under instrumented co-scheduled load and name the exact tripping assertion, then remove the timing sensitivity - event/condition waits over fixed sleeps, or tolerances derived from the component's own configured grace - WITHOUT loosening the watcher lock's fail-closed semantics (SIGSTOP handling and stale-heartbeat detection are safety code). The issue's fallback of quarantining the test from parallel lanes is acceptable only if the sensitivity is irreducible; the PR must state which was chosen and why.
Root cause established by repro (3/3 failures under 2x CPU oversubscription, identical assertion "arm did not start before self-eviction check"): two fixtures set FM_ARM_CONFIRM_TIMEOUT=1, and bin/fm-watch-arm.sh computes its confirmation deadline immediately AFTER forking the real child watcher, so a real fork + exec + lock acquisition + beacon publication had a 2-second wall-clock budget. Measured real startup under that load is 3.1-3.6s, so the arm correctly reported "FAILED - no live watcher with a fresh beacon" and killed the child, destroying the fixture's premise. The production code behaved honestly; the fixture was asking a real process startup to beat a one-second clock.
Re-running the fixed suite at 3x oversubscription surfaced a second case on the same mechanism:
test_arm_propagates_immediate_wake_before_confirmationreturned non-zero after printing the wake it already held. That child must also execute a registered check before exiting, and it runs on the production budget - measured at 1.9-2.3s idle but 9.1-13.1s under load, against 11s. It was a coin flip, not a fixed cost.Fix (test-only, no production change, no default-behavior change):
The suite's own waits stay iteration-counted poll loops, which stretch under load where a wall-clock budget does not. CONTRIBUTING.md records the convention so the pattern does not come back.
Verification: 3/3 red before the change at 2x oversubscription, then 4/4 green at 3x (loadavg 75-80) with the whole suite completing, plus a clean unloaded run. The remaining fixed sleeps in the 40-way lock-contention cases were probed to 6x oversubscription without tripping, so they are left alone rather than rewritten speculatively.
Quarantine was rejected on evidence, not preference: bin/fm-test-isolation-proof.sh already excludes the watcher/wake/lock family, and bin/fm-test-run.sh already refuses --jobs > 1 for this script, yet the reporter's failure happened at --jobs 1 under ambient machine load. Quarantine was already in force and could not have prevented it.
VISION.md alignment: "This repository ships through its own discipline: firstmate work is validated like any other project's, and field incidents become regression coverage" - a flaky red trains contributors to ignore red. "Scripts stop safely and report when the world surprises them" - the arm's refusal was correct, so the test was corrected rather than the safety code weakened. Deliberately no default-behavior change anywhere in bin/.
What Changed
Fixes #2844
Risk Assessment
✅ Low: the test-only change directly addresses the reproduced timing race while preserving production watcher safety behavior and platform-specific confirmation defaults.
Testing
The real-process watcher-lock suite passed normally in 80 seconds and under 2x CPU oversubscription in 225 seconds, including the formerly failing self-eviction startup assertion, real-child peer handoff, immediate wake propagation, typed arm failure, stale-heartbeat detection, and SIGSTOP fail-closed behavior. Reviewer-visible CLI transcripts were captured, production scripts were unchanged, and no transient processes or worktree artifacts remained.
Evidence: Focused watcher-lock test transcript
Source: Focused watcher-lock test transcript
Evidence: Watcher-lock test under 2x CPU oversubscription
Source: Watcher-lock test under 2x CPU oversubscription
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
✅ **Review** - passed
✅ No issues found.
✅ **Test** - passed
✅ No issues found.
bin/fm-test-run.sh tests/fm-watcher-lock.test.shStarted 40yesCPU workers across 20 logical CPUs, then ranbin/fm-test-run.sh tests/fm-watcher-lock.test.shVerifiedgit diff --exit-code -- binand confirmed no production script changesVerified all 40 load workers exited and the worktree remained clean✅ **Document** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.