Repository navigation
fix(supervisor): make four flaky supervisor tests reliable - #866
Merged
Merged
Conversation
Registry ownership is an flock on supervisor.lock, and flock belongs to the open file description. A child process spawned by any other thread holds a duplicate of every descriptor between fork and exec (close-on-exec only closes it at exec), so closing the owner's descriptor left the root owned until that child exec'd. A successor opening the registry in that window was refused as Busy. Seen as intermittent Busy failures in tests that drop a supervisor and cold-open its registry while neighbouring tests spawn processes (provider_accepted_completion_retries_after_registry_success_on_fresh_healthy_startup, retained_output_owned_preparing_replacement_keeps_committed_prior, recovery_five_natural_successes_exhaust_durable_budget_until_manual_retry). OwnerLock now unlocks explicitly on drop, which releases the lock for every duplicate at once, and uses std's File::try_lock/unlock instead of raw libc flock calls. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ercises
provider_mountpoint_candidate_target_joins_owned_preparation_and_cleanup
ran on the wall clock with 250 ms preparation/cleanup budgets and a
50 ms caller cancellation. Under machine load those bounds expired at
arbitrary points: mid-request (the fixture's request read failed with
"early eof"), mid private exchange (UnexpectedEof in the fixture's
helper), or before success paths finished ("image lookup timed out"
in the prepared and empty modes).
The test now runs on a paused tokio clock held still by a blocking task
(tokio's documented way to stop auto-advance during real I/O), so no
budget can expire on its own. The fixture reports where it parks the
operation, and the test advances the clock past the first bound only at
that stall: the withheld private exchange after the helper's exit was
answered (timeout, cancel-private-exchange) or the withheld image lookup
(slow-image). If the code under test wedges, the hold ends after 60 real
seconds and the test's own bound fails it instead of letting it hang
(checked by removing the operation and image-lookup deadlines).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e it replies serve_control answered each connection in a detached task that kept its Supervisor clone, and so the registry's Arc and owner lock, until after it had written the reply. A client that read its answer and then handed the registry to a successor could be refused as Busy whenever that task's worker was descheduled between the write and the end of the task. provider_accepted_completion_retries_after_registry_success_on_fresh_healthy_startup does exactly that: it reads a Recover reply, drops its handles and cold-opens the registry. A 10-20 ms stall injected after the reply write made it fail with Busy at the reopen nearly every time. The per-connection work is now serve_control_connection, which drops its supervisor handle before it writes the reply, so the reply happens after the release. The regression test writes the reply through a stream that cold-opens the registry at the moment the reply goes out; it was refused as Busy before this change and succeeds after it, for both a valid and a malformed request. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
danshapiro
added a commit
that referenced
this pull request
Oct 23, 2026
Resolves the mountpoint test against #866's frozen test clock: main's version of the joined preparation test is kept, adapted to this branch's helper API (HelperDeadlines, AttemptStep, owned HelperContainer). The launch hand-off modes (a create Docker answers late, a cleanup inspection that outruns its window) move to their own real-time test, because the background reaper's removal is real socket I/O after the run returns; removing the hand-off, or handing off only an unanswered create, still fails it. A stand-alone helper's removal keeps exactly its cleanup window. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
danshapiro
added a commit
that referenced
this pull request
Oct 23, 2026
Brings in #864 (Windows/Linux tree-guard fixes and the shared process_probe test support), #866/#868/#869 (flaky-test fixes) and #867 (managed helper create retry) on top of core PR-C. No textual conflicts. Seven files changed on both sides and merged cleanly; each was checked by hand: - claude.rs: main's await_create_ownership_committed / await_create_adoption_committed helpers and their call sites land in PR-C's slimmed test module unchanged (12 uses, same as main). - claude_snapshot.rs: main's "never clear HOME" fix applies to the locate_transcript test; PR-C's genesis tests do not touch HOME. - claude/teardown_tests.rs, lib.rs: main's zombie-aware ProcessProbe module and the REST turn test-pause handshake sit beside PR-C's module reshuffle. - opencode_ws.rs, session_handoff/tests.rs: independent hunks. - fresh-agent-rollback-rust.spec.ts: main's wait for the codex undo refill before typing /redo applies inside PR-C's reworked spec. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Fixes four
freshell-supervisortests that failed now and then in pre-push hooks and CI, mostly when the machine was heavily loaded. No test was skipped and no assertion was loosened.The tests, their causes, and the fixes
1.
provider_accepted_completion_retries_after_registry_success_on_fresh_healthy_startupFailed with
Registry::open ... Busywhen the test reopened the registry. This test had two separate causes:flock. When another thread starts a child process, the child holds a copy of every open file until it runs exec. That copy kept the lock held after the registry was dropped, so a reopen right afterwards got Busy. In a scratch test with 4 threads starting/bin/truewhile the registry was opened and dropped in a loop, 1,814 of 3,000 reopens returned Busy before the fix and 0 of 3,000 after. Fix: the registry's lock now unlocks explicitly when it is dropped, which releases it for every copy at once (OwnerLockinregistry.rs, using std'sFile::try_lock/unlock, with a warning log if the release fails). New regression test:dropped_registry_releases_ownership_while_a_spawning_child_shares_its_lock.serve_controlanswered each connection in a separate task that held its own copy of the supervisor, and so of the registry, until after it wrote the reply. The test reads the reply, drops its handles and reopens the registry. If that task's thread was paused for 10–20 ms between writing the reply and finishing, the reopen got Busy. With a 100 ms pause injected at that point, the test failed 3 out of 3 times; with the fix and the same pause it passed 6 out of 6. Fix: the connection now lets go of the supervisor before it writes the reply (serve_control_connectioninservice.rs). New regression test:control_connection_releases_supervisor_before_it_repliesreopens the registry at the moment the reply is written. It got Busy before the fix and passes after it, for both a valid and a malformed request.2.
recovery_five_natural_successes_exhaust_durable_budget_until_manual_retryDid not fail in about 1,100 runs. It uses only the registry, so the only way it can get Busy is the child-process lock copy described above. The same registry fix covers it.
3.
retained_output_owned_preparing_replacement_keeps_committed_priorGot Busy 4 times in 96 whole-suite runs, from the same child-process lock copy. The same registry fix covers it.
4.
provider_mountpoint_candidate_target_joins_owned_preparation_and_cleanupFailed in 52 of 64 whole-suite runs under load ("early eof",
UnexpectedEof, "image lookup timed out", or losing track of its helper). Cause (test bug): the test used real 250 ms time limits and a 50 ms cancellation, so under load the limits ran out at random points instead of at the stall each case is meant to test. Fix: the test now runs on tokio's paused clock. The fake helper reports when it is holding back the exchange being tested, and only then does the test move the clock past the limit. If the code under test hangs, the test fails after 60 real seconds instead of hanging forever. Removing either of the product's time limits makes it fail that way, as it should. Addstokiotest-utilas a dev-dependency.Stress results after the fixes
cargo fmt --checkandclippy -D warningsare clean, and the pre-push hook passed.Not fixed here
These tests still fail sometimes under heavy load. They fail the same way on
mainand are outside this change:provider_claude_nested_source_reply_requires_live_scope_after_private_ipc("must not reacquire the held soul lifecycle lock: Elapsed")provider_claude_source_helper_uses_private_bound_ipc_without_creating_stable_homenative_auth_uses_private_ipc_and_cleans_only_verified_owned_helper("helper must actually run")🤖 Generated with Claude Code