A swap's redial dials until logd admits one, bounded by the swap's window alone - #566
Conversation
…ndow alone The netd-swap tests went red with "the stream's redial was turned away 64 time(s), its ceiling of 64, and gave up" on boots whose guest finished every swap: the re-claimed netd leased and init said "in service" or "restored". The red was the host's own dial count, which is neither the event nor a timeout. The event, "logd admits a dial", is seen only by dialing, and each dial already waits on the machine's answer. So `Stream::redial` now dials until a connection carries a line or its bound passes, and loses its ceiling parameter: - the reader's ceiling is the first dial's only (`State::ceiling` is `None` once a redial begins), so `turned_since_dial` and `past_ceiling` go; - a redial whose bound passes with every connection closed before a line now says so (`Stream::unopened`), where it used to go quiet; - `metalswap::swap` bounds the redial by what is left of the swap's window, waits on the admitted connection, prints how long the refusal window was and how many dials it turned away, and returns `Err` naming both when the window passes with none admitted; - the judge's ceiling finding is deleted; `turned_away` stays a reported number; - `TURNED_AWAY_CEILING` now bounds first dials only, so it moves beside the reader in `metaltalk`. The two ceiling tests become tests that a redial asks past 1000 refusals and resets and past 200 closes before a line, and that it ends at its bound alone and says so on both paths. The judge's test now holds 10 000 dials turned away to be no verdict. The diagnostics issue on the redial's spin keeps its exit; what bounds the spin is now the window alone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W6rME2DoqwjcYFStYHHY4j
|
Review r1 at NOT READY FOR REVIEW |
Review r1 of #566 at
|
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W6rME2DoqwjcYFStYHHY4j
…tests come off the redlist QEMU's slirp forward takes every connect for as long as QEMU lives, so a refused or reset connect on a `Peer::At` redial means QEMU has exited. With the dial ceiling gone, nothing else stopped that redial spinning a host core until the swap's window passed. `count_or_give_up` now ends a forward's redial at the first failed connect and says why in `unopened`. `a_redial_on_a_forward_that_refuses_ends_at_the_refusal` drops the listener and requires the end within 5 s of a 60 s redial with one dial turned away. It is red at 74f7d71 ("a refusing forward ends the redial at once", exit 101) and red again with only the forward arm deleted. The refusal and reset tests move to `Peer::Named` through a `Reach` that answers the name at once, since that is where refusals are real (the T14). `TURNED_AWAY_CEILING` bounds first dials only, and every caller passed it, so the `ceiling` parameter and `State::ceiling` are gone. The const is private, and `open` is told whether it is redialling. `metalswap::swap` no longer returns an `Err` when the redial admits nothing. No guest run reached it, and the judge already reds on `connections (1, 1)` and the missing word. It prints only the milliseconds to the admission, because the judge's `said` line already carries the count. The bound test's closer thread now ends instead of staying blocked in `accept`. `lan_swap`, `swap_netd` and `swap_crash_rolls_back` come off the redlist, and `issues/build/a-swaps-redial-races-a-hard-dial-ceiling-against-an-unbounded-guest-gap.md` is deleted. Its exit condition was "`Stream::redial` gives up on its time bound alone, never on a dial count, and a swap whose refusal window is staged long is green". The orchestrator's hold-green run at 74f7d71 met it: init held the old netd for 1 s, the swap was turned away 97 times, it was green, and the guest console carried logd's second `serving this boot's log to 10.0.2.2:52608` line. The same hold with the change reverted was red on "turned away 64 time(s), its ceiling of 64". The diagnostics issue records that the T14's redial may ask the link faster than RFC 6762 §5.2's floor between queries. That rate is unmeasured on metal. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W6rME2DoqwjcYFStYHHY4j
…ows, and file the harness's misreport quiesce_stops_the_machine was disabled behind a finding whose title and exit rested on the harness's message that the guest asked for a reboot and stayed up. PR #566's capture at 74f7d71 refutes that: writer 5's first pass ran from 2.170 s to 7.434 s, the job printed "5 of 6 writers reached their loop in 5s" at 6.874 s and exited 1, and it never printed "asking for the reset". No stop began. PR #524's capture at 235c5a5 shows the same with "3 of 6", and nightly run 36351950439 on PR #555 at d265676 with "4 of 6". - The slow pass is the defect quiesce_dump_holds_the_stopped's issue already tracks, whose exit names a first write-and-fsync pass over 5 s. That issue is renamed to what both tests show, and gains these sightings. Both rows point at it. - The stops finding is folded into it and deleted. It carried no durable line for a module header; its three sightings move with their evidence. Its a58abf5 sighting also had no stop: record, and whether that job printed its give-up line was not recorded. - stopped_boot waits its whole QMP budget and then calls returned_to_firmware before it reads the console, so a job that never asked is reported as a guest that asked. In the #566 capture every scheduler heartbeat from 10.750 s to 253.244 s was idle, and the test went red after 266 s. Filed as tooling, held by the orchestrator. - The park issue is renamed: its records show 0 block operations open, so both threads were running, and one was the held thread, which last::hold keeps spinning while a sweep counts 2. It now names each sighting's PR and head, adds the 98e803c stop that gave up, labels the dispose_yield suspect as a hypothesis, and records that woken_by_its_threads has no enabled caller. Its exit asks for an instrument that names each thread still running, and for that coverage back. - Deleted: the scratch log names and paths, "after a stop that reported every thread stopped", "on a loaded host", and the build/ park issue's "--known-red answers NO". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W6rME2DoqwjcYFStYHHY4j
Review r2 of #566 at
|
…d an owner, and three NOTEs become their own issues The redial's forward cost was a NOTE with no evidence: add hold-green's 8125 dials turned away in 7019 ms, 16231 of the guest's 19034 interrupts on cpu0, and hold the diagnostics issue for the orchestrator alongside it. Three more NOTEs from the round-2 review are filed rather than fixed here: the T14's redial still re-asking mDNS after every refusal below RFC 6762 §5.2's floor, `wait_until` for init's final word never waking once the stream is dead, and a ~6 s guest-side gap after `logd` re-binds that a swap's redial actually pays for. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W6rME2DoqwjcYFStYHHY4j
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W6rME2DoqwjcYFStYHHY4j
…und's brief Running the round's own cargo test --lib gate under this machine's current concurrent load reproduced a red on a-redial_on_a_forward_that_refuses_ends_at_the_refusal twice in six runs, always green alone or on a quiet host. Filed rather than fixed: it is not named in this round's ruling. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W6rME2DoqwjcYFStYHHY4j
…edials a_redial_on_a_forward_that_refuses_ends_at_the_refusal went red under host load with turned_away() == 2. The landing round blamed the first dial retrying a transient refusal from a live listener. That cannot happen here: the listener is bound before the dial and never holds more than one pending connection, so a loopback SYN to it is taken, never refused. The real race was in the test helper `accepted`. Its thread accepted on a try_clone of the listener and dropped that clone only after sending the connection back. So the test's drop(server) did not close the listener when the helper thread had not yet run past its send. The redial's connect was then taken into the still-live accept queue, reset once the clone closed (read counts it: a connection ended before a line), dialled again, and refused (count_or_give_up counts it and ends the redial). That makes two counts. The product code counted that sequence correctly; the stimulus was wrong. Holding the window open with a 300 ms sleep after the send reproduced it every time (left: 2, right: 1). With the fix, the same sleep stays green. The helper now drops its clone before it sends, so the listener is closed before `accepted` returns. The test also asserts that the first dial counts nothing before the redial, so the first dial's count and the redial's are measured apart. Deletes issues/build/a-forwards-redial-negative-control-counts-a-live-listeners-transient-refusal.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W6rME2DoqwjcYFStYHHY4j
…istener This corrects 33cfc34's root cause, which was incomplete. Closing `accepted`'s clone before its send closed one window (a 300 ms sleep after the send reproduced the red every time). But in the full `--lib` suite under load, that binary still went red on this test 9 times in 41 runs. The pre-fix binary went red 9 times in the same 41, interleaved with it. The listener has a second holder: any child process this test process is spawning. A spawned child holds a copy of every fd until its exec closes the close-on-exec ones, and many lib tests spawn processes (git, cargo, the test binary itself). A scratch probe measured it: bind 127.0.0.1:0, drop, connect, 20000 times. - No concurrent spawn: 0 taken, 0 reset, 20000 refused (twice). - A thread spawning children beside it: 34 taken and 4 reset, then 26 taken and 7 reset. So no test can make a dropped loopback listener provably gone. The redial's connect was taken by the still-open listener and reset once the child's exec closed it, which `read` counts. It was then dialled again and refused, which `count_or_give_up` counts. That makes two counts. The product code counts that sequence correctly and is unchanged. The test now stages the forward through `TurnedAway`, as the other redial tests stage their refusals. The first dial is real and taken, and every dial after it is refused or reset by the test. It asserts that the first dial counts nothing and that the redial counts its one refused connect. The `accepted` change is reverted, since no test depends on a dropped listener closing any more. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W6rME2DoqwjcYFStYHHY4j
… forward redial's fix The proof loop for 8cc19ef ran 200 full `--lib` suites of its test binary and of 941f0bd's, interleaved, beside `cargo test --workspace --exclude toyos-build` at one-minute load averages up to 43. Three other tests went red. None is on this round's brief, so all three are filed and not fixed. - a_refused_first_dial_is_asked_again_up_to_its_ceiling: once for 8cc19ef and twice for 941f0bd. It dials a dropped listener, which is the measured spawn-held-fd mechanism. - a_redial_ends_at_its_bound_alone_and_says_so: once for 941f0bd. Its reader woke past a 50 ms bound and never dialled. - buildlock's a_key_being_built_is_waited_for_and_another_key_is_not: once for 8cc19ef. A dropped lock was still held; the cause is unmeasured. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W6rME2DoqwjcYFStYHHY4j
…e scheduler Two of this PR's lib tests reddened under load, each 1 in 200 full `--lib` suite runs beside `cargo test --workspace --exclude toyos-build`. `a_refused_first_dial_is_asked_again_up_to_its_ceiling` dialled a dropped listener. A child that another test in the same process is spawning holds a copy of that fd until its exec, and while it does the listener takes connects (8cc19ef measured this). Its refusals are now staged through `Reach` by `Refusing`, as the forward test's are. Every dial is refused, and the stage refuses a resolve or an ask loudly, since an address is only dialled. `a_redial_ends_at_its_bound_alone_and_says_so` assumed its reader dials inside a 50 ms bound. The bound is computed in `redial` on the test's thread and read on the reader's, so no test can make that dial happen without a clock seam, and adding one is a production change. The test now accepts either history and checks the reason names what happened. A redial that never dialled ends "by the bound: never asked". A redial that dialled ends on its one close, which `close_every` makes only once the bound has passed since it took the connection, so `serve`'s check ends the redial and names the close. More than one counted dial fails the test. Injecting a 100 ms delay before a redial's first dial reproduces the filed red on the old test (EXIT=101, "never asked") and passes the new one (EXIT=0). Along the way I found a window this test no longer reaches: `serve` continues inside the bound, `open` then re-reads the clock, and a redial that dialled can end saying "never asked". It is filed as issues/diagnostics/a-redial-that-dialled-can-end-saying-never-asked.md. Both issue files are deleted. The keyed-lock issue's citation of the first one goes with it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W6rME2DoqwjcYFStYHHY4j
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W6rME2DoqwjcYFStYHHY4j
Review, round 4, at efa141bGate. CI Earlier BLOCKERs. r2 left none open. Checked, no finding
BLOCKERNone. NOTE
REMOVE
LAND AFTER NAMED CHANGES |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W6rME2DoqwjcYFStYHHY4j
…sts' closes reach the reader whatever a child holds `serve` read the clock after a connection closed before a line, and `open` read it again before its first dial, starting from "never asked". A bound passing between the two reads ended a redial that had dialled, and counted the close, with "... by the bound: never asked". `serve` no longer reads the bound: it hands the close's reason to `open` as its `last`, so `open`'s one bound check names every redial's end, and `serve`'s second reason format is gone. `read` returns how a connection it counted as turned away ended, in place of a bool. `a_redial_ends_at_its_bound_alone_and_says_so` now requires a redial that dialled once to end with "by the bound: the latest connection ended before a line". Against the unfixed production code it is red on the deleted format; with round 4's m2a on that code, and with the fix's reason not carried, it is red on "by the bound: never asked". The tests' three closes of an accepted connection shut it down before dropping it: on macOS std sets close-on-exec only after `accept` returns, and a child spawned in that window holds a copy that keeps the reader from seeing EOF. `close_every`'s closer ends on the wake-up connection rather than closing it. Deletes issues/diagnostics/a-redial-that-dialled-can-end-saying-never-asked.md, the defect being fixed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W6rME2DoqwjcYFStYHHY4j
src/redlist.rs conflict: main's #564 deleted so_cache_refusals and usb_disk_index_stable rows (their tests are gone); this branch's redial fix deleted swap_crash_rolls_back, swap_netd and lan_swap rows (their tests now pass). Both sets of deletions kept; no row this branch changed was reverted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W6rME2DoqwjcYFStYHHY4j
Review, round 5, at e0edce3Gate. CI Earlier BLOCKERs. None were open after round 4.
Checked, no finding
BLOCKERNone. NOTE
REMOVE
LAND AFTER NAMED CHANGES |
… the second buildlock red's duplicate issue file is deleted. serve's guard at :347 always fails on the path that leads to read's early exit, so its string was never read; it now returns None, which serve treats the same as a line having been carried. issues/build/a-dropped-keyed-lock-is-still-held-in-the-loaded-lib-suite.md duplicated the record main already keeps under a-key-being-built-is-waited-for-and-another-key-is-not-reds-under-host-load.md for the same test and assertion, and named no owner. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W6rME2DoqwjcYFStYHHY4j
Conflict in src/redlist.rs: main still disables swap_crash_rolls_back and swap_netd behind issues/build/a-swaps-redial-races-a-hard-dial-ceiling- against-an-unbounded-guest-gap.md, the issue this branch's fix resolves and already deleted at 7e06a65; those two rows are dropped, keeping main's unrelated new syscall_window_nmi row. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W6rME2DoqwjcYFStYHHY4j
Brings in #572 (host QEMU's edk2), #580 and #579 (disabled reds), #549 (a kill never waits on its victim) and #566 (metaltalk redial). - src/redlist.rs: one row each for user_copy_races_munmap and quiesce_leaves_the_volume_whole, which both sides added. main's new rows stay (netd_refused_accept, quiesce_wakes_on_the_last_teardown, root_chunk_refused_on_a_usb_stick, syscall_window_nmi). The rows for tests or issues this branch deleted go (hda_tone, doom_sound_flood, latency_wake, sched_check_build), and so does lan_swap, whose issue main deleted with swap_netd's and swap_crash_rolls_back's rows. - The two issue files both sides added take main's text. - tests/common/power.rs: main's woken_by_the_held_thread, shared by the new quiesce_wakes_on_the_last_teardown, without the two clock verdicts this branch took off QEMU (stopped_the_machine in stopped_boot, and woken_by_its_threads). - tests/common/qemu.rs: qemu_command takes main's firmware_vars and has no audio_wav, so profile_argv passes six paths. The too_many_arguments allow goes, because seven parameters do not trigger it. - kill_while_blocked.rs: main's text. After #549 a kill does not park in retire_task, so this branch's doc for arm 4 was false. main's arm also has no clock. - tests/toyos.rs check_rust_result: this branch's single-print form, which already carries the stdout main added. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W6rME2DoqwjcYFStYHHY4j
Main's rust pin has not moved since the last merge, so the fork is unchanged. Every conflict, and how it was resolved: Modify/delete, main deleted: - issues/build/a-swaps-redial-races-a-hard-dial-ceiling-against-an-unbounded-guest-gap.md: #566 fixed the defect and deleted the issue. This branch had added one sighting to it, and a sighting of a fixed defect has no home, so the file stays deleted. - src/heartbeat.rs: #562 deleted `kernel_heartbeat`'s CPU-mask and gap verdicts with the file. This branch had given its done-line table blockd and fsd rows. The table goes with the verdict it served. - tests/doomcase/system.toml: #562 moved the doom audio tests to metal and deleted their QEMU config. This branch had added blockd and fsd rows to it. Nothing boots it now. Modify/delete, this branch deleted: - tests/toyos-rust-tests/src/bin/ftruncate_flush_race.rs, tests/toyos-rust-tests/src/bin/quiesce_fsync.rs, issues/build/ftruncate-flush-race-reds-intermittently-and-nothing-says-why.md, issues/build/quiesce-leaves-the-volume-whole-needs-its-flush-to-close-inside-the-stops-budget.md and issues/kernel/a-root-metadata-read-refused-on-budget-is-not-retried.md: main's hunks remove timing from them or note its own runs. They are about the kernel FAT flush, the stop's kernel sync and the kernel's metadata read, which this branch deletes, so they stay deleted. Content: - kernel/src/actuator.rs: main's `quiesce_last_teardown` (#549) is kept. The kernel FAT actuators `fat_flush_meta_refuse`, `resize_evict_window` and `resize_fault_refuse` stay deleted. `process_reopen_selftest` stays where this branch has it, with main's doc (#549 also opens every kernel thread's pid). - src/redlist.rs: both conflicted rows go. `doom_sound_flood` left QEMU with #562, and this branch deletes `ftruncate_flush_race`. - tests/common/gpt.rs: this branch's `device_saying` and decoy `boot` are kept. Main drops the `drain_serial` window, so its `qemu` binding is no longer `mut`. - tests/common/inspect.rs: main's "nothing plays audio" (#562 deleted `inspect_plays`) is taken, with this branch's clause on the boot stick. - tests/common/iommu.rs: main's `panic-reboot-fast` and its wait for the fatal path's reset are kept. This branch's `iommu_empty_domain` reads the xHCI's DCBAAP over QMP, and QEMU has exited by the time that reset is seen. So `fault_boot` now takes a `holding` read, which it runs after the fault line and before it waits for the reset, while the fatal path holds its panel. `iommu_context_absent` reads nothing there. - tests/common/origin.rs: main's judgement of `log_ring_keeps_the_owners_slots` is taken whole: init says it waited a flush out, or its stop line is missing. That drops the millisecond inference between two records, whose record this branch had changed from `Syncing filesystems...` to the stop record (#562: no QEMU test measures time). - tests/common/volumes.rs: main's timing edit to `ftruncate_flush_race` goes with the test. - tests/logstallcase/system.toml: main drops `power` and the `shutdown` symlink, since the metal row reads `/log` without a stop. This branch's blockd and fsd rows are kept, because fsd holds `/log`. - tests/toyos-rust-tests/src/bin/blockd_io.rs: main's `claim_when_free`, now generic and with no deadline, is taken inside this branch's `if let Some(syscap)`. `bench` is this branch's blockd-only arm with main's timing removed: no MiB/s, and the line says only how many Flushes each run took. The module doc's "timed" goes. - tests/toyos-rust-tests/src/roster.rs (add/add): both sides wrote one roster decoder. Main's is taken whole, because five binaries read it and it has no deadline (#562). This branch's copy had a 5 s give-up. - tests/toyos-rust-tests/src/bin/process_lifecycle.rs: main's is taken whole. This branch's only change to it was the move onto its own roster.rs. - tests/toyos-rust-tests/src/bin/process_stats.rs: main's `refused_calls_are_counted` and its roster wait for the held child are kept, and so are this branch's two connection arms. The system capability is taken once in `main` and passed to the three arms that read the roster, since a second take of the label finds nothing. The connection arms now wait on main's `threads_of` for the child's main thread to be blocked, with no deadline. - tests/toyos-rust-tests/src/bin/quiesce_twice.rs: main's `Duration`-only import. This branch deletes the owed file, so `File` and `Write` go. - tests/toyos.rs: - RUST_SKIP: main's audio rows are taken. `audio_tone_load` goes, since main deleted it. `log_volume_reread` goes, since this branch deletes it. - MACHINE_TESTS: `quiesce_leaves_the_volume_whole` stays deleted. `quiesce_wakes_on_the_last_teardown` comes from main with main's comment. `blockd_serves_nothing` is kept. `hda_tone` and `hda_client_stall` went to metal with #562, and `hda_two_live_refused` takes main's comment. - CARRIES and dispatch: the same. - `nvme_wide_sector`: this branch's blockd arm, which already had no drain window. - toyos-quiesce/src/lib.rs: this branch's `FILES_MS`, `FLUSH_MS` and `SYNC_MS` are kept, with main's `LAST_THREAD` doc, which names both quiesce-last actuators. - userland/logd/src/policy.rs: this branch deletes the module doc and the `LOG_WRITE_BUDGET` paragraphs main edited one line of, so they stay deleted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A swap of netd reddened
lan_swap,swap_netdandswap_crash_rolls_backwith "the stream's redial was turned away 64 time(s), its ceiling of 64, and gave up", on boots where the guest finished every swap: the re-claimed netd leased, and init saidin serviceorrestored. The red came from the host's own dial count. A count is neither the event nor a timeout.What changed, per decision
logdadmitting a dial. It can only be seen by dialing, and each dial already waits on the machine's answer.Stream::redial(by)dials until a connection carries a line orbypasses, and a redial whose bound passes says so inStream::unopened.metalswap::swapbounds the redial by what is left of the swap'swindow.turned_since_dialandpast_ceilingare deleted: the first dial's count isturned_awayitself, since nothing is dialled before it.Peer::At's only user is QEMU's slirphostfwd, which takes every connect for as long as QEMU lives. A refused or reset connect there means QEMU has exited, and without a dial ceiling nothing else stopped that redial spinning a host core until the window passed.count_or_give_upends it on the first failed connect and says why inunopened. A forward's first dial keeps its ceiling.serveused to read the clock after a connection closed before a line, andopenread it again before its first dial, starting from "never asked". A bound passing between the two reads ended a redial that had dialled, with its close counted, as… by the bound: never asked.serveno longer reads the bound. It hands the close's reason toopenas its startinglast, soopen's one check ends every redial and names what its latest dial got:… by the bound: the latest connection ended before a line: <how>.serve's second reason format is deleted.readreturns how a connection it counted as turned away ended, in place of a bool.TURNED_AWAY_CEILINGbounds first dials only, and it is private tometaltalk. Every caller passed it, so theceilingparameter ofStream::connectandState::ceilingare gone, andopenis told whether it is redialling.swapprints the milliseconds to the admission. It does not judge them. The judge'ssaidline already carries how many dials were turned away. When the redial admits nothing, the judge reds onconnections (1, 1)and the missing word, with noErrofswap's own.turned_awaystays in the report as a number and is never a verdict.src/redlist.rs, andissues/build/a-swaps-redial-races-a-hard-dial-ceiling-against-an-unbounded-guest-gap.mdis deleted with them. Its exit condition was "Stream::redialgives up on its time bound alone, never on a dial count, and a swap whose refusal window is staged long is green". Hold-green meets it (below). The slug has no other citation in the tree (git grep).issues/diagnostics/a-swaps-redial-asks-again-with-no-event-to-wait-on.mdrecords that the T14's redial may ask the link faster than RFC 6762 §5.2's floor. That rate is unmeasured on metal, and the issue's exit removes it.ReachonPeer::Named, because refusals are real there (the T14).shutdown(Shutdown::Both)before the drop (close). On macOS std sets close-on-exec only afteracceptreturns, and a child spawned in that window holds a copy, so a drop alone does not give the reader its EOF.close_every's closer ends on its wake-up connection rather than closing it.Reach(TurnedAway), because a loopback listener this test process drops still takes connects while any child that another test is spawning holds its fd. The test asserts that the first dial counts 0 and the redial counts 1.Reach(Refusing) for the same reason.The forward test's flake
a_redial_on_a_forward_that_refuses_ends_at_the_refusalwent red under host load withturned_away() == 2.read_first's listener is bound before the dial and never has more than one pending connection, so a loopback SYN to it is taken, never refused. In all 9 reds of33cfc348's binary, its new pre-redial assertion (turned_away() == 0) held, and the count of 2 was the redial's.readcounts. It was then dialled again and refused, whichcount_or_give_upcounts. That makes two counts.fdprobe/): bind127.0.0.1:0, drop, connect, 20000 times per run.accepted's helper thread drops itstry_cloneonly after its send. A 300 ms sleep after the send reproducedleft: 2, right: 1every time. Closing the clone first (33cfc34) still went red 9 times in 41 full suites, the same count as the unfixed binary in the same interleaved runs. 8cc19ef reverts that change, since no test depends on a dropped listener closing any more.Reach, as the other redial tests stage theirs, andissues/build/a-forwards-redial-negative-control-counts-a-live-listeners-transient-refusal.mdis deleted. The slug had no other citation.Proof: 200 iterations beside a looping
cargo test --workspace --exclude toyos-build(11 runs, all EXIT=0), at one-minute load averages up to 43. Each iteration ran three things.8cc19ef1--libsuite, at8cc19ef1--libsuite,941f0bda's binary, interleavedA first loop, with the test alone, went 200/200 on both the unfixed and the fixed binary at load average 3.5, so it had no power to detect the race.
The two load-flaky tests (e47d1f0)
Both tests the loaded suite found are fixed here, and their issue files are deleted.
a_refused_first_dial_is_asked_again_up_to_its_ceilingdialled a dropped listener. That is the forward test's mechanism: a taken dial closed before a line ends a first dial withunopenedunset. Its refusals are now staged throughReachbyRefusing, which refuses every dial and treats a resolve or an ask asunreachable!, since an address is only dialled.a_redial_ends_at_its_bound_alone_and_says_so's closes arm assumed its reader dials inside a 50 ms bound. No test can make that true.redialcomputes the bound on the test's thread and the reader reads it on its own, and staging a clock would be a production change. So the arm accepts either history, and its reason must name what happened:… by the bound: never asked;by the bound: the latest connection ended before a line;close_everynow closes a connection only once the bound has passed since it took it. A redial that dialled therefore ends on its first close.probe-late-reader.patchsleeps 100 ms at the top of a redial'sopen. With it, the test as54ce11eahas it goes red with the filed message,At(127.0.0.1:57899) was not serving its log by the bound: never asked(EXIT=101). The test ase47d1f06has it stays green (EXIT=0). Both builds exit 0, and the patch is applied checked and restored (mutate.sh,mutations.txt).Loop at
e47d1f06: 200 runs of the full--libtest binary beside a loopingcargo test --workspace --exclude toyos-build(6 runs, all EXIT=0), at one-minute load averages from 4.70 to 53.49 (redial-r4/loop.sh,loop-results.txt,load-exits.txt).a_refused_first_dial_is_asked_again_up_to_its_ceilinga_redial_ends_at_its_bound_alone_and_says_soa_redial_on_a_forward_that_refuses_ends_at_the_refusalAt 1 red in 200, a loop of 200 has little power to show a race gone. The staged refusals and the late-reader probe are the evidence that it is gone. The loop only shows nothing new appeared.
Loop at
76cbf2d7: the same method, 200 runs of the full--libtest binary built from a clean tree at76cbf2d7(bin-provenance.txt), beside a loopingcargo test --workspace --exclude toyos-build(5 runs, all EXIT=0), at one-minute load averages from 6.36 to 71.69 (redial-r5/loop.sh,loop-results.txt,load-exits.txt). Each run checked all 15metaltalk::tests.metaltalktests, every runThe three suite reds are off this PR:
buildlock::tests::a_key_being_built_is_waited_for_and_another_key_is_notatsrc/buildlock.rs:945in runs 92 and 135, andsysroot::tests::a_sweep_removes_what_no_worktree_names_and_nobody_usesatsrc/sysroot.rs:942in run 134, where the sweep afterdrop(using)removed nothing (full-92.log,full-134.log,full-135.log).Mutations of the product behaviour each test guards. Each is applied as a checked patch, built (EXIT=0), run with
--exactand restored. The tree was compared with the fix after each one (mutations.txt).m1a-ceiling-off-by-one:turned_away > TURNED_AWAY_CEILINGleft: 65, right: 64m1b-first-dial-not-asked-again: a first dial gives up at its first refusalleft: 1, right: 64m2b-named-redial-ends-at-a-refusal: a named redial ends at a refusal the way a forward's does… a forward fails a connect only once QEMU has exitedThe redial's-reason fix's controls, at
76cbf2d7. Each is a checked patch against the fix, built (EXIT=0), run with--exacton the bound test, and restored. The tree was compared with the fix after each (redial-r5/mutate.sh,mutations.txt), and the metaltalk tests were green after the last restore (EXIT=0).nc1-unfixed-production: every production hunk of the fix reverted onto its base, the new test keptAt(127.0.0.1:61545) admitted no connection by the redial's bound: the latest ended before a line, the deleted formatnc2-m2a-on-unfixed:nc1plus round 4'sm2a, which holds open the window betweenserve's clock read andopen'sAt(127.0.0.1:61551) was not serving its log by the bound: never askednc3-close-not-carried: the fix, withserveseedinglastwith "never asked" instead of the closeAt(127.0.0.1:61561) was not serving its log by the bound: never askedOn the unfixed code the window is two adjacent clock reads with no seam between them, so no host test reaches it without a mutation.
nc2is that window held open, andnc3is the fix with the defect put back.Gates at
76cbf2d7Logs are in the job scratchpad,
redial-r5/, from a clean tree.cargo test --workspace --exclude toyos-buildgate-workspace.log)cargo test -p toyos-build --libgate-lib.log)cargo run -- --clippygate-clippy.log)Negative controls
Host-level:
a_redial_on_a_forward_that_refuses_ends_at_the_refusalwas added onto74f7d717. That tree built (exit 0), and the test was red on "a refusing forward ends the redial at once" (exit 101, after 5.02 s;redial-r2/b1-red.sh,b1-red-run.log).8cc19ef1, deleting only the forward arm ofcount_or_give_up(forward-arm.patch, applied checked and restored) built. The staged test went red on "a refusing forward ends the redial at once" (exit 101, after 5.02 s;mutation-forward-arm-staged.log).Guest-level, run by the orchestrator at
7e06a657.cargo test --test toyos-build -- swap_netd: EXIT=0 (566r2-swap_netd.log).cargo test --test toyos-build -- lan_swap: EXIT=0 (566r2-lan_swap.log).cargo test --test toyos-build -- swap_crash_rolls_back: EXIT=0 (566r2-swap_crash_rolls_back.log).hold.patchon the branch;cargo test --test toyos-build -- swap_netd): EXIT=0. The redial was turned away 8125 times, andlogdadmitted the stream again 7019 ms after the redial (566r2-hold-green.log).hold.patchplusfix.patchreverted, which is the whole code change reverted onto its base;cargo test --test toyos-build -- swap_netd): EXIT=1 on "the stream's redial was turned away 64 time(s), its ceiling of 64, and gave up" (566r2-hold-red.log).cargo test --test toyos-build(the Fast tier, no filter): 399 passed, 399 total, EXIT=0 (566r2-fast.log).fix.patchat that head isgit diff af817e51 7e06a657oversrc/metal.rs,src/metalswap.rs,src/metaltalk.rsandtests/common/logstream.rs. It leaves out the issue files, which are prose, andsrc/redlist.rs, whose rows would disable the test being run. Against a scratch index of7e06a657,git apply --cached --check hold.patchexits 0,git apply --cached -R --check fix.patchexits 0, and so doesfix.patchreverted on top ofhold.patch. Both arms build at7e06a657: with each applied,cargo run -- --build-onlyandcargo test --test toyos-build --no-runexit 0 (arms-build.sh,arms.status).Guest-level, run by the orchestrator at
76cbf2d7:cargo test --test toyos-build -- swap_netd: EXIT=0 (566r5-swap_netd.log).cargo test --test toyos-build -- lan_swap: EXIT=0 (566r5-lan_swap.log).cargo test --test toyos-build -- swap_crash_rolls_back: EXIT=0 (566r5-swap_crash_rolls_back.log).cargo test --test toyos-build(the Fast tier, no filter): 398 passed, 398 total, EXIT=0 (566r5-fast.log).Independent oracle: hold-green's guest console carries
logd's own word that it admitted the redialled reader, in its second serving line:{7.721 tid=1 logd} logd: serving this boot's log to 10.0.2.2:59913. The first was{0.435 tid=1 logd} … 10.0.2.2:51775. Nothing this host counts produces that line.Unsure
never askedwith no dial counted. The loops did not record which arm each run took.🤖 Generated with Claude Code
https://claude.ai/code/session_01W6rME2DoqwjcYFStYHHY4j