Repository navigation
test(runtime): isolate fork-exposed host checks and make scratch names unique - #399
Merged
Merged
Conversation
…s unique HACK-1347: in the shared parallel test process, an unrelated test's pre-exec fork (process::clean_command) holds a copy of every open descriptor until it execs. Endpoint discovery then sees the fork as a second holder of the test's listener (or finds a listener the test already dropped), and the lsof release checks see disk handles the fixture had just written, so they correctly refuse before the result under test. Run every such fixture alone in a child test process with the shared run_isolated helper: - host_endpoint: exclusive and executable-pinned discovery, plus the two close/rebind fixtures that still shared the process; the module's own runner now delegates to run_isolated (120 s hang guard, not 5 s). - admission_pool: every release-check test that expects a pass or a non-active refusal (7 more), behind one isolated() helper that keeps serial scans. - short_home, host_filesystem, interrupted: recovery, migration and stop tests that need the lsof proof to pass. HACK-1348: fixtures named scratch directories from the PID and the wall clock and created them exclusively. macOS reports the clock in whole microseconds, so parallel tests can read the same value and the second create fails with "File exists (os error 17)". Add crate::next_test_scratch() and append it to every such name in the lib tests, with a regression test. No product code changes. Refs HACK-1347 Refs HACK-1348 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
HACK-1347 audit, second part: more runtime-core tests drop a listener (or a publication that owns one) in the shared parallel test process and then require a release check to pass. An unrelated test's pre-exec fork (process::clean_command) holds a copy of every open descriptor until it execs, so the dropped listener still accepts and its flock stays held; the check correctly refuses and the test fails. Under in-process fork pressure with a 20 ms pre-exec hold the full all-features lib suite failed 3/3 runs on 9 to 13 such tests. - Make provider::test_executable available on every Unix target (the quiescent-recovery tests also run on Linux) and isolate, with run_isolated: dependency_slots socket recovery (2), quiescent_recovery dropped-listener tests (2), foreground::transport abandoned-publisher tests that expect retirement to pass (7), acknowledged_publisher::missing_lock (5, all use the dead-publisher fixture), native foreground abandoned publication (1) and the https_recovery full commands (2, their fixture drops the provider flock that recovery must then acquire). - Fixtures that only need a stale socket inode now bind a socket that never listens (new provider::test_socket::bound_unlistened, shared with the quiescent-recovery fixture) instead of dropping a listener: https_recovery and dependency_slots::fixture_prior_boot. Regression test included. - relay_exec framing test: shut the peer down instead of only dropping it, so the truncated frame always reaches end of stream. No product code changes. Refs HACK-1347 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
provider::publisher::tests::Fixture picked a port by binding 127.0.0.1:0,
reading the port and dropping the probe, then started stream-relay-host on
that explicit port. In the shared parallel test process an unrelated test's
pre-exec fork (process::clean_command) can still hold the probe listener, or
another test can take the port, and the publisher exits 73 without
readiness ("publisher readiness missing", 2 of 5 full-suite runs under fork
pressure with a 20 ms pre-exec hold).
Start the publisher with --publish 0 (from #382): it binds a kernel-chosen
port and reports it as "ready <port>", which the fixture now requires.
Refs HACK-1347
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 22, 2026
roodboi
added a commit
that referenced
this pull request
Oct 24, 2026
…ts (#461) ## Root cause runtime-core test fixtures named their temporary roots `<prefix>-<pid>-<clock>` (or `<prefix>-<pid>` alone). On macOS `SystemTime` has microsecond resolution and `crate::node::now()` counts milliseconds, so two tests that `cargo test` starts together on parallel threads can compute the same name. Where the fixture then creates the root non- exclusively (`state::private_directory` is recursive), the second test silently shares the first one's root. In `provider/hostname_authority/managed.rs` this gives the hosted `Candidate core (macos-latest)` failure (run 39968507108): one test finds the other's provider lock held (`provider_busy` at `:111`), and the first fixture's `Drop` (`remove_dir_all`) deletes the root under the second (`NotFound` at `:146`, or the deadline assertion at `:142`). Where the create is exclusive, the same collision fails with `File exists` instead. ## Fix (test-only; no product code path changes) - New `#[cfg(test)]` module `packages/runtime-core/src/test_root.rs`, re-exported as `crate::unique_test_root(parent, prefix)` (mode 0777 less umask, the same as `fs::create_dir`) and `crate::private_test_root(parent, prefix)` (mode 0700). Each root is named `<prefix>-<pid>-<sequence>`. The sequence is a process-wide `AtomicU64`. The root is made with one exclusive, non-recursive `mkdir`. A name left by an earlier process with the same pid is skipped and never reused. Failures panic with a single value-free line (`E_TEST_ROOT_CREATE` / `E_TEST_ROOT_EXHAUSTED`). - Lib fixtures that named roots from the pid alone or from pid plus clock now use the helper. Each fixture keeps its own parent (canonicalized or not), permissions, follow-up `private_directory` calls and cleanup: `node/journal.rs` (2), `provider/bridge.rs`, `provider/config_audit.rs`, `provider/graph/normalized.rs`, `provider/graph/probes.rs` (2), `provider/graph/relay.rs`, `provider/image_load.rs` (2), `provider/network_tools.rs`, `provider/publication/recovery.rs`, `provider/source_sync.rs` (2), `provider/graph/host_relay/live_tests.rs` (the control root, through a small `ControlRoot::claim`). - **Overlap with #399 (G40, HACK-1347).** #399 also changes some of these fixtures. It appends `crate::next_test_scratch()` to the name and adds `src/test_scratch.rs`. For those fixtures this PR makes the byte-identical edit (same hunks, same `test_scratch.rs`, same `lib.rs` lines) instead of moving them to the new helper: `hostname_authority/managed.rs` (the failing test), `project/snapshot.rs`, `config_audit.rs` (recorded fixture), `dependency_socket.rs`, `disk_audit.rs`, `disk_template.rs`, `engine.rs`, `guest_tool.rs`, `lifecycle.rs` (5), `lifecycle/interrupted.rs`, `lifecycle/prepared_boot/tests.rs`, `lifecycle/pressure_tests.rs`, `persistent_data/enrollment/witnessed/tests.rs`, `relay_loop/admission_tests.rs` and `source_transfer.rs`. `git merge-tree` of this head with #399's head (`cb6661fd`) is clean, so the two PRs merge in either order. - Fixtures the issue lists that already had a process-wide counter are unchanged: `graph_cli/restore_tests.rs`, `project/adapter.rs`, `project/live_source.rs`, `project/registry.rs`, `project/snapshot/generated.rs` and `project/snapshot/mountpoints.rs`. ## Tests - `test_root::tests::parallel_fixtures_receive_distinct_freshly_created_roots`: 32 threads behind one barrier each create 8 roots (mixed private and plain). The test asserts that all 256 are distinct, each is empty when returned, each holds only its own marker afterwards, and the private ones have no group or other bits. - `test_root::tests::a_root_left_by_an_earlier_process_is_skipped_and_never_reused`: when the first three sequence names already exist, the fourth is returned, it is empty, and the stale roots are left untouched. - Mutation proof: - Removing the counter (every name repeats) fails the parallel test: other threads exhaust their attempts on the one existing name with `E_TEST_ROOT_EXHAUSTED`. - Making `AlreadyExists` return the existing path (reuse) fails the stale test: it gets `fixture-0` where `fixture-3` was expected. ## Evidence - Repro (macOS, M-series, `--test-threads=2 hostname_authority::managed`, 200 runs each): - `origin/next` lib test binary: 30/200 and 41/200 failing runs, with the CI signatures (`provider_busy` at `managed.rs:111`; `:142` / `:146`). - This branch: 0/200. - Gates, all with `cargo +1.97.1`: - `bun run check:local` (fmt and clippy, all targets, `-D warnings`): pass. - All-feature clippy (`--all-targets --all-features -D warnings`): pass. - `bun run test:local`: lib 1050 passed / 60 ignored; bin 43 passed; all integration test binaries pass. - Root `bun run check` (includes privacy-check) and `bun run --cwd packages/cli typecheck`: pass. - gitleaks 8.30.1 over `origin/next..HEAD`: no leaks. - Hosted CI at head `a87aee14`: every check passes, including `Candidate core (macos-latest)`, `Candidate core (ubuntu-latest)`, `test`, all four `docker-e2e` shards and all four `Config compiler` jobs. ## Gaps - The integration tests in `packages/runtime-core/tests/` are separate crates and cannot use the `#[cfg(test)]` lib helper. Their remaining pid-only or pid-plus-clock names (`stream_relay_contract.rs`, `relay_fence_contract.rs`, `quiescent_dependency_recovery_cli_contract.rs`, `source_device_rebind_cli_contract.rs`, `absent_recovery_cli_contract.rs`, `loopback_descriptor_churn.rs`, `cache_quarantine_cli_contract.rs`) are each used by exactly one test, so they cannot collide inside one process. The only remaining risk is a stale name from an earlier process, which fails loudly. - The `src/main.rs` unit tests (`source_capture_tests.rs`) are also outside the lib crate. They use two distinct prefixes and an exclusive create. - Manual `#[ignore]` VM tests that build owner tokens from pid plus clock (`source_transfer.rs`, `graph_probe_test.rs`, `source_failure_test.rs`, `gateway_probe_test.rs`) are unchanged. - **Pre-existing, unrelated flake.** Found while running the gates: `provider::relay_owner::publ ication::tests::dead_publication_recovery_requires_unchanged_receipt_and_socket` fails intermittently with `NotFound` at `publication/tests.rs:299` (2/30 runs on `origin/next`, 1/30 on this branch). Under full-suite load its two `dead_witness_*` siblings once failed with `relay_owner_refused` at `:384` / `:432`. Its roots already use a counter, so this PR does not address it; it is reported separately. Refs HACK-1393 🤖 Generated with [Claude Code](https://claude.com/claude-code)
roodboi
marked this pull request as ready for review
October 24, 2026 05:47
roodboi
added a commit
that referenced
this pull request
Oct 25, 2026
…reap failed relay starts (#508) ## Summary Fix unit G112 for HACK-1342, items 1, 2 and 4. Test-harness only: one file changed (`packages/runtime-core/tests/stream_relay_contract.rs`). The relay (`guest/stream-relay.c`) is not changed, because the evidence shows it behaves correctly. **Item 3 (the 2 s deadlines in `provider/graph/native/runtime/tests.rs`) is left to #419 (HACK-1367).** This PR does not touch `runtime/tests.rs`, `exec_tests.rs`, `run_tests.rs` or `test_budget.rs`. | Item | Root cause | Change | |---|---|---| | 2. `Relay::connect` EINVAL (CI run 40119166731, `stream_relay_contract.rs:108:14`) | The relay refuses an excess connection by accepting it and calling `close()` (`stream-relay.c:489`). For an unavailable target it calls `release()` after `connect_target` fails. Both can happen before the client's next syscall. Once the peer has closed a stream socket, macOS (XNU `sosetopt`) rejects `SO_RCVTIMEO`/`SO_SNDTIMEO` with EINVAL, because both directions are shut. `Relay::connect` unwraps `set_read_timeout` (line 105) and `set_write_timeout` (line 108), so it panics when the refusal arrives first. The hypothesis is **confirmed**. The relay is correct. | The two refusal tests now call `Relay::connect_refused()`. It sets no socket timeouts. It waits up to `REFUSAL_DEADLINE` (10 s, as before) with `poll` and then still requires EOF or a reset (`assert_refused`). A peer that stays open fails the check (timeout), and so does a peer that sends data. `Relay::connect` still unwraps the timeout calls for streams the relay must keep, so EINVAL there is still a real failure. | | 4. Orphaned `stream-relay-host` on a readiness panic | `start_options` spawned the child and ran its readiness assertions before any `Relay` existed, so a panic dropped the `Child` without killing it. | The readiness checks moved into `await_bare_ready`. Until readiness passes, the existing `PublisherStartup` guard owns the child. Its `Drop` kills and reaps the child, and removes a fresh scratch root (never a root the caller passed in). | | 1. `provider/publisher.rs` probe-port race | Already fixed on `next` by #399 (674a0a4): the fixture now runs `--publish 0` and reads `ready <port>`. No probe listener is left. | No change. It was re-verified under load (below). | ## Evidence ### Root cause (macOS 26.5.1, Darwin 25.5.0) A standalone Rust repro (not committed): - socketpair, peer still open: `set_read_timeout` = `Ok(())` - socketpair, peer dropped: `set_read_timeout` and `set_write_timeout` = `Os { code: 22, InvalidInput }`; `read` = `Ok(0)` - Unix listener, server accepts and then closes, as the relay does for an excess connection: **200/200** `set_write_timeout` calls return EINVAL - peer shut down only its write side: `set_read_timeout` = `Ok(())`. The error needs both directions to be shut, which matches XNU's check. ### Parallel load on this macOS host (`cargo +1.97.1`, Zig 0.15.2 via mise, 8 concurrent processes × `--test-threads 16`, host load average ~105–116 on 18 cores) | Run | Before (origin/next) | After | |---|---|---| | Full `stream_relay_contract` binary | 104/104 pass | 104/104 pass | | Focused (`connection_limit`, `occupied_socket`) | **1994/2000 pass**: 6 failures, all `connection_limit_refuses_excess...` with `Os { code: 22 }` at the `set_read_timeout` unwrap (`:105:14`) | **2000/2000 pass** | | Item 1: `provider::publisher` lib tests (`--features native-stream-relay`) | n/a (already fixed on next) | 104/104 pass | The before failures appeared at line 105 locally and at line 108 on CI. Both are the same race; it depends on which `setsockopt` the relay's `close()` lands before. ### New tests, each proven by mutation - `refusal_check_accepts_a_peer_that_closed_before_the_check`: drops the peer before the check. Mutation: restore the old pattern (set timeouts, then read) in `assert_refused_within`. Result: it fails deterministically with EINVAL. - `refusal_check_rejects_a_live_peer` and `refusal_check_rejects_a_peer_that_sends_data` (`should_panic`). Mutation: make the check return early (accept anything). Result: both fail. The new check does not weaken the refusal proof. - `failed_relay_readiness_kills_and_reaps_the_child`: a live child prints the wrong readiness line. The test then requires `waitpid(pid, WNOHANG)` = ECHILD (already killed and reaped) and the fresh root removed. Mutation: take the child out of the guard before the checks (the old ownership). Result: it fails with `E_RELAY_STARTUP_ORPHAN`, and the test kills the child itself. ### Gates - `cargo +1.97.1 fmt --check`; clippy `--all-targets -D warnings` with default features (`check:local`) and with `--all-features`: clean - `test:local` (`cargo +1.97.1 test --jobs 2`): all pass (lib 1065 passed, 101 ignored) - `stream_relay_contract` with `native-stream-relay`: 29/29 - `bun run check` (includes `privacy:check`) and `bun run --cwd packages/cli typecheck`: 0 errors - gitleaks 8.30.1 over `origin/next..HEAD` (`gitleaks dir` on a `git log -p` export): no leaks - Hosted CI at head 8cfc2a0 (run 40145181695): all 20 checks green, including `test` shards 1-4 and `Candidate core (macos-latest)` (optional feature contracts). This is the full-suite evidence. ## Gaps - Load proof ran on this macOS host, not the M3. The M3 is reserved; hosted CI provides the full suite. - The EINVAL behavior comes from XNU's `sosetopt` check. Linux does not show it, and the new helper does not depend on it either way. - Item 3 is left to #419. ### Round 2 (5752a32): stale scratch roots The verifier hit one unrelated failure on `next` under 32-process load: `fs::create_dir` at line 52 returned EEXIST. Roots are named `/tmp/<prefix>-<pid>-<n>`, and a directory left by an earlier run whose PID is now reused collides. The new `fresh_root(prefix)` skips an existing name and panics on any other error. Every root site uses it, including the fixed-name `hkr- netns`, `hkr-reservation`, `hkp-ack` and `hkp-private` roots. - New test `fresh_root_skips_a_stale_directory_with_the_next_name`. A mutation that stops skipping makes it fail. - `stream_relay_contract`: 30/30 pass. 32 full-binary runs (4 rounds of 8 processes, `--test- threads 16`, load 92-154) all pass. fmt and `clippy --all-targets --all-features -D warnings` are clean. gitleaks found no leaks. - Not changed: the occupied-socket test still takes its "unavailable" port by binding and dropping a listener, so a parallel process could in theory take that port. I tried holding a bound socket that never listens. On macOS 26 a connect to such a port times out (ETIMEDOUT after about 7.8 s, with `net.inet.tcp.blackhole` 0) and is not refused, so that would turn the refusal into a timeout. The theoretical race is tracked separately. Refs HACK-1342 🤖 Generated with [Claude Code](https://claude.com/claude-code)
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.
Summary
Fix unit G40: two runtime-core test reliability defects, following #387. Test-only; no product code changes.
Rework after verification: the verifier found that part of the HACK-1347 audit was still open. Several tests outside the first commit's modules still checked a dropped listener in the shared process, and the full lib suite failed 0/5 under the brief's fork-pressure condition. The rework isolates or de-exposes every such test found (see the audit), and merges
origin/next(b90b67e) for #382's--publish 0.process::clean_commandsets apre_execumask, so std uses fork+exec, notposix_spawn) holds a copy of every open descriptor until it execs.HostEndpoint::discover(host_endpoint.rs:202-247) scans every same-user process running the selected executable; the test binary is both the scanner and the selected executable, so the fork is a second exclusive holder of the test's listener (tests.rs:78, ambiguity refusal, the CI signature) or still holds a listener the test already dropped (tests.rs:84). Thelsofrelease checks (lock_absent_disks_within) see the fork's copy of a disk handle the fixture just wrote or read, and socket probes see a dropped listener still accepting, so they correctly refuse before the result under test.test_executable::run_isolatedhelper from #387 (new sharedISOLATED_HANG_GUARD, 120 s). See the audit below.<prefix>-<pid>-<nanos>and created them exclusively. macOS reports the wall clock in whole microseconds (nanos always end in000), so two parallel tests in one process can read the same value and the second create fails withFile exists (os error 17)(witnessed/tests.rs:22).crate::next_test_scratch()(process-unique sequence) appended to every pid+timestamp scratch name in the lib tests: the witnessed fixture and 18 sibling sites in 14 other files. The 4 integration-test files with the same pattern each have one test and one call, so they cannot collide in-process; they are unchanged. Regression testtest_scratch::tests::parallel_fixtures_reading_one_clock_tick_create_distinct_directories.HACK-1347 audit: what runs isolated now
Rule: a test that runs a release or liveness check in the shared process (endpoint discovery, the
lsofdisk-handle check, a dropped listener that must refuse connections, anflockthe fixture dropped and the check must acquire), and expects anything other than the fail-closed refusal, runs isolated, or its fixture stops depending on a dropped descriptor.First commit (1c14276):
host_endpoint:discovery_requires_the_exact_executable_and_live_exclusive_listener(the reported test),executable_pinned_discovery_follows_a_new_listener_process, and the two close/rebind fixtures that still shared the process (replacement_between_precheck_and_connect_receives_no_payload,backend_fin_does_not_bypass_listener_replacement). The module's own runner now delegates torun_isolated(its old 5 s watchdog for launching the whole binary becomes the 120 s hang guard; it now also requires the exact child's... okline).admission_pool: every release-check test that expects a pass or a non-active refusal (7 more, behind oneisolated()helper that keeps holdingserialso the module's whole-host scans stay one at a time). This closes the gap test(runtime): make four hosted-macOS candidate-core tests deterministic #387 left open.short_home(4),host_filesystem(7, every test whoseinspectmust pass first),interrupted(1): recovery, migration and stop tests that need thelsofproof to pass.Rework (f6bc8ed, cb6661f), the rest of the brief's audit. The verifier's run found these still exposed; a full all-features lib run under fork pressure (below) found the rest:
test_executableis now compiled on every Unix target (cfg(all(test, unix))), becausequiescent_recoveryalso runs on Linux.sleeping_executablestays macOS-only in use.run_isolated(each module has oneisolated()helper with the reason; each child is#[ignore]d with its parent's name):graph::dependency_slots:recovery_refuses_live_and_replaced_sockets_without_touching_siblings,recovery_preflights_entire_assignment_before_unlinking(a dropped listener must refuse).dependency_socket::quiescent_recovery:first_socket_refusal_records_closed_live_listener_facts_without_removal,live_listener_and_foreign_replacement_are_never_removed.graph::foreground::transport: the 7abandoned_publishertests that expect retirement or the dead-owner guard to pass (recovered_publisher_retirement...,cleanup_fence...,retired_recovery_refuses_pending...,retired_recovery_rechecks...,interrupted_socket_move...,selected_legacy_device_socket_move...,dead_owner_guard...). The fixture drops a live publication, so both its listener and its operation-lockflockcan survive in a fork.graph::acknowledged_publisher::missing_lock: all 5 tests (they all build on the dead-publisher fixture, which requires the dropped listener closed).graph::native::foreground:abandoned_publication_is_evidence_and_never_implicit_recovery_authority(needsnative-config-plan).https_recovery:full_command_preserves_large_public_mode_ca_and_exact_retry,full_legacy_command_archives_with_real_current_witness_and_exact_retry(the fixture drops the providerflockthat recovery must acquire).provider::test_socket::bound_unlistenedbinds a socket that never listens, so every copy refuses connections (shared with the existingquiescent_recoveryfixture, which already did this inline). Used by thehttps_recoveryfixture (owner.sock) anddependency_slots::fixture_prior_boot(slot sockets). Regression testtest_socket::tests::an_inherited_copy_of_an_unlistened_socket_still_refuses_connections.engine::relay_exec::framing_bounds_reject_truncation_invalid_stream_and_overflow: shuts the peer down instead of only dropping it, and now holds a cloned copy of the peer for the whole check, so it proves the truncation refusal with an inherited copy present, every run.provider::publisherstop fixture: startedstream-relay-hoston a probe port it had bound and dropped. A fork's copy of the probe (or another test) kept the port, and the publisher exited 73 without readiness. It now uses--publish 0from test(runtime-core): bind stream relay publisher test ports in the publisher #382 and requires theready <port>line. This needednext, so the branch mergesorigin/next(b90b67e).rebind(isolated in test(runtime): make four hosted-macOS candidate-core tests deterministic #387;nested_testskeeps its listener in a child that never forks),network_updateand the remainingshort_home/host_filesystem/admission_pool/transporttests (they assert only the fail-closed refusal, or refuse before any host-wide check),identity::executable_runningusers (a pre-exec fork runs the test binary, never the pool's provider path),https_recovery::rejects_a_live_inherited_unix_listener_at_the_pinned_inode(it already polls the dropped listener for 1 s), and other fixtures that drop anflock(interrupted_start_cleanup,live_owner_cleanup::relay,retained_startup,persistent_data::engine,same_boot_absent_relay): none failed in the pressure runs; see Gaps.Product question: can product forks cause false ambiguity refusals?
process::clean_command,node::supervisorandnode::transportall usepre_exec, so they fork+exec and have a pre-exec window in which the child runs hack-local's image and holds all of its descriptors. Discovery only counts processes running the user-selected dependency executable, and hack-local never selects itself, so a hack-local fork cannot be a second holder in the product. Only the test binary, which is both the scanner and the target, sees this.FD_CLOEXECwould not help: the descriptors are already close-on-exec, and the window ends at exec. No product change. A separate, real descriptor leak found during the audit is reported in Findings (non-atomic close-on-exec for sockets on macOS).Evidence
"Fork pressure" adds a scratch co-tenant test (never committed) to the same test process: 4 threads loop
Command+pre_exec(umask) on/usr/bin/true, the same fork+exec pathprocess::clean_commandtakes, and it starts first. "Hold N ms" makes those children sleep N ms before exec, to model a stalled pre-exec child on a loaded host. "CPU load" isnicebusy loops stopped by a shell trap.Rework (this Mac, Apple Silicon, 18 CPUs, already at load average 10-24 from other work; Rust 1.97.1)
Full all-features lib test binary (CI's
--all-featuresset, 1296-1297 tests),--test-threads=16, fork pressure for 90 s:flockrefusals:missing_lock(4 of its 5 tests,missing_lock.rs:832),dependency_slots(:719,:735),transport(3),native::foreground(1),https_recovery(3),quiescent_recovery(1)graph_timeout(Findings 7), not a descriptor refusalIntermediate builds showed the remaining exposures one at a time. After the first isolation round, every run still failed on the two
https_recoveryfull-command tests (flockdrop, 0/3). After those,provider::publisherfailed 2/5 withpublisher readiness missing(exit 73: the dropped probe port). The publisher fixture change fixed that.Mutations (each new test must fail without the property it guards):
relay_execframing test: remove the peershutdown(keep the cloned copy)relay_exec.rs:338(no end of stream, so no truncation refusal)test_socket::bound_unlistened:listen()afterbind()test_socket.rs:73)assertions did not runEvery isolated parent passes only if its child's exact
test ... okline and1 passedare in the child's output, so a missing or renamed child cannot pass silently.Linux:
cargo +1.97.1 clippy --all-targets -D warnings --target x86_64-unknown-linux-gnu(cross-checked with zig cc, this Mac): exit 0, which coverstest_executableandtest_socketnow compiling on Linux. Running the Linux tests is left to hosted CI.First commit (1c14276, M3, 16 CPUs)
next16f4547)host_endpoint+ fork pressure + CPU loadtests.rs:84or:78)host_endpoint,admission_pool,short_home,host_filesystem,interrupted+ fork pressure + CPU loadhost_endpointdrop tests whose own 1 s read timeout is shorter than the hold)test_scratchregression test, sequence mutated to 0File exists (os error 17), the CI signature)The verifier independently confirmed these at 1c14276 (
host_endpoint20/20 vs next 3/10; the 5 modules 8/8 vs 3/5; thetest_scratchmutation 0/10).Gates at cb6661f
cargo +1.97.1 fmt --check: exit 0.clippy --all-targets -D warnings, default and--all-features(with the pinned Zig): exit 0 (this Mac).cargo test(thetest:localcommand, private target dir): exit 0; lib 1049 passed, 101 ignored; 21 binaries, 1204 passed in total.--all-featurescargo test: the first run, concurrent with this unit's 4-CPU-loop stress, failed 3 native runtime tests withgraph_timeout(Findings 7). Two reruns without the stress: exit 0 both times, 1538 passed over 25 binaries.bun run check(Bun 1.4.2; includes the Bun pin check andprivacy:check: ok) andbun run --cwd packages/cli typecheck: exit 0.gitleaks diron thegit log -p origin/next..HEADexport): no leaks.No TypeScript, CLI surface, agent guidance or task pins changed.
Gaps
host_endpointdrop tests at 1 s;https_recovery::rejects_a_live_inherited_unix_listener...at 1 s).flockand later require it free (listed above) and did not fail in any of the 22 full-suite pressure runs on this Mac. A longer hold could still expose them.test_scratchmutation.Findings (not fixed here)
More fork-exposed release-check tests outside this unit: fixed in this rework (see the audit).SOCK_CLOEXEC, so std (socket,accept,socketpair) andhost_endpoint::pending::connect(pending.rs:16-25) set close-on-exec in a second call. A child spawned by another thread in between (fork+exec orposix_spawn, which std uses withoutPOSIX_SPAWN_CLOEXEC_DEFAULT) keeps the socket for its whole life. Repro (scratch probe, never committed): one thread creates sockets with the two-step pattern (gap widened to 500 us), another spawns the test binary to count its socket descriptors: 199/200 children inherited a socket with either spawn path; control without the creator: 0. Expected: a droppedPendingConnectionor listener closes. Actual: it stays open until an unrelated child exits. Proposed fix: spawn product helpers withPOSIX_SPAWN_CLOEXEC_DEFAULT(a smallposix_spawnwrapper), or serialize socket creation against spawns with a process-wide lock. Needs a product-flow repro before changing behavior.lsofbound still times out under load (HACK-1328 product part, known).short_home::vm_lock_releases...failed once in 20 runs atlock_absent_diskswithprovider_timeout_uncertainunder CPU load and concurrent scans. Unchanged here.network_update::stoppedreturns a plainFilelock (minor). UnlikeVmLock, it does notLOCK_UNon drop, so a concurrent fork's copy keeps the VM lock held briefly after the network update ends. Not reproduced.EBADF(low).host_endpoint::tests::accepted_stream_preserves_payload_and_half_closefailed once atread_to_stringwithBad file descriptoron the unmodified base under fork pressure (1 of 10 runs); not seen in 60+ later runs.git worktree add --detach <dir> <commit>, thenbun test tests/project-task-catalog.test.ts -t "recipes are all reviewed"in it. Expected: the pin review runs (it passes on an attached branch). Actual: it fails attests/project-task-catalog.test.ts:1415withE_USAGEdetached_checkout, because the test selects an instance implicitly fromREPO_ROOT. Reproduced at both 331ec18 (base) and 1c14276. Any detached CI or verification worktree reports a false failure. Proposed fix: pass an explicit branch selector, or useworktree.auto_branch=false, in that test.graph::native::runtime::tests::{two_owned_bridges_keep_per_network_members_and_aliases_through_cleanup, two_bridge_cleanup_retries_only_the_retained_second_bridge_after_first_delete}(tests.rs:143,:243) andauthored_run::{cleanup_reaps_a_recorded_one_off..., finite_one_off_refuses_changed_member_mounts...}(run_tests.rs:155) callexecution::run(..., Duration::from_secs(2))against the in-memoryFakebackend and fail withgraph_timeoutwhen the host is saturated. Repro: all-features lib binary,--test-threads=16, on a host at load average 17-24 plus 4nicebusy loops: 1 of 5 runs (and 3 tests in one CI-equivalentcargo test --all-featuresrun during the stress). Expected: a fake backend finishes well within its deadline. Actual: the 2 s wall-clock deadline expires on an unoptimized, CPU-starved run. Not a descriptor refusal; reruns without the extra load passed (2/2). The isolated tests add ~20 short child test processes per suite run, which slightly raises load. Proposed fix: give these fake-backend runs a hang-guard deadline (for example 30 s), as test(adoption): give v14 compound routing operations the product's caller deadline #383 did for v14 routing.next.Refs HACK-1347
Refs HACK-1348
🤖 Generated with Claude Code