Repository navigation
Conversation
The mio fork's ToyOS selector armed a watch for each registration from a copy it took at the start of every select, then parked. A registration another thread made meanwhile had no watch until that select returned for some other reason, so a select with no timeout and nothing else ready waited for good. Fork commit 3e070f6 gives the selector an event that every select watches: a register or reregister made while a select waits on its copy signals it, and the select arms its registrations again from a fresh copy. A select now returns only with an event or at its timeout, an empty one included. Both lockfiles that name the fork move to it. `mio_registers_while_parked` is a new member of the testcases boot: the main thread polls with no timeout, a second thread waits for the kernel's roster to show it blocked, then registers a waker through a cloned registry and wakes it, and the poll must return that waker's token. A poll that returns without it, or never returns, ends the process by name past a 10 s ceiling. The issue record is closed. Its owed metal run of `mio_waker_coalesces` (#1261) is part of the testcases boot of main that issues/nothing-fails-when-a-devices-release-or-close-stops-answering-its-polls.md owes whole, every shared member that polls passing, which now holds `mio_registers_while_parked` too. Found while reading the selector and recorded, not fixed: issues/mios-selector-fails-every-select-past-63-registrations.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hy its order holds Comments only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…kfiles A fork patch registered in .cargo/local.toml, as the implementer rules say, reaches every cargo invocation under the checkout. rust/library, rust/src/bootstrap and sdk/libc then write [[patch.unused]] into their lockfiles, and the build refuses rust/src/bootstrap as dirty (build/llvm.rs:329). This was seen during this PR and reproduced at f9e3135. It is recorded here, not fixed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review r1 of #1276 at f9e3135ArchitectureThe selector is the right owner, and an event it signals itself (the self-pipe pattern) is the right mechanism. It needs no kernel change and respects the one-submitter ring. The fork rules are met:
Net change: fork Races I traced, with no lost or duplicated wake found:
Evidence checked:
FindingsB1. Evidence: no run shows the test red on the recorded defect itself. The test's own red path for the defect, a poll that never returns followed by the second thread's Cheapest sufficient check: one B2. Before, that made The spin is not new in kind, but the new loop now hides it inside a wait that claims to wait. Fix: report the refused registration, for example as an event for which B3. S1. S2. Notes for the orchestratorThese are not findings against this branch.
SEND BACK (B1, B2, B3) |
… 63-registration record counts streams right B2: fork commit ed29321 ends the spin a refused registration watch made of a select. The kernel refuses a watch it will never fire: a handle closed while it was watched, one without the right to wait on it, or an interest its object has no watch for. Such a watch now ends its registration and is reported once, as an error in the directions the registration reports, so nothing is armed again in a loop. The same commit makes the selector's change event and a waker's event one type (S1), and makes its take as loud as its signal (S2). Both lockfiles that name the fork move to it. B3: a TCP stream registered for both directions takes two registrations, so the record's tokio runtime fails at its 31st stream, not its 64th socket. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem
The mio fork's ToyOS selector arms a watch for each registration at the start of every
select, from a copy of the list it takes under the selector's lock, and then parks ininbox_submit(issues/mios-selector-arms-no-registration-made-while-another-thread-selects.md). A registration that another thread made while thatselectwas parked was not in the copy, so its handle had no watch until theselectreturned for some other reason. With no timeout and nothing else ready, theselectwaited for good while the new source's event happened. Upstream'sRegistry::try_cloneexists for exactly that other thread. The registering thread cannot arm the watch itself: a ring has one submitter, so its submit while theselectis parked is refusedBusy.What this does
Fork commits
3e070f6anded29321onToyOSOrg/miotoyos, on top of4108eff. Both lockfiles that name the fork pined29321(Cargo.lock,tests/guest/rust-tests/Cargo.lock).selectwatches it beside the registrations, under a token no registration's generation takes. A register or reregister made between aselecttaking its copy and draining what it waited for signals the event, once, under the lock the copy is taken with. The parkedselectwakes and arms its registrations again from a fresh copy. It takes the signal under that copy's lock, so any later signal is a change the copy does not hold. A deregister needs no wake: the drain already drops a completion whose registration is gone.selectwith nothing registered now waits too. It used to return at once whatever its timeout, which upstream's selectors do not do. A timeout past what anInstantholds waits for good, and a timeout's nanoseconds no longer wrap.ed29321, review r1). The kernel refuses a watch it will never fire: a handle closed while it was watched, one held without the right to wait on it, or an interest its object has no watch for. The drain used to drop that refusal silently. Under the new loop it would have been armed again and refused again, spinning until the timeout. Now it is reported once as an error (event::is_error, which was false for every event before) in the directions the registration reports, so whoever reads or writes the source meets the refusal. No laterselectarms it again. A refused watch on the selector's own event fails theselectby name.mio_registers_while_parked. The main thread polls with no timeout. A second thread waits until the kernel's roster shows the main thread blocked, past the flag it raises just before polling. It then registers aWakerthrough a cloned registry and wakes it. The poll must return that waker's token. If it returns without it, or never returns, the second thread ends the process by name past a 10 s ceiling, so the shared boot is not wedged. The poll is parked with nothing else registered: aPollholds oneWaker(upstream's debug assertion), and thetestcasesboot runs no netstack, so a waker is the one source it can make ready.mio_waker_coalesces(A poll belongs to the handle it was made through, and a ring to its one submitter #1261) is part of thetestcasesboot of main thatissues/nothing-fails-when-a-devices-release-or-close-stops-answering-its-polls.mdalready owes whole, with "every other shared member that polls passing". That boot now also holdsmio_registers_while_parked.issues/mios-selector-fails-every-select-past-63-registrations.md, found while reading and not fixed here. The ring has 64 submission slots and eachselectqueues every watch in one submit, so past 63 registrations, the watch on the change event among the 64, everyselectfails. A TCP stream registered for both directions takes two registrations, so a tokio runtime with its waker and one listener fails at its 31st stream.issues/a-fork-registered-in-local-toml-dirties-the-nested-lockfiles-and-stops-the-build.md, met while building this and recorded, not fixed. A fork registered in.cargo/local.toml, as the implementer rules say, writes[[patch.unused]]into therust/andsdk/libclockfiles, and the build then refusesrust/src/bootstrapas dirty.Why a guest test
The selector is ToyOS-only code over the kernel's inbox. What it must establish is the kernel's parked submit waking for the selector's own event, and two threads ordered by the kernel's own report of one of them blocked. No host build has that. QEMU establishes order and completion, which is the whole claim. The test is a member of the
testcasesshared boot, x86-64 only, as every member is. It runs on the guest rowtestcases_members_exit_0(QEMU x86-64 q35, KVM on the guest runner) and on the T14'stestcasesboot.Checks
Base: main
3c5c55584. Head:924b72173, on a macOS arm64 host unless it says otherwise.testcases_members_exit_0at924b72173: run 43163983980. Queued; its verdict is added here.mio-registration-wakes-selector-control-b1at95301017f. It is the head with the fork ated29321vendored undertests/guest/rust-tests/mio-control, in whichSelectorInner::changesignals nothing, and the guest crate takes mio from that copy. The empty poll still waits. Vendoring keeps the fork'stoyosbranch the only branch on the fork. Run 43163987153, dispatched beside the head's, was red as predicted:1 of testcases's 242 member(s) did not exit 0: test_rs_mio_registers_while_parked, exit 1, witha waker registered and woken while the poll was parked was not its event within 10s10 s after the member started. Every other member exited 0. So the change signal is what delivers the late registration; the empty-poll wait alone does not. The control branch was deleted after the run, and its two patches are in the evidence.mio_registers_while_parkedandmio_waker_coalescesbuilt asbuild_toyos_binbuilds them (cargo build --target x86_64-unknown-toyos --profile toyos, sysroot0dd1ef87d639f652, the toolchain's clang). Exit 0, with no warnings, at924b72173, with mio fetched from the fork ated29321. The fork was type-checked for the target before the push, also exit 0.924b72173:cargo test --lib -- sourcegatepasses, 9 tests, over the new and changed records.cargo test -p toyos-net-nodepasses; its host tests take mio from the fork ated29321, and their unix selector is unchanged.f9e3135d2:cargo run -- --ci hostexit 1, 1 of 123 steps red. That one isthe_caller_s_environment_reaches_neither_the_build_nor_the_key,issues/the-llvm-environment-test-breaks-git-on-macos.md, red on every macOS host, in a file this branch does not touch.cargo run -- --build-onlyexit 0. What changed since is the fork pin and three records; the two host checks above cover what those can change.f48024a32, green with 242 members. The whole-change control 43151340044, with the fork pinned back to4108eff, was red ontest_rs_mio_registers_while_parkedalone, through the base selector's empty-poll early return.Evidence on
pr-1276: round 2's checks and the control's mutation, the.cargo/local.tomlreproduction, and round 1's host logs and guest runs.Risks
event_signalwhile aselectis parked, and oneevent_takeat the next copy. A register made while noselectwaits costs nothing more.Pollwith no timeout to return at once now waits. That is upstream's meaning, and tokio always registers its waker first.reregistermakes it a registration again.Owed on metal
Nothing new. The
testcasesboot of main is already owed on the T14 (see above), andmio_registers_while_parkedis a member of it.🤖 Generated with Claude Code