Skip to content
Merged
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
15 changes: 10 additions & 5 deletions Cargo.lock

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

41 changes: 41 additions & 0 deletions build/ci.rs
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,19 @@ const fn red(
Control { krate, feature, test, must_red: true, verdicts }
}

/// Every case of the session model a registration reds without its arm, its
/// ring, or both.
const REGISTERED: &[Verdict] = &[
Fails("a_source_registered_after_bytes_arrived_gets_its_first_event"),
Fails("a_source_registered_as_bytes_arrive_gets_its_first_event"),
Fails("a_source_registered_after_the_end_gets_its_first_event"),
Fails("a_source_registered_as_the_end_arrives_gets_its_first_event"),
Fails("a_source_registered_with_room_gets_its_first_event"),
Fails("a_source_registered_as_room_is_released_gets_its_first_event"),
Fails("a_source_registered_over_a_full_ring_after_a_reset_gets_its_first_event"),
Fails("a_source_registered_over_a_full_ring_as_a_reset_arrives_gets_its_first_event"),
];

/// Every negative control a model crate declares; `build/build.rs`'s
/// `every_model_control_is_run` holds this against the manifests.
pub(crate) const CONTROLS: &[Control] = &[
Expand Down Expand Up @@ -665,6 +678,34 @@ pub(crate) const CONTROLS: &[Control] = &[
red(TRANSPORT, "want-relaxed", Some("loom"), &[
Fails("a_consumer_that_looked_again_is_rung_for_bytes_it_missed"),
Fails("a_producer_that_looked_again_is_rung_for_room_it_missed"),
Fails("two_sleepers_each_wake_when_room_is_released_first"),
Fails("two_sleepers_each_wake_when_data_is_published_first"),
Fails("a_poll_beside_a_blocking_read_is_answered_for_the_bytes_it_left"),
Fails("a_poll_beside_a_blocking_read_is_answered_for_the_end_it_left"),
Fails("a_poll_beside_a_blocking_write_is_answered_for_the_room_it_left"),
Fails("a_source_registered_as_bytes_arrive_gets_its_first_event"),
Fails("a_source_registered_as_the_end_arrives_gets_its_first_event"),
Fails("a_source_registered_as_room_is_released_gets_its_first_event"),
Fails("a_source_registered_over_a_full_ring_as_a_reset_arrives_gets_its_first_event"),
]),
// A session's lost wakes: a reader and a writer on one doorbell, a take
// not passed on, a registration that neither arms nor rings, and a reset
// a look that arms does not see.
red(TRANSPORT, "one-doorbell", Some("loom"), &[
Fails("two_sleepers_each_wake_when_room_is_released_first"),
Fails("two_sleepers_each_wake_when_data_is_published_first"),
]),
red(TRANSPORT, "no-pass-on", Some("loom"), &[
Fails("a_poll_beside_a_blocking_read_is_answered_for_the_bytes_it_left"),
Fails("a_poll_beside_a_blocking_read_is_answered_for_the_end_it_left"),
Fails("a_poll_beside_a_blocking_write_is_answered_for_the_room_it_left"),
]),
red(TRANSPORT, "register-no-arm", Some("loom"), REGISTERED),
red(TRANSPORT, "register-no-ring", Some("loom"), REGISTERED),
red(TRANSPORT, "register-neither", Some("loom"), REGISTERED),
red(TRANSPORT, "head-only", Some("loom"), &[
Fails("a_source_registered_over_a_full_ring_after_a_reset_gets_its_first_event"),
Fails("a_source_registered_over_a_full_ring_as_a_reset_arrives_gets_its_first_event"),
]),
red(TRANSPORT, "end-keeps-inflight", None, &[Fails(
"inflight::tests::an_end_answers_every_tag_once_and_a_late_completion_nothing",
Expand Down
2 changes: 2 additions & 0 deletions build/sdkversion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ pub struct Crate {
/// The published crates, in the order a publisher must take them.
pub const PUBLISHED: &[Crate] = &[
Crate { name: "toyos-abi", dir: "sdk/abi" },
Crate { name: "toyos-untrusted", dir: "lib/untrusted" },
Crate { name: "toyos-transport", dir: "lib/blockring/transport" },
Crate { name: "toyos-keymap", dir: "sdk/keymap" },
Crate { name: "toyos-font", dir: "sdk/font" },
Crate { name: "toyos", dir: "sdk/toyos" },
Expand Down
21 changes: 15 additions & 6 deletions build/sysroot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,14 @@ use crate::toolchain::{self, host_triple, GuestTarget, Owner, Role, GUEST_TARGET
use whole_toolchain::{whole, Whole};

/// The per-worktree sources that end up inside a sysroot: std links the
/// `toyos-abi` and `toyos` [`std_patch`] hands it and compiles its ToyOS
/// backend from `sdk/std`, and
/// `toyos-abi`, `toyos`, `toyos-transport` and `toyos-untrusted` [`std_patch`]
/// hands it and compiles its ToyOS backend from `sdk/std`, and
/// `libtoyos_c.a` is `sdk/libc` with `toyos-elf` and `toyos-osrelease`.
pub const SYSROOT_SOURCES: [&str; 7] = [
pub const SYSROOT_SOURCES: [&str; 9] = [
"sdk/abi/src",
"sdk/toyos/src",
"lib/blockring/transport/src",
"lib/untrusted/src",
"sdk/std",
"lib/elf/src",
"sdk/osrelease/src",
Expand All @@ -80,9 +82,11 @@ pub const SYSROOT_SOURCES: [&str; 7] = [

/// Their manifests, and the lockfile and cargo configuration libc is built
/// under: the features, versions and flags of the same build.
pub(crate) const SYSROOT_MANIFESTS: [&str; 7] = [
pub(crate) const SYSROOT_MANIFESTS: [&str; 9] = [
"sdk/abi/Cargo.toml",
"sdk/toyos/Cargo.toml",
"lib/blockring/transport/Cargo.toml",
"lib/untrusted/Cargo.toml",
"lib/elf/Cargo.toml",
"sdk/osrelease/Cargo.toml",
"sdk/libc/Cargo.toml",
Expand All @@ -93,7 +97,8 @@ pub(crate) const SYSROOT_MANIFESTS: [&str; 7] = [
/// Of [`SYSROOT_MANIFESTS`], the crates std names by version, which std's
/// lockfile resolves with the fork's own through [`std_patch`]: what of a
/// worktree can move a freestanding target's dependency versions.
const STD_MANIFESTS: [&str; 2] = ["sdk/abi/Cargo.toml", "sdk/toyos/Cargo.toml"];
const STD_MANIFESTS: [&str; 4] =
["sdk/abi/Cargo.toml", "sdk/toyos/Cargo.toml", "lib/blockring/transport/Cargo.toml", "lib/untrusted/Cargo.toml"];

/// One of the crates std names by version, in a worktree.
pub(crate) struct StdCrate {
Expand Down Expand Up @@ -1274,7 +1279,7 @@ pub(crate) mod tests {
/// The manifests [`std_crates`] reads, in a worktree at `root` that has
/// no other source: what a bootstrap run is handed std's patch from.
pub(crate) fn std_manifests(root: &Path) {
for (manifest, name) in STD_MANIFESTS.into_iter().zip(["toyos-abi", "toyos"]) {
for (manifest, name) in STD_MANIFESTS.into_iter().zip(["toyos-abi", "toyos", "toyos-transport", "toyos-untrusted"]) {
write(&root.join(manifest), &format!("[package]\nname = \"{name}\"\n"));
}
}
Expand Down Expand Up @@ -1315,6 +1320,10 @@ pub(crate) mod tests {
format!("patch.crates-io.toyos-abi.path=\"{at}/sdk/abi\""),
"--config".to_string(),
format!("patch.crates-io.toyos.path=\"{at}/sdk/toyos\""),
"--config".to_string(),
format!("patch.crates-io.toyos-transport.path=\"{at}/lib/blockring/transport\""),
"--config".to_string(),
format!("patch.crates-io.toyos-untrusted.path=\"{at}/lib/untrusted\""),
],
);
for name in ["work tree", "work\"tree", "work\\tree", "work\ttree"] {
Expand Down
4 changes: 3 additions & 1 deletion build/toolchain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -825,8 +825,10 @@ mod tests {
let registry = |name: &str, version: &str| {
format!("/home/runner/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/{name}-{version}/src/lib.rs")
};
let rest = format!("{} {}", ours("lib/blockring/transport"), ours("lib/untrusted"));
let built_from = |abi: &str, toyos: &str| {
fs::write(built.join("dist/libstd.d"), format!("{}/dist/libstd.rlib: {abi} {toyos}\n", built.display())).unwrap();
fs::write(built.join("dist/libstd.d"), format!("{}/dist/libstd.rlib: {abi} {toyos} {rest}\n", built.display()))
.unwrap();
std::panic::catch_unwind(|| assert_std_built_from(&root, &built))
.err()
.map(|refused| *refused.downcast::<String>().expect("a formatted refusal"))
Expand Down
27 changes: 27 additions & 0 deletions issues/a-stream-commits-eight-mebibytes-and-a-process-holds-128.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
status: open
kind: defect
opened: 2026-10-14
---

# A stream commits 8 MiB at its connect, and a process holds at most 128

Every connect and accept makes its region whole before a byte moves:
`toyos::net::stream::REGION_BYTES`, four 2 MiB pages from `SYS_SHM_CREATE`,
physically contiguous, where the pipes it replaced took a page on first write
and [tcp]'s rings grew as text was held. A program that opens many short
connections pays 8 MiB for each, and a fragmented machine can refuse a
connect a pipe would have taken. #863 accepted the cost with the design.

The process's ends are a fixed table, `MAX_STREAMS` = 128 streams and 256
handles of their connections (`sdk/toyos/src/net/stream.rs`), since `toyos`
allocates nothing: past it a connect or an accept is refused
`ResourceExhausted` before anything is made. 128 is libc's `MAX_SOCKETS`, an
estimate no measurement set, and a new limit for a std program.

**Exit condition**: regions whose pages are allocated as the rings fill, so a
stream commits what it holds; and the table grown on demand, or its bound
read off a measured program's need.

**Owner**: the orchestrator, who stages
`issues/netstack-moves-every-byte-through-two-kernel-pipes.md`.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,19 @@ kind: defect
opened: 2026-10-09
---

# A stream's failure reaches its client as a reset, whatever it was
# A stream's failure is told its client by name, and only a reset is tested

The pipe ABI tells a stream's client how the stream ended by the order its two
pipes end in (`system/netstack/node/src/streams.rs`'s header), which carries
one bit: the peer's FIN, or a failure. The node knows which failure
(`toyos_net_tcp::Failure`: a reset, R2's timeout, an ICMP error), and std
answers every one `ConnectionReset`, where Linux answers a connection [tcp]
gave up on `ETIMEDOUT` and one an ICMP error ended `EHOSTUNREACH` or
`ENETUNREACH`. libc answers every one `ECONNRESET`
(`sdk/libc/src/streamend.rs`).
The receive ring's `end` word carries which failure ended a stream:
`RESET`, `TIMED_OUT` or `UNREACHABLE` (`sdk/toyos/src/net/stream.rs`), which
the node stores from [tcp]'s `Failure` (`system/netstack/node/src/streams.rs`),
std answers `ConnectionReset`, `TimedOut` or `HostUnreachable`
(`sdk/std/sys/net/connection.rs`) and libc `ECONNRESET`, `ETIMEDOUT` or
`EHOSTUNREACH` (`sdk/libc/src/socket.rs`). The node's host tests read a
peer's reset as `RESET`; nothing reads the other two words, and no guest test
reads any of them through std or libc.

A pipe's end carries no word, and every other carrier costs what this one does
not: a third pipe or a kept request connection is a 2 MiB page each
(`kernel/src/pipe.rs`, `PIPE_SIZE`), and a reason the node keeps for a client to
ask about after both pipes are gone is kept for ever for a client that died,
since nothing then tells the node it left. The clean carrier is a word the
writer of a pipe sets as it lets its end go and the reader reads at the end,
which is the kernel's to add.

The same word set would answer a connect that an ICMP error ended, which the
move's netstack answers `ERR_OTHER` for want of a word.

Read from the code, not measured.

**Exit condition**: a pipe's end carries its reason, the node writes
`Failure`'s through it, and std and libc answer each as Linux does; a node
host test reads each failure's word, and a guest test whose peer is
unreachable reads it as unreachable.
**Exit condition**: a node host test reads a connection [tcp] gave up on as
`TIMED_OUT` and one an ICMP error ended as `UNREACHABLE`, and a guest test
whose peer is unreachable reads it as unreachable through std.

**Owner**: whoever holds `issues/toyos-has-its-own-network-stack.md`.
Loading