Repository navigation
Two host tests that reddened under concurrency read their own state right - #1186
Conversation
…is_finished a_lane_gives_its_reader_every_record_in_order_or_counts_it_refused ended its read loop on JoinHandle::is_finished and compared the refusals it had read with the writer's own count. JoinHandle::join documents that the thread's completion synchronizes with its return; is_finished documents no ordering, and is `Arc::strong_count(&self.packet) == 1`, a Relaxed load (std's thread/lifecycle.rs, alloc's sync.rs). So seeing the writer finished orders none of its pushes before the reader's next read, and on a weak-ordered host the last read can miss the writer's last refusal: the one red issues/a-lane-reader-counted-one-refusal-fewer-than-its-writer-made.md recorded, 49999 read against 50000 refused at a load average of 44, was one short at the end. The reader now reads while the writer runs, joins it, and reads once more: the join orders every push before that read. The ring test beside it already waited on a Release/Acquire flag stored after its joins. Not reproduced: the unchanged test ran green 2400 times, twelve loops of 200 at once on an aarch64 macOS host. The fix rests on join's documented ordering. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
buildlock's a_narrow_guest_does_not_overtake_a_wide_one_waiting reads the wide guest as queued once a probe finds the queue lock held, and only then starts the narrow one (issues/a-narrow-guest-test-reads-any-queue-holder-as-the-wide-waiter.md). A flock belongs to the open file description, and a sibling test's fork shares every description this process has open until the child execs: if one forks while the first guest holds the queue, the queue stays held after that guest's handle is dropped, the probe reads the wide guest as queued before it has asked, and the narrow guest that follows takes the free CPU first. Its guest threads then outlive the failed test, write lock files into its removed scratch, and the process's last TempDir finds its root not empty: the second red that run reported against whichever test dropped last (sysroot's the_primary_s_fork_checkout_is_moved_to_its_pin, buildlock's a_clean_cannot_land_inside_a_build). Measured on the dev host (aarch64 macOS), the lib test binary filtered to `buildlock::`: in a whole `cargo run -- --ci host`, the two reds above; in loops of the filtered binary, 3 of 15 and 2 of 60 red; the one red read whole was this test, its leftover root holding only this test's `locks/guest-cpus/cpu-*`. A/B in one session, the two binaries interleaved 150 runs each: the base 9 red, this change 0. The scenario is now an #[ignore]d test the real one runs alone through `rerun`, as the module's other process-level scenarios are: no sibling forks in that process, and its threads end with it. There the queue's only holders are the guests and the probe, and the first guest's handle is closed before it reports, so a failed probe is the wide guest holding the queue, which is what the record's exit asked for, and the record goes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review r1 of #1186 at 7d79229Architecture. Both changes are confined to tests and fix defects in the tests themselves. The product code in buildlock: the cause is real, and isolating the test does not hide a fairness defect.
Lane proof: the cause is real. In the fork, Records: both deletions are accurate, and nothing outside RequiredNone. Suggestions
LAND |
Problem
Two host tests went red intermittently, each for a defect in the test:
buildlock::tests::a_narrow_guest_does_not_overtake_a_wide_one_waiting(issues/a-narrow-guest-test-reads-any-queue-holder-as-the-wide-waiter.md). The test treats the wide guest as queued once a probe finds the queue lock held. A flock belongs to the open file description, and a fork made by a sibling test shares every description this process has open until the child execs. If a sibling forks while the first guest holds the queue, the lock stays held after that guest drops its handle. The probe then treats the wide guest as queued before it has asked, and the narrow guest takes the free CPU. The failed test's guest threads also outlive it and recreate lock files in its removed scratch directory, so the process's lastTempDirdrop panicsDirectory not empty. That panic lands on whichever test drops last:sysroot'sthe_primary_s_fork_checkout_is_moved_to_its_pinin a whole--ci hostrun here, anda_clean_cannot_land_inside_a_buildin a loop.toyosSDKlog::proof::a_lane_gives_its_reader_every_record_in_order_or_counts_it_refused(issues/a-lane-reader-counted-one-refusal-fewer-than-its-writer-made.md, 49999 counted against 50000). The read loop stopped onJoinHandle::is_finished, which is a RelaxedArc::strong_countload and orders nothing.joindocuments that the thread's completion synchronizes with its return. On a weak-ordered host the last read could therefore miss the writer's last refusal.Change
#[ignore]d test that the real test runs alone throughrerun, the pattern this module already uses for process-level scenarios. That process has no sibling forks, so a failed probe means the wide guest holds the queue. The scenario's threads also end with its process.Evidence
Dev host (aarch64 macOS, 14 cores), the build-system lib test binary filtered to
buildlock::, the base and fixed binaries interleaved in one session, 150 runs each: base 9 red, this change 0. The one leftover scratch root inspected held only this test'slocks/guest-cpus/cpu-*. Earlier loops of the base reproduced it 3 of 15 and 2 of 60.The lane race was not reproduced: the unchanged test ran green 2400 times (twelve parallel loops of 200 on the same host). That fix rests on
join's documented ordering againstis_finished's implementation.Host checks:
cargo run -- --ci hostat 205e7f8 (the head 7d79229 has the same tree, its message reworded), exit 0, 108 steps green. The same check on #1187's tree reddened only on this buildlock test and itsDirectory not emptyfollow-on. Logs and the A/B: https://github.com/ToyOSOrg/evidence/tree/ab37950cf5b2603b81464a27a05eeed52b5480afNo guest behaviour changes, so no guest rows ran.
sdk/toyos/srcchanges in test code only, but that is a token change, so the sysroot identity moves.🤖 Generated with Claude Code