diff --git a/Cargo.lock b/Cargo.lock index 648b9e6e73..08a130960c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1760,7 +1760,7 @@ dependencies = [ [[package]] name = "mio" version = "1.2.0" -source = "git+https://github.com/ToyOSOrg/mio?branch=toyos#4108eff31632d3b8ed7e0871bec6e7842aac78aa" +source = "git+https://github.com/ToyOSOrg/mio?branch=toyos#ed29321759dfc6c981926cb0c59f7deb9936b12c" dependencies = [ "libc", "toyos-abi", diff --git a/issues/a-fork-registered-in-local-toml-dirties-the-nested-lockfiles-and-stops-the-build.md b/issues/a-fork-registered-in-local-toml-dirties-the-nested-lockfiles-and-stops-the-build.md new file mode 100644 index 0000000000..4752ca1e4a --- /dev/null +++ b/issues/a-fork-registered-in-local-toml-dirties-the-nested-lockfiles-and-stops-the-build.md @@ -0,0 +1,40 @@ +--- +status: open +kind: tooling +opened: 2026-11-12 +--- + +# A fork registered in `.cargo/local.toml` dirties the nested lockfiles, and the build then stops + +`.cargo/config.toml` includes the untracked `.cargo/local.toml`. That is where +an implementer registers a fork clone under edit, as a `[patch]` of its git +source and of crates-io (`.claude/agents/implementer.md`, "A fork"). Cargo +reads that configuration for every invocation under the checkout, including +those in workspaces the patch has nothing to do with: `rust/library`, +`rust/src/bootstrap` and `sdk/libc`. Each of those resolves without the +patched crate and writes `[[patch.unused]]` entries for it into its own +`Cargo.lock`. `build/llvm.rs`'s `refuse_uncommitted_bootstrap` then finds +`rust/src/bootstrap` holding changes no commit does, and the build panics +there. So a checkout with a fork registered as the instructions say builds no +image and no sysroot until the registration is removed and the nested +lockfiles are restored. Meanwhile `git status` shows `rust` and +`sdk/libc/Cargo.lock` modified. + +Seen at `3c5c55584` with the mio fork registered: +- a sysroot build wrote the entries into all three lockfiles; +- `cargo run -- --build-only` then panicked at `build/llvm.rs:329`. + +Reproduced at `f9e3135d2` without a sysroot build: +- `cargo metadata` in `rust/src/bootstrap` and in `sdk/libc` writes the entries; +- `cargo run -- --build-only` exits 101 at the same assertion + (https://github.com/ToyOSOrg/evidence/tree/a3ad7b166e2d80021bf2d633adcd704096c66f17/pr-1276/local-toml). + +Building one crate with the patch given as `cargo --config` leaves every other +lockfile alone. + +**Exit**: a fork registered as the instructions say reaches the builds that +consume it, and no lockfile outside those workspaces changes. Alternatively, +the instructions name a registration that has that property. + +**Owner**: the orchestrator, for the build system and the implementer's fork +rules. diff --git a/issues/mios-selector-arms-no-registration-made-while-another-thread-selects.md b/issues/mios-selector-arms-no-registration-made-while-another-thread-selects.md deleted file mode 100644 index a59cf7b6dc..0000000000 --- a/issues/mios-selector-arms-no-registration-made-while-another-thread-selects.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -status: open -kind: defect -severity: [hang] -opened: 2026-11-11 ---- - -# mio's selector arms no registration made while another thread selects - -The mio fork's ToyOS selector (`src/sys/toyos/selector.rs`) 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 in `inbox_submit`. A -registration that another thread makes while that `select` is parked is not in -the copy. Its handle gets no watch until the parked `select` returns for some -other reason. With no timeout and nothing else ready, the `select` waits for -good, while the new source's event happens and is never reported. Upstream -mio's `Registry::try_clone` documents a registry that may be cloned to other -threads and registers with the same `Poll`, which is the case this misses. - -The registering thread cannot arm the watch itself: a ring has one submitter, -so its `inbox_submit` while the `select` is parked is refused `Busy`. - -Found by reading, not exercised. - -**Exit**: a registration made while another thread is parked in `select` is -watched before that `select` would otherwise return, for example by the -registration waking the selector so that it arms the watch again; a test -registers a source from a second thread while the first is parked in a -`select` with no timeout, makes the source ready, and the `select` returns its -event. - -- **Owed on metal:** the selector's generation drop (#1261, fork `4108eff`) in - a ToyOS binary. - - Row: `mio_waker_coalesces`, a member of the `testcases` boot of main. - - Bench: the T14. - - Main must show it passing, built from mio `4108eff`. diff --git a/issues/mios-selector-fails-every-select-past-63-registrations.md b/issues/mios-selector-fails-every-select-past-63-registrations.md new file mode 100644 index 0000000000..5a6f621107 --- /dev/null +++ b/issues/mios-selector-fails-every-select-past-63-registrations.md @@ -0,0 +1,24 @@ +--- +status: open +kind: defect +opened: 2026-11-12 +--- + +# mio's selector fails every select past 63 registrations + +The mio fork's ToyOS selector (`src/sys/toyos/selector.rs`) makes its ring with +64 submission slots, and every `select` queues one watch per registration and +one for a change to them before it hands them over in one `inbox_submit`. Its +`queue` does not look at the ring's head, so a 65th watch overwrites the first +before the kernel has taken it, and the kernel refuses a submit of more than +its ring holds `InvalidArgument` (`kernel/src/inbox/mod.rs`, +`submit_submissions`). So while more than 63 registrations stand, every +`select` fails. A TCP stream registered for both directions takes two, its +receive bell and its bell (`src/net/tcp/toyos_stream.rs`, `register`), so a +tokio runtime with its waker and one listener fails at its 31st stream. + +Found by reading, not exercised. + +**Exit**: a `select` arms every registration however many there are, in +submissions the ring holds, and a test registers more sources than the ring has +slots and sees each one's event. diff --git a/tests/guest/rust-tests/Cargo.lock b/tests/guest/rust-tests/Cargo.lock index 244a6db9e8..71a6eafb9c 100644 --- a/tests/guest/rust-tests/Cargo.lock +++ b/tests/guest/rust-tests/Cargo.lock @@ -465,7 +465,7 @@ dependencies = [ [[package]] name = "mio" version = "1.2.0" -source = "git+https://github.com/ToyOSOrg/mio?branch=toyos#4108eff31632d3b8ed7e0871bec6e7842aac78aa" +source = "git+https://github.com/ToyOSOrg/mio?branch=toyos#ed29321759dfc6c981926cb0c59f7deb9936b12c" dependencies = [ "libc", "log", diff --git a/tests/guest/rust-tests/Cargo.toml b/tests/guest/rust-tests/Cargo.toml index ee37ce8827..caadd33706 100644 --- a/tests/guest/rust-tests/Cargo.toml +++ b/tests/guest/rust-tests/Cargo.toml @@ -32,7 +32,7 @@ toyos-pci = { path = "../../../kernel/pci" } libloading = { git = "https://github.com/ToyOSOrg/rust_libloading", branch = "toyos-sdk-0.12" } cpal = { git = "https://github.com/ToyOSOrg/cpal", branch = "toyos-0.18.0" } # Its ToyOS selector and waker, which no program in the image builds, for -# `mio_waker_coalesces`. +# `mio_waker_coalesces` and `mio_registers_while_parked`. mio = { git = "https://github.com/ToyOSOrg/mio", branch = "toyos", features = ["os-poll"] } memmap2 = "0.9" # `ring`'s primitives, for `ring_kat`'s known answers and as `crypto_parity`'s diff --git a/tests/guest/rust-tests/src/bin/mio_registers_while_parked.rs b/tests/guest/rust-tests/src/bin/mio_registers_while_parked.rs new file mode 100644 index 0000000000..685fae2182 --- /dev/null +++ b/tests/guest/rust-tests/src/bin/mio_registers_while_parked.rs @@ -0,0 +1,64 @@ +//! mio's ToyOS selector watches a registration another thread makes while a +//! poll is parked: a waker registered from a second thread once the first is +//! parked in a poll with no timeout, then woken, is the event that poll +//! returns. +//! +//! The poll is parked with nothing else registered: a `Poll` holds one waker, +//! and the waker is the one source this boot can make ready, having no +//! netstack. Red where the poll returns without the waker's event, and where +//! it never returns: the second thread then ends the process past [`CEILING`], +//! by name. + +#[path = "../roster.rs"] +mod roster; + +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::mpsc; +use std::time::Duration; + +use mio::{Events, Poll, Token, Waker}; +use toyos::endow::{Endowments, SYSCAP_LABEL}; +use toyos::syscap::SysCap; + +const LATE: Token = Token(7); +/// A hang's ceiling: the waker is woken once the poll is parked, and the poll +/// is owed nothing else. +const CEILING: Duration = Duration::from_secs(10); + +/// Set just before the main thread polls. +static POLLING: AtomicBool = AtomicBool::new(false); + +fn main() { + let cap: SysCap = Endowments::get() + .take(SYSCAP_LABEL) + .expect("test-runner endows every binary it spawns a system capability"); + let mut poll = Poll::new().expect("a poll"); + let registry = poll.registry().try_clone().expect("a registry for the second thread"); + let (returned, on_return) = mpsc::channel::<()>(); + + let late = std::thread::spawn(move || { + // Past `POLLING`, until its poll returns, the main thread blocks only + // inside the poll, so a registration made from here lands after the + // poll's copy of the registrations. + roster::await_true(|| { + POLLING.load(Ordering::SeqCst) + && roster::my_threads(&cap).iter().any(|&(is_thread, state)| !is_thread && state == roster::BLOCKED) + }); + let waker = Waker::new(®istry, LATE).expect("a waker registered while the poll is parked"); + waker.wake().expect("a wake"); + if on_return.recv_timeout(CEILING).is_err() { + eprintln!("mio_registers_while_parked: a waker registered and woken while the poll was parked was not its event within {CEILING:?}"); + std::process::exit(1); + } + waker + }); + + let mut events = Events::with_capacity(8); + POLLING.store(true, Ordering::SeqCst); + poll.poll(&mut events, None).expect("a poll"); + returned.send(()).expect("the registering thread waits for the poll's return"); + let tokens: Vec = events.iter().map(|event| event.token()).collect(); + let _waker = late.join().expect("the registering thread"); + assert_eq!(tokens, [LATE], "a poll parked while another thread registered and woke a waker"); + println!("mio_registers_while_parked: a waker registered while the poll was parked was its event"); +}