Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -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.

This file was deleted.

24 changes: 24 additions & 0 deletions issues/mios-selector-fails-every-select-past-63-registrations.md
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 1 addition & 1 deletion tests/guest/rust-tests/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion tests/guest/rust-tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
64 changes: 64 additions & 0 deletions tests/guest/rust-tests/src/bin/mio_registers_while_parked.rs
Original file line number Diff line number Diff line change
@@ -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(&registry, 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<Token> = 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");
}
Loading