Skip to content

The node keeps the pipe order std reads a stream's end by: a FIN after a shutdown reads as a FIN, a failure as a reset - #803

Merged
Japabu merged 5 commits into
mainfrom
wt/toyos-finreset
Oct 9, 2026
Merged

Japabu merged 5 commits into
mainfrom
wt/toyos-finreset

Conversation

@Japabu

@Japabu Japabu commented Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator

The node keeps the pipe order std reads a stream's end by: a client that shut its sending half down reads its peer's FIN as an end, and a failure still reads as a reset.

The defect

A stream reaches its client as two pipes; the receive pipe's end says only that it ended. std's ended (sdk/std/sys/net/connection.rs) reads the kind off the send pipe: no reader behind a receive pipe at its end is a failure. The node broke that order on an orderly end: it let the client's send pipe go as soon as the client's FIN was queued (streams.rs, the read == 0 arm) and again when the connection reached CLOSED or TIME-WAIT. So shutdown(Write) then read to end answered ConnectionReset (measured on the move, #801, both cards, 4,194,304 bytes). It also broke it for failures in a narrower way: a failure dropped the receive pipe first and the send pipe later in the same pass, and a stream dropped whole dropped to_client first by declaration order, so a client reading the end between the two could read a reset as a FIN.

Decision: the node keeps the order; std and the ABI are unchanged

Weighed, from the code:

  • A typed end record through the pipe ABI (orderly, reset, timed out, unreachable) is the exact answer, but has no clean carrier today. A pipe's end carries no word. A third pipe or a request connection kept open is a 2 MiB page each (kernel/src/pipe.rs, PIPE_SIZE; a connection is two pipes, kernel/src/object/service.rs). A reason the node keeps for the client to ask about after both pipes are gone is kept for ever for a client that died: with both ends let go nothing tells the node it left. The clean carrier is a word the writer sets on its pipe end as it lets go, which is a kernel change outside this fence. Recorded: issues/a-streams-failure-reaches-its-client-as-a-reset-whatever-it-was.md, on the track; connect's ICMP refusal (ERR_OTHER on the move) belongs to the same word set and is named there, not done here.
  • Correcting std's inference cannot work: the pipes carry no more than which end is gone and in what order. Reading a send pipe std shut itself as "orderly" would read a peer's RST after our FIN as a clean end, a hidden loss.
  • The node keeps the send pipe until both directions are done makes the existing one bit exact at zero cost. Chosen; the move's review (The shipped netstack runs ToyOS's own network stack and smoltcp leaves the tree; libc reads a stream's end as std does #801, BLOCKER 1) reached the same reading independently.

What the node does now (userland/netstack/node/src/streams.rs, its header states the contract):

  • A from-client end whose last byte and FIN are queued is kept, read no more ([tcp] has no room once the FIN is queued), its writer watched (Watch::writer), and let go when its writer leaves, or when the client closes the stream or its reader is gone (nobody then reads the end: Stream::left).
  • A failure lets from_client go before to_client: in the pass's failure arm, and in any whole drop (pipe_broken, a broken pipe's abort), from_client being declared first.
  • Reaching CLOSED or TIME-WAIT in order no longer ends the send pipe; a failure does (status.failure). An orderly end of the connection comes after the client's FIN, so its writing was over already.
  • A kept end is off the cut's clock (OWNERLESS_LIFE): it holds no byte to send. It holds its place for as long as its client holds the stream, as any stream does.

libc is not changed here: its reading of an end goes with the move

Round 1 also taught libc to read an end's kind as std does (a zero-byte write into the send pipe at a read of 0; ECONNRESET where it finds no reader; EPIPE after SHUT_WR, 0 after SHUT_RD). That reads right only on the node. On main the shipped netstack is the smoltcp shell, whose bridge_piped (userland/netstack/src/main.rs) closes both pipes in one pass once a connection is done in both directions, so a client that shut its sending half down finds the send pipe gone at the peer's FIN. Measured (below): with that libc, a C client's shutdown(SHUT_WR) then recv to the end reads ECONNRESET on main; with main's libc it reads 0. So the libc change is removed from this branch, userland/libc and tests/libc-arch are main's, and the removed diff with its host test is posted on this pull request (comment 6082081208) for the move to carry with the guest C case issues/a-netstack-client-cannot-tell-a-reset-from-the-peers-fin.md's exit now names.

What main does at a stream's end, measured

The instrument: one std program (stream_ends) and one C program (shut_first.c) run in a guest on tests/netcase against a peer on this host, and the same std source compiled into the harness and run against the same peer on this Mac's TCP, the two reports compared line for line. The peer is driven by the client's own steps, so no line depends on timing. The case is not landed (below); its patch is in comments 6081887880 (round 1) and 6082065591 (this round's shut_first row and C job). Runs: round 1's two (one at the base, whose log was lost with a wiped scratch directory, one at 1e303178b, comment 6081887880) and this round's two at 96f48560f, one with round 1's libc (comment 6082065591, exit 1) and one with main's libc (comment 6082078719, exit 1).

case this Mac's TCP main's netstack on smoltcp, std
write 1 MiB, shutdown(Write), read to end 1,048,576 back, then Ok(0) 65,536 back (round 1) or 65,535 (this round), then Err(ConnectionReset)
shut_first: shut down with nothing pending, peer then sends 4 bytes and FIN 4 bytes, then Ok(0) 4 bytes, then Err(ConnectionReset), both runs
peer FINs first, client reads then writes Ok(0), then a write Ok(5) the same, four runs
peer resets mid-stream Err(ConnectionReset) the same, four runs
peer resets after the client's half-close Err(ConnectionReset) the same, four runs
peer sends 3 bytes and FIN, client then shuts down and reads Ok(0), then Ok(0) Ok(0), then Ok(0) in round 1's two runs and Err(ConnectionReset) in this round's two: the race between bridge_piped's two closes

C, shut_first: recv -1, errno ECONNRESET with round 1's libc; recv 0 with main's.

So on main, std reads the end of a half-closed stream as a reset whether or not a byte was cut: that is bridge_piped's, recorded in issues/a-netstack-client-cannot-tell-a-reset-from-the-peers-fin.md, and the cut count is issues/a-shutdown-of-the-sending-half-drops-what-the-send-pipe-still-holds.md's. Both close at the move, whose netstack runs the node. The smoltcp shell is not touched here: the owner's ruling is that nothing new is built on it.

Why no guest test lands here

tests/netcase boots netstack on smoltcp on main, where the case is red for the two defects above, which only the move closes, and the owner's ruling is that no new test is built on smoltcp. The case is posted for the move instead; on the move it is also this change's guest negative control (this change reverted, the half-close row reads ConnectionReset, as #801 measured). The node's host tests are the tier that reaches the rule itself: the pipe order is the node's, the node has a host build with faked pipe ends, and every rule here is red there under mutation.

What the move's shell must do with this: nothing new in the ABI. It already lets a pipe go when the node drops it and reports Node::pipe_gone(id, PipeEnd::FromClient) when the kernel says the send pipe's writer left; that watch must be asked whenever Watch::writer says so, which is now also true of a kept end after the FIN (writer: from && (!done_writing || fin_queued)). Its netstack_streams can read to the stream's end again.

Recorded here

  • issues/a-netstack-client-cannot-tell-a-reset-from-the-peers-fin.md: what main does, by the measurement above; the race was met; libc reads no end's kind; exit, on the move, the std case and the guest C case with the libc change, red under each of round 1's review's three libc mutations.
  • issues/a-shutdown-of-the-sending-half-drops-what-the-send-pipe-still-holds.md: the count, and that the reset after it is the other issue's.
  • issues/a-streams-failure-reaches-its-client-as-a-reset-whatever-it-was.md: one bit carries no failure's kind.
  • issues/libc-answers-epipe-and-raises-no-sigpipe.md: libc's write answers EPIPE and raises no SIGPIPE (libc has no signals), as if MSG_NOSIGNAL were always given; whether it should is a libc design decision, its exit that decision and a guest C case on both paths.
  • The track's line in issues/toyos-has-its-own-network-stack.md.

Checks

High-risk (the pipe order between netstack and every program, and std's reading of it). Every row at 052679491 (this round's head, main at 558283168 merged in), each log posted on this pull request as it was made except --ci host and the guest suite, whose logs were too long for a comment and stay on the development machine (their exits and lines are quoted here).

check exit
cargo test -p toyos-net-node --no-fail-fast 0: 157 tests, 1 new this round (comment 6082190310)
cargo test -p toyos-net-tcp --no-fail-fast ([tcp], unchanged) 0: 385 tests (comment 6082190310)
cargo test -p toyos-libc-copies --no-fail-fast 0: 46 tests, main's count (comment 6082190310)
cargo run -- --clippy 0: clippy: 24 invocations clean (comment 6082190310)
cargo run -- --ci host 0: Host: 78 step(s), all green (comment 6082273747)
cargo run -- --build-only 0 (comment 6082291835)
cargo test --test toyos-build -- netstack_socket_churn libc_sockets 0: 2 passed, 2 total (comment 6082291835)
cargo test, the whole guest suite 0: 38 passed, 38 total; load average 27.64 at its start (comment 6082313797)

Negative control: the node's whole change reverted onto the base (git diff origin/main HEAD -- userland/netstack/node/src/streams.rs, applied reversed as a checked patch, restored after) under this branch's tests: exit 101, red the_peers_fin_after_the_clients_shutdown_leaves_the_send_pipe_to_its_writer, a_shutdown_after_the_peers_fin_keeps_the_send_pipe_past_the_connections_end, a_shutdown_sends_what_the_pipe_held_and_then_the_fin, a_finished_send_pipe_goes_with_its_reader, a_reset_ends_both_pipes_and_the_stream, a_pipe_that_is_no_pipe_resets_its_connection. Green on the base too: a_reset_after_the_clients_shutdown_lets_the_send_pipe_go_first, a_reset_after_the_peers_fin_ends_the_send_pipe and this round's a_writer_that_leaves_after_the_shutdown_leaves_the_stream_to_its_reader: those orders were right before, and the tests hold that they still are.

Mutations at 052679491, each a checked patch, built, run with --no-fail-fast, reversed, the tree checked clean after; patches, runner and log in comment 6082159813.

mutation exit red
m1: a failure drops to_client before from_client 101 a_reset_ends_both_pipes_and_the_stream, a_reset_after_the_clients_shutdown_lets_the_send_pipe_go_first
m2: to_client declared first again 101 a_pipe_that_is_no_pipe_resets_its_connection
m3: CLOSED/TIME-WAIT ends the send pipe again 101 a_shutdown_after_the_peers_fin_keeps_the_send_pipe_past_the_connections_end, the_peers_fin_after_the_clients_shutdown_leaves_the_send_pipe_to_its_writer
m4: the FIN drops the send pipe again 101 a_finished_send_pipe_goes_with_its_reader, a_shutdown_after_the_peers_fin_keeps_the_send_pipe_past_the_connections_end, a_shutdown_sends_what_the_pipe_held_and_then_the_fin, the_peers_fin_after_the_clients_shutdown_leaves_the_send_pipe_to_its_writer
m5: a kept end's writer is not watched 101 a_shutdown_sends_what_the_pipe_held_and_then_the_fin, the_peers_fin_after_the_clients_shutdown_leaves_the_send_pipe_to_its_writer
m6: a close keeps the send pipe 101 a_close_sends_what_the_pipe_held_and_then_the_fin, a_close_with_text_unread_resets, a_wake_is_owed_only_for_a_connection_there_is_a_place_for
m7: a kept end is on the cut's clock 101 the_peers_fin_after_the_clients_shutdown_leaves_the_send_pipe_to_its_writer (cut at 100 s past TIME-WAIT)
m8: a reader's leaving keeps the send pipe 101 a_finished_send_pipe_goes_with_its_reader
m9: a finished send pipe's writer leaving takes the whole stream 101 a_writer_that_leaves_after_the_shutdown_leaves_the_stream_to_its_reader
m10: a finished send pipe's writer leaving is not heard 101 a_writer_that_leaves_after_the_shutdown_leaves_the_stream_to_its_reader, a_shutdown_after_the_peers_fin_keeps_the_send_pipe_past_the_connections_end, the_peers_fin_after_the_clients_shutdown_leaves_the_send_pipe_to_its_writer

Independent oracles: this host's TCP running the same program against the same peer (the table above, macOS); RFC 9293 §3.6 and §3.10.7.4 for the orders the node tests script. Not yet: Linux, and the guest case on the node, which is the move's.

Unsure

  • A kept end holds a place until its client drops the stream, where the node used to free the place at the FIN. A client that half-closes and holds its stream open holds what any open stream holds.
  • The C oracle row was not run on this Mac's TCP: the C program runs only in the guest, against the std oracle's answer for the same steps.

Net

git diff --shortstat origin/main...HEAD: 7 files, +336 / -49. Production: the node +67 / -21; tests +134 / -5; issues/ the rest.

🤖 Generated with Claude Code

https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C

Known imprecision, corrected in the move (#801), which edits both files: issues/a-netstack-client-cannot-tell-a-reset-from-the-peers-fin.md and issues/a-shutdown-of-the-sending-half-drops-what-the-send-pipe-still-holds.md count one run whose log was lost with the orchestrator's scratch directory; three runs are logged, and every conclusion stands on those three.

Japabu and others added 2 commits October 9, 2026 14:53
… reads as a FIN

A stream reaches its client as two pipes, and the receive pipe's end says
only that it ended. std (`ended`) reads the kind off the send pipe: one
with no reader behind a receive pipe at its end is a failure. The node broke
that order for an orderly end. It let the client's send pipe go as soon as
the client's FIN was queued, and again once the connection reached CLOSED or
TIME-WAIT, so a client that shut its sending half down and read its peer to
the end read `ConnectionReset` where the peer had sent a FIN. A failure
dropped the receive pipe first and the send pipe later in the same pass,
and a stream dropped whole dropped them in declaration order, receive first:
a client that read the end between the two read a reset as a FIN.

The node now keeps a from-client end whose last byte and FIN are queued,
reads it no more, watches its writer and lets it go when the writer leaves,
or when the client closes the stream or its reader is gone (nobody then
reads the end). A failure lets the send pipe go before the receive pipe, in
the pass and in a whole drop, `from_client` being declared first. The
connection's reaching CLOSED or TIME-WAIT in order no longer ends the send
pipe; only a failure does. A kept end is not on the cut's clock: it holds no
byte to send.

libc read every end as 0 and every refused write as EIO. It now asks the
send pipe what std asks at a read of 0 (`streamend.rs`, host-tested by
`toyos-libc-copies`), answers ECONNRESET for a failure and for a write the
send pipe refuses for its reader's leaving, and keeps `shutdown`'s halves:
a send after SHUT_WR is EPIPE without reaching a pipe the node reads no
more, and a recv after SHUT_RD is 0, as std's are.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C
… end

`a-netstack-client-cannot-tell-a-reset-from-the-peers-fin` described netd's
bridge of the smoltcp era. It now says what holds: the node keeps the order
std and libc read, netstack on smoltcp closes the two pipes in one pass for
every end, and a guest on main read a reset, a reset after a half-close and
both FIN orders as a host's TCP does, in two runs. Its exit is the own
stack's guest test, which only a netstack on the node can run.

`a-shutdown-of-the-sending-half-drops-what-the-send-pipe-still-holds` was
read from the code; it now carries the count measured on main: 65,536 of
1,048,576 bytes back, then `ConnectionReset`.

The order carries one bit, and std and libc answer every failure
ECONNRESET where Linux answers a timeout ETIMEDOUT and an ICMP error its own
word: `a-streams-failure-reaches-its-client-as-a-reset-whatever-it-was`,
whose clean carrier is a word on a pipe's end, the kernel's to add. The
track carries it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C
@Japabu

Japabu commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator Author

Mutation patches and the negative control, each against 1e303178b (git apply, cargo test -p toyos-net-node --no-fail-fast, git apply -R); results in the body's table.

m1-failure-drops-to-client-first

--- a/userland/netstack/node/src/streams.rs
+++ b/userland/netstack/node/src/streams.rs
@@ -256,8 +256,8 @@
                 // The connection's failure: the client's send pipe goes first, so that the end it
                 // reads finds no reader behind it.
                 (Err(Error::Failed(_)), _) => {
-                    self.from_client = None;
                     self.to_client = None;
+                    self.from_client = None;
                 }
                 (Err(Error::WouldBlock), _) => break,
                 (Err(refusal), _) => unreachable!("[tcp] refused the holder of a connection a read: {refusal:?}"),

m2-fields-to-client-first

--- a/userland/netstack/node/src/streams.rs
+++ b/userland/netstack/node/src/streams.rs
@@ -157,8 +157,8 @@
     remote: Ipv4Addr,
     /// Before `to_client`, so a stream dropped whole lets its send pipe go first: the order a
     /// failure owes its client.
-    from_client: Option<Box<dyn FromClient>>,
     to_client: Option<Box<dyn ToClient>>,
+    from_client: Option<Box<dyn FromClient>>,
     /// The connect is not answered yet, and no byte moves.
     connecting: bool,
     /// The connect's, while connecting; then the cut's, once the client can see the stream no

m3-over-drops-send-pipe

--- a/userland/netstack/node/src/streams.rs
+++ b/userland/netstack/node/src/streams.rs
@@ -301,7 +301,7 @@
         // A connection that failed takes no more of the client's bytes: its writes fail rather
         // than fill a pipe nobody drains, and its end reads as a failure. One that ended in order
         // ended after the client's FIN, so its writing was over already.
-        if status.failure.is_some() {
+        if matches!(status.state, State::Closed | State::TimeWait) {
             self.from_client = None;
         }
         // A finished send pipe is kept only for a client that can still read the end and look

m4-fin-drops-send-pipe

--- a/userland/netstack/node/src/streams.rs
+++ b/userland/netstack/node/src/streams.rs
@@ -287,9 +287,7 @@
             if read == 0 {
                 stack.tcp_shutdown_write(now, conn);
                 self.fin_queued = true;
-                if writer_gone {
-                    self.from_client = None;
-                }
+                self.from_client = None;
                 break;
             }
             let Some(bytes) = space.get(..read) else { unreachable!("a pipe read {read} bytes into room for {room}") };

m5-kept-pipe-unwatched

--- a/userland/netstack/node/src/streams.rs
+++ b/userland/netstack/node/src/streams.rs
@@ -470,7 +470,7 @@
             let (from, to) = (stream.from_client.is_some(), stream.to_client.is_some());
             (*id, Watch {
                 readable: from && stream.room && !stream.fin_queued,
-                writer: from && (!stream.done_writing || stream.fin_queued),
+                writer: from && !stream.done_writing,
                 writable: to && stream.held,
                 reader: to,
             })

m6-close-keeps-pipe

--- a/userland/netstack/node/src/streams.rs
+++ b/userland/netstack/node/src/streams.rs
@@ -408,7 +408,6 @@
         }
         stream.to_client = None;
         stream.done_writing = true;
-        stream.left = true;
         self.bridge(now);
     }
 

m7-kept-pipe-timed

--- a/userland/netstack/node/src/streams.rs
+++ b/userland/netstack/node/src/streams.rs
@@ -316,7 +316,7 @@
         }
         // Nothing of the stream reaches its client again, alive or not, and its pipe still holds
         // what may be bytes to send.
-        if self.to_client.is_none() && self.done_writing && !self.fin_queued {
+        if self.to_client.is_none() && self.done_writing {
             if !self.extended {
                 let kept = extended.entry(self.remote).or_insert(0);
                 if *kept < OWNERLESS_PER_PEER {

m8-reader-gone-keeps-pipe

--- a/userland/netstack/node/src/streams.rs
+++ b/userland/netstack/node/src/streams.rs
@@ -438,10 +438,7 @@
     pub fn pipe_gone(&mut self, now: Instant, id: StreamId, end: PipeEnd) {
         let Some(stream) = self.streams.live.get_mut(&id).filter(|stream| !stream.connecting) else { return };
         match end {
-            PipeEnd::ToClient => {
-                stream.to_client = None;
-                stream.left = true;
-            }
+            PipeEnd::ToClient => stream.to_client = None,
             // A finished send pipe was kept for its writer alone.
             PipeEnd::FromClient if stream.fin_queued => stream.from_client = None,
             PipeEnd::FromClient => stream.done_writing = true,

control: git diff origin/main HEAD -- userland/netstack/node/src/streams.rs, applied with -R.

@Japabu

Japabu commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator Author

The guest case for the move (netstack_stream_ends on tests/netcase, Rust job stream_ends), not landed here because main's netstack is smoltcp. Applies to 1e303178b with git apply. Its report there, cargo test --test toyos-build -- netstack_stream_ends, exit 1: the host oracle agreed with the job's expected lines, and the guest, main's smoltcp shell, differs in the second line.

The guest:

half_close: wrote 1048576 bytes, then shut the sending half: Ok
half_close: 65536 bytes came back as sent, then Err(ConnectionReset)
peer_fin_first: the read Ok(0), then a write Ok(5)
reset_mid_stream: 65536 bytes came as sent, the answer Ok(1), then Err(ConnectionReset)
reset_after_half_close: shut the sending half: Ok, then Err(ConnectionReset)
both_close: 3 bytes came as sent, then Ok(0)
both_close: shut the sending half: Ok, then Ok(0)
done

This host's TCP (macOS):

half_close: wrote 1048576 bytes, then shut the sending half: Ok
half_close: 1048576 bytes came back as sent, then Ok(0)
peer_fin_first: the read Ok(0), then a write Ok(5)
reset_mid_stream: 65536 bytes came as sent, the answer Ok(1), then Err(ConnectionReset)
reset_after_half_close: shut the sending half: Ok, then Err(ConnectionReset)
both_close: 3 bytes came as sent, then Ok(0)
both_close: shut the sending half: Ok, then Ok(0)
diff --git a/tests/toyos-rust-tests/src/bin/stream_ends.rs b/tests/toyos-rust-tests/src/bin/stream_ends.rs
new file mode 100644
index 000000000..293c7fccd
--- /dev/null
+++ b/tests/toyos-rust-tests/src/bin/stream_ends.rs
@@ -0,0 +1,22 @@
+//! `stream_ends.rs`'s report, in a guest: one line each, then `done`, and an
+//! exit of 1 where it is not the one a host's TCP gives.
+//!
+//! argv: the address and port of the harness's peer.
+
+#[path = "../stream_ends.rs"]
+mod stream_ends;
+
+fn main() {
+    let args: Vec<String> = std::env::args().collect();
+    let [_, host, port] = &args[..] else { panic!("usage: stream_ends <host> <peer port>") };
+    let port = port.parse().unwrap_or_else(|e| panic!("the port {port}: {e}"));
+    let mut report = Vec::new();
+    stream_ends::run(host, port, |line| {
+        println!("stream_ends: {line}");
+        report.push(line);
+    });
+    println!("stream_ends: done");
+    if report != stream_ends::EXPECTED {
+        std::process::exit(1);
+    }
+}
diff --git a/tests/toyos-rust-tests/src/stream_ends.rs b/tests/toyos-rust-tests/src/stream_ends.rs
new file mode 100644
index 000000000..893d83fce
--- /dev/null
+++ b/tests/toyos-rust-tests/src/stream_ends.rs
@@ -0,0 +1,135 @@
+//! Each way a stream can end, as std reports it to a client: the same code
+//! runs in a guest against netstack and on the harness's host against the host
+//! kernel's TCP, and the two reports are compared line for line.
+//!
+//! The peer is the harness's: a connection's first byte names what it does.
+//! Every end it makes waits on something the client did, so no line depends
+//! on how long anything took.
+//!
+//! Std alone: the harness compiles this file too.
+
+use std::io::{ErrorKind, Read, Write};
+use std::net::{Shutdown, TcpStream};
+
+/// What a client half-closes after, and reads back.
+pub const BULK: usize = 1 << 20;
+/// What the peer sends before the reset of `reset_mid_stream`.
+pub const AHEAD: usize = 1 << 16;
+
+/// The peer reads to the client's end, sends every byte back, then its FIN.
+pub const HALF_CLOSE: u8 = b'H';
+/// The peer sends its FIN before the client writes.
+pub const PEER_FIN_FIRST: u8 = b'F';
+/// The peer sends [`AHEAD`] bytes, reads the client's one, then resets.
+pub const RESET_MID_STREAM: u8 = b'R';
+/// The peer reads to the client's end, then resets.
+pub const RESET_AFTER_HALF_CLOSE: u8 = b'D';
+/// The peer sends three bytes and its FIN; the client then sends its own.
+pub const BOTH_CLOSE: u8 = b'B';
+
+/// The report a host's TCP gives, and so the one netstack owes.
+pub const EXPECTED: [&str; 7] = [
+    "half_close: wrote 1048576 bytes, then shut the sending half: Ok",
+    "half_close: 1048576 bytes came back as sent, then Ok(0)",
+    "peer_fin_first: the read Ok(0), then a write Ok(5)",
+    "reset_mid_stream: 65536 bytes came as sent, the answer Ok(1), then Err(ConnectionReset)",
+    "reset_after_half_close: shut the sending half: Ok, then Err(ConnectionReset)",
+    "both_close: 3 bytes came as sent, then Ok(0)",
+    "both_close: shut the sending half: Ok, then Ok(0)",
+];
+
+/// Byte `i` of anything sent here: a pattern no shift or cut reproduces.
+pub fn pattern(i: usize) -> u8 {
+    (i % 251) as u8
+}
+
+fn kind<T: std::fmt::Debug>(result: std::io::Result<T>) -> String {
+    match result {
+        Ok(_) => "Ok".to_string(),
+        Err(e) => format!("Err({:?})", e.kind()),
+    }
+}
+
+/// What one more read answers: the end, or a byte that is not one.
+fn the_end(stream: &mut TcpStream) -> String {
+    let mut byte = [0u8; 1];
+    match stream.read(&mut byte) {
+        Ok(n) => format!("Ok({n})"),
+        Err(e) => format!("Err({:?})", e.kind()),
+    }
+}
+
+/// Reads until `want` bytes, the end or a refusal: how many matched
+/// `expect`, and what stopped it, if anything did before `want`.
+fn take(stream: &mut TcpStream, want: usize, expect: impl Fn(usize) -> u8) -> (String, Option<String>) {
+    let mut buf = vec![0u8; 65536];
+    let mut got = 0;
+    while got < want {
+        let room = buf.len().min(want - got);
+        match stream.read(&mut buf[..room]) {
+            Ok(0) => return (format!("{got} bytes came"), Some("Ok(0)".to_string())),
+            Ok(n) => {
+                if let Some(at) = (0..n).find(|&i| buf[i] != expect(got + i)) {
+                    return (format!("byte {} differs after {got} bytes", got + at), None);
+                }
+                got += n;
+            }
+            Err(e) if e.kind() == ErrorKind::Interrupted => {}
+            Err(e) => return (format!("{got} bytes came"), Some(format!("Err({:?})", e.kind()))),
+        }
+    }
+    (format!("{got} bytes came"), None)
+}
+
+fn dial(host: &str, port: u16, what: u8) -> TcpStream {
+    let mut stream = TcpStream::connect((host, port)).unwrap_or_else(|e| panic!("the peer at {host}:{port}: {e}"));
+    stream.write_all(&[what]).unwrap_or_else(|e| panic!("naming the end to the peer: {e}"));
+    stream
+}
+
+/// Every end, in order: one line each as [`EXPECTED`] spells them.
+pub fn run(host: &str, port: u16, mut say: impl FnMut(String)) {
+    let mut stream = dial(host, port, HALF_CLOSE);
+    let bulk: Vec<u8> = (0..BULK).map(pattern).collect();
+    let wrote = kind(stream.write_all(&bulk));
+    say(format!("half_close: wrote {BULK} bytes, then shut the sending half: {}", match wrote.as_str() {
+        "Ok" => kind(stream.shutdown(Shutdown::Write)),
+        _ => format!("the write {wrote}"),
+    }));
+    let (came, end) = take(&mut stream, BULK, pattern);
+    let end = end.unwrap_or_else(|| the_end(&mut stream));
+    say(format!("half_close: {} as sent, then {end}", came.replace("came", "came back")));
+
+    let mut stream = dial(host, port, PEER_FIN_FIRST);
+    let read = the_end(&mut stream);
+    let write = match stream.write(b"after") {
+        Ok(n) => format!("Ok({n})"),
+        Err(e) => format!("Err({:?})", e.kind()),
+    };
+    say(format!("peer_fin_first: the read {read}, then a write {write}"));
+
+    let mut stream = dial(host, port, RESET_MID_STREAM);
+    let (came, end) = take(&mut stream, AHEAD, pattern);
+    let line = match end {
+        Some(end) => format!("{came}, then {end}"),
+        None => {
+            let answer = match stream.write(b"k") {
+                Ok(n) => format!("Ok({n})"),
+                Err(e) => format!("Err({:?})", e.kind()),
+            };
+            format!("{came} as sent, the answer {answer}, then {}", the_end(&mut stream))
+        }
+    };
+    say(format!("reset_mid_stream: {line}"));
+
+    let mut stream = dial(host, port, RESET_AFTER_HALF_CLOSE);
+    let shut = kind(stream.shutdown(Shutdown::Write));
+    say(format!("reset_after_half_close: shut the sending half: {shut}, then {}", the_end(&mut stream)));
+
+    let mut stream = dial(host, port, BOTH_CLOSE);
+    let (came, end) = take(&mut stream, 3, |i| b"bye"[i]);
+    let end = end.unwrap_or_else(|| the_end(&mut stream));
+    say(format!("both_close: {came} as sent, then {end}"));
+    let shut = kind(stream.shutdown(Shutdown::Write));
+    say(format!("both_close: shut the sending half: {shut}, then {}", the_end(&mut stream)));
+}
diff --git a/tests/toyos.rs b/tests/toyos.rs
index ed832b7f6..ae7e1f79c 100644
--- a/tests/toyos.rs
+++ b/tests/toyos.rs
@@ -2,6 +2,8 @@
 extern crate toyos_build;
 
 mod common;
+#[path = "toyos-rust-tests/src/stream_ends.rs"]
+mod stream_ends;
 
 use std::collections::{BTreeMap, BTreeSet};
 use std::fs;
@@ -137,6 +139,9 @@ const RUST_SKIP: &[&str] = &[
     // It needs a host that dials its listeners when it says they wait:
     // `libc_sockets` runs it on `tests/netcase`.
     "nodelay_accepted",
+    // It needs a host peer that ends each stream as the stream asks:
+    // `netstack_stream_ends` runs it on `tests/netcase`.
+    "stream_ends",
     // It asserts nothing at all: it holds `dump_nmi_probe`'s boot open for
     // twenty seconds. On a shared boot it would be twenty seconds of nothing.
     "lan_hold",
@@ -270,6 +275,11 @@ const MACHINE_TESTS: &[&str] = &[
     // peer that answers: the calls are requests on netstack's port, netstack
     // has no host build, and the T14's peer is the bench's network.
     "libc_sockets",
+    // How std reads each way a stream ends, against the host kernel's TCP
+    // running the same code: the end is what netstack, std and the pipes
+    // between them say together, netstack has no host build, and the T14's
+    // bench has no peer that resets on cue.
+    "netstack_stream_ends",
     // The nested-NMI report is a raw write to the 16550, which the T14 does not
     // have.
     "nested_nmi_is_loud",
@@ -3079,6 +3089,97 @@ fn libc_sockets() -> Result<(), String> {
     Ok(())
 }
 
+/// Every way `stream_ends.rs` ends a stream, read by std in a guest through
+/// netstack and by the same code on this host through its kernel's TCP, both
+/// against one peer here: the guest's report is the host's, line for line, and
+/// the host's is the one the job spells.
+fn netstack_stream_ends() -> Result<(), String> {
+    const JOB: &str = "stream_ends";
+    let peer = stream_ends_peer()?;
+    let mut host = Vec::new();
+    stream_ends::run("127.0.0.1", peer, |line| host.push(line));
+    if host != stream_ends::EXPECTED {
+        return Err(format!("the oracle: this host's TCP reported\n{}\nand not\n{}", host.join("\n"), stream_ends::EXPECTED.join("\n")));
+    }
+
+    let bin = qemu::build_toyos_bin(qemu::SUITE_ARCH, &compile::repo_root().join("tests/toyos-rust-tests"), JOB);
+    let mut qemu = boot_netcase(&[], &[(JOB.to_string(), bin)], BootOptions::default())?;
+    let result = qemu.run_test(&format!("test_rs_{JOB} 10.0.2.2 {peer}"), Duration::from_secs(120));
+    if let Some(why) = &result.error {
+        return Err(format!("{why}\nthe job said:\n{}", result.stdout));
+    }
+    let guest: Vec<&str> = result.stdout.lines().filter_map(|l| l.trim_end().strip_prefix("stream_ends: ")).collect();
+    if guest.last() != Some(&"done") || guest[..guest.len() - 1] != host {
+        return Err(format!("the guest reported\n{}\nwhere this host's TCP reported\n{}", guest.join("\n"), host.join("\n")));
+    }
+    if result.exit_code != Some(0) {
+        return Err(format!("the job ended {:?}:\n{}", result.exit_code, result.stdout));
+    }
+    Ok(())
+}
+
+/// The peer `stream_ends.rs` dials, for as long as the process lives: its
+/// port. Each connection's first byte names how it ends, and each end waits
+/// on what the client did before it.
+fn stream_ends_peer() -> Result<u16, String> {
+    use std::io::{Read, Write};
+    use std::os::fd::AsRawFd;
+    /// A close that sends a reset (RFC 9293 §3.10.4's ABORT): a zero linger.
+    fn reset(stream: std::net::TcpStream) {
+        let linger = libc::linger { l_onoff: 1, l_linger: 0 };
+        // SAFETY: a live socket, and an option of the size its type says.
+        let set = unsafe {
+            libc::setsockopt(
+                stream.as_raw_fd(),
+                libc::SOL_SOCKET,
+                libc::SO_LINGER,
+                (&raw const linger).cast(),
+                size_of::<libc::linger>() as libc::socklen_t,
+            )
+        };
+        assert_eq!(set, 0, "a zero linger: {}", std::io::Error::last_os_error());
+    }
+    fn serve(mut stream: std::net::TcpStream) {
+        let mut what = [0u8; 1];
+        if stream.read_exact(&mut what).is_err() {
+            return;
+        }
+        match what[0] {
+            stream_ends::HALF_CLOSE => {
+                let mut all = Vec::new();
+                if stream.read_to_end(&mut all).is_ok() {
+                    let _ = stream.write_all(&all);
+                }
+            }
+            stream_ends::PEER_FIN_FIRST => {}
+            stream_ends::RESET_MID_STREAM => {
+                let ahead: Vec<u8> = (0..stream_ends::AHEAD).map(stream_ends::pattern).collect();
+                let mut answer = [0u8; 1];
+                if stream.write_all(&ahead).is_ok() && stream.read_exact(&mut answer).is_ok() {
+                    reset(stream);
+                }
+            }
+            stream_ends::RESET_AFTER_HALF_CLOSE => {
+                if stream.read_to_end(&mut Vec::new()).is_ok() {
+                    reset(stream);
+                }
+            }
+            stream_ends::BOTH_CLOSE => {
+                let _ = stream.write_all(b"bye");
+            }
+            _ => {}
+        }
+    }
+    let peer = std::net::TcpListener::bind(("127.0.0.1", 0)).map_err(|e| format!("the peer: {e}"))?;
+    let port = peer.local_addr().map_err(|e| format!("the peer's port: {e}"))?.port();
+    thread::spawn(move || {
+        for stream in peer.incoming().flatten() {
+            thread::spawn(move || serve(stream));
+        }
+    });
+    Ok(port)
+}
+
 /// Run the machine-shape test, which owns its QEMU: the machine shape *is* the
 /// test.
 fn run_machine_test(name: &str, test_config: &Path) -> Result<(), String> {
@@ -3086,6 +3187,7 @@ fn run_machine_test(name: &str, test_config: &Path) -> Result<(), String> {
         "iommu_virtio_platform" => common::iommu::iommu_virtio_platform(test_config),
         "netstack_socket_churn" => netstack_socket_churn(),
         "libc_sockets" => libc_sockets(),
+        "netstack_stream_ends" => netstack_stream_ends(),
         "nested_nmi_is_loud" => faults::nested_nmi_is_loud(test_config),
         "machine_shutdown" => power::machine_shutdown(test_config),
         "acpi_power_button" => power::acpi_power_button(test_config),

@Japabu

Japabu commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator Author

Review, round 1, head 96f48560f.

Net: git diff --shortstat origin/main...96f48560f: 11 files, +360 / -52. Production: the node +67 / -21, libc +78 / -6. Tests: +147 / -6. The rest is issues/.

Evidence read at the cited heads, each with its log and exit: --build-only 0, clippy 0 (24 invocations clean), --ci host 0 at 1e303178b and at 96f48560f (78 steps), the guest suite 0 (37 of 37), netstack_socket_churn and libc_sockets 0, the node 0 (156), the control 101 with the 6 named reds, m1 to m8 each 101 at the tests the body names, [tcp] 0, toyos-libc-copies 0 (48). The measurement of main exists, exit 1, and its lines match the body's table.

The questions in the brief:

  • Every way a connection ends, read against streams.rs at the head, drops the send pipe first or keeps it in the order ended reads correctly. Covered: a failure in the receive loop drops from_client and then to_client. A failure after the peer's FIN drops the send pipe through status.failure. A whole drop (a broken pipe, the cut, pipe_broken) goes in field order, from_client first. A timeout and an ICMP end are Failed like a reset. A close and a reader that left set left. An orderly CLOSED or TIME-WAIT implies fin_queued, because [tcp] reaches neither without our FIN.
  • The pin: no timer bounds a kept end. Its bound is places (places.rs) and the client's own life, because the kernel drops a dead client's writer. This is the bound every open stream has, and on a host an fd keeps its socket the same way. the_peers_fin_after_the_clients_shutdown_leaves_the_send_pipe_to_its_writer holds the stream 300 s past TIME-WAIT and frees it when its writer leaves. [tcp] answers status for a held Ended connection, so the kept stream's passes do not panic.
  • A writer that leaves after the shutdown and before the peer's FIN: pipe_gone(FromClient) with fin_queued drops the send pipe. The stream lives on to_client and closes at the peer's FIN. No test plays this order (NOTE below).
  • SIGPIPE: none is raised. libc's write raises none on a pipe either, so this is consistent within libc. In effect it is MSG_NOSIGNAL always, where a host kills the process by default (NOTE below).

BLOCKER

  1. userland/libc/src/socket.rs:395-401 together with issues/a-netstack-client-cannot-tell-a-reset-from-the-peers-fin.md, on what main does. Problem: the claim that main's shell already reads every end as a host does, and that row 1's ConnectionReset belongs to issues/a-shutdown-of-the-sending-half-drops-what-the-send-pipe-still-holds.md, is contradicted by main's code. bridge_piped (userland/netstack/src/main.rs:1178-1188) closes the receive pipe and then the send pipe in the same pass once !socket.is_open(). A client that shut its sending half down first reaches TIME-WAIT at the peer's FIN, so it reads both pipes gone, and gets ConnectionReset, whether or not any byte was cut. The dropped bytes explain the 65,536 count, not the reset. No row measured on main separates the two: both_close shuts down after the peer's FIN, and reset_after_half_close is a reset.
    Why it matters: if the code reads true, then this branch's libc probe turns a C client's shutdown(SHUT_WR) followed by recv to the end on main from 0, which was right, into ECONNRESET. That is the HTTP/1.0 client's pattern, and the regression ships until the move.
    Required:

    • Measure, at the landing head on main's stack, the row the table lacks: a client that shuts down with nothing pending, then a peer that writes and sends its FIN. One run in a guest and one on this host's TCP.
    • If it reads ConnectionReset, either hold libc's recv probe until the move, or record the regression with that row's measurement. In either case, correct both issues' attribution and their "neither met the race".
  2. userland/libc/src/socket.rs:355-401, :536-537: no test can fail on libc's wiring. Problem: the pure rule is host-tested. The call sites are reached by nothing: libc_sockets reads no stream's end, and the guest patch is std only. High-risk by the body's own reckoning.
    These mutations each pass every test today:

    • 0 => Ok(0) in place of the streamend::read_end(...) probe in recv.
    • Delete entry.write_shut |= matches!(how, SHUT_WR | SHUT_RDWR);.
    • Delete if entry.read_shut { return 0; }.

    Required: follow the UDP broadcast line of issues/toyos-has-its-own-network-stack.md, which names a guest C case for libc at the move. Either that issue or issues/a-netstack-client-cannot-tell-a-reset-from-the-peers-fin.md names in its exit a guest C case on tests/netcase that the move carries. The case reads:

    • recv 0 at the peer's FIN after shutdown(SHUT_WR);
    • ECONNRESET on a reset mid-stream and after SHUT_WR;
    • send EPIPE after SHUT_WR;
    • recv 0 after SHUT_RD.

    The exit also says that each of the three mutations above turns the case red.

NOTE

  • userland/netstack/node/src/streams.rs:268 and :472: .filter(|_| !self.fin_queued) and readable: … && !stream.fin_queued are dead conjuncts. [tcp]'s status answers writable 0 once its FIN is queued (toyos-net-shard/tcp/src/stack.rs:1128) and 0 for Ended, so room == 0 already breaks the loop and room is already false. Deleting either changes nothing a test can see. Delete both, or the comment says why they hold what [tcp] does not.
  • userland/netstack/node/tests/streams.rs: no test plays a writer that leaves after the client's shutdown and before the peer's FIN. The send pipe then goes first and the stream lives on to_client until the peer's FIN. This path is read from the code only.
  • userland/libc/src/socket.rs:358: EPIPE without SIGPIPE, where POSIX raises it unless MSG_NOSIGNAL is given. libc's write to a pipe does the same, and no issue records the departure.
  • Prose: the body's "So on main the FIN-as-reset defect is not the shipped stack's" and "main's shell already reads every end as a host does" are false of main's code, as BLOCKER 1 says. The issue a-netstack-client-cannot-tell-a-reset-from-the-peers-fin.md repeats "neither met the race", which row 1 contradicts.

What host and guest / suite must show at the landing head

The pull request is a draft, so ci.yml's host has not run on it. At the head that lands, both checks must be green on GitHub at that exact head: host (it runs the node's 156 tests and toyos-libc-copies) and guest / suite (KVM). The orchestrator may land on those two only after BLOCKERs 1 and 2 are closed by measurements and records in the body at that head. Neither check reads any of what the BLOCKERs ask:

  • host asserts the node's order, not a host's answer;
  • guest / suite on main runs no stream-end case.

SEND BACK

@Japabu

Japabu commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator Author

Round 2, step 1: the row the review asks for, measured at 96f48560f on main's stack (smoltcp shell), with this branch's libc. shut_first: the client sends one byte, reads the peer's one-byte answer, shuts its sending half down with nothing pending; the peer, at that FIN, sends four bytes and its own FIN. Std job (stream_ends, the posted guest case plus this row) and a C job (tests/netcase/shut_first.c) in one guest on tests/netcase, and the same std source on this Mac's TCP. Command cargo test --test toyos-build -- netstack_stream_ends, EXIT=1.

Log r2-measure-head.log:

15:37  up 10 days,  3:22, 6 users, load averages: 20.99 23.79 27.87
96f48560f2d5ee70d69e6224d1c7edbba1d2df5c
   Compiling toyos-build v0.1.0 (<worktree>)
    Finished `test` profile [optimized + debuginfo] target(s) in 6.38s
     Running tests/toyos.rs (target/debug/deps/toyos_build-497f416569e2c965)

13:38:06 running 1 tests, 12 wide

13:38:06   RUN   netstack_stream_ends
13:38:06   BUILD x86_64 stream_ends of tests/toyos-rust-tests, for netstack_stream_ends
13:38:11   BUILT x86_64 stream_ends of tests/toyos-rust-tests, for netstack_stream_ends  (5s)
13:38:15   BUILD x86_64 kernel, loader, ROOT of tests/netcase, for netstack_stream_ends
13:38:20   BUILT x86_64 kernel, loader, ROOT of tests/netcase, for netstack_stream_ends  (5s)
the C job, exit Some(1), error None:
shut_first_c: send 1
shut_first_c: the answer 1
shut_first_c: shutdown(SHUT_WR) 0
shut_first_c: 4 bytes came, then recv -1, errno ECONNRESET

the Rust job, exit Some(1):
stream_ends: half_close: wrote 1048576 bytes, then shut the sending half: Ok
stream_ends: half_close: 65535 bytes came back as sent, then Err(ConnectionReset)
stream_ends: peer_fin_first: the read Ok(0), then a write Ok(5)
stream_ends: reset_mid_stream: 65536 bytes came as sent, the answer Ok(1), then Err(ConnectionReset)
stream_ends: reset_after_half_close: shut the sending half: Ok, then Err(ConnectionReset)
stream_ends: both_close: 3 bytes came as sent, then Ok(0)
stream_ends: both_close: shut the sending half: Ok, then Err(ConnectionReset)
stream_ends: shut_first: the answer Ok(1), shut the sending half: Ok, 4 bytes came as sent, then Err(ConnectionReset)
stream_ends: done

this host's TCP:
half_close: wrote 1048576 bytes, then shut the sending half: Ok
half_close: 1048576 bytes came back as sent, then Ok(0)
peer_fin_first: the read Ok(0), then a write Ok(5)
reset_mid_stream: 65536 bytes came as sent, the answer Ok(1), then Err(ConnectionReset)
reset_after_half_close: shut the sending half: Ok, then Err(ConnectionReset)
both_close: 3 bytes came as sent, then Ok(0)
both_close: shut the sending half: Ok, then Ok(0)
shut_first: the answer Ok(1), shut the sending half: Ok, 4 bytes came as sent, then Ok(0)
13:38:25 FAIL netstack_stream_ends: the guest reported
half_close: wrote 1048576 bytes, then shut the sending half: Ok
half_close: 65535 bytes came back as sent, then Err(ConnectionReset)
peer_fin_first: the read Ok(0), then a write Ok(5)
reset_mid_stream: 65536 bytes came as sent, the answer Ok(1), then Err(ConnectionReset)
reset_after_half_close: shut the sending half: Ok, then Err(ConnectionReset)
both_close: 3 bytes came as sent, then Ok(0)
both_close: shut the sending half: Ok, then Err(ConnectionReset)
shut_first: the answer Ok(1), shut the sending half: Ok, 4 bytes came as sent, then Err(ConnectionReset)
done
where this host's TCP reported
half_close: wrote 1048576 bytes, then shut the sending half: Ok
half_close: 1048576 bytes came back as sent, then Ok(0)
peer_fin_first: the read Ok(0), then a write Ok(5)
reset_mid_stream: 65536 bytes came as sent, the answer Ok(1), then Err(ConnectionReset)
reset_after_half_close: shut the sending half: Ok, then Err(ConnectionReset)
both_close: 3 bytes came as sent, then Ok(0)
both_close: shut the sending half: Ok, then Ok(0)
shut_first: the answer Ok(1), shut the sending half: Ok, 4 bytes came as sent, then Ok(0)
13:38:25   FAIL  netstack_stream_ends  (8s)
13:38:25   --- 1 guests, 0 of them not the shipping kernel, 1 kernel build(s): [""]

13:38:25 host: fastest boot 2883 ms against the reference 1424 ms — liveness ceilings paid at 2.02x
13:38:25 host: 14 core(s); a guest wider than that waits vcpus/cores longer again
13:38:25 failures:
13:38:25     netstack_stream_ends: the guest reported

13:38:25 test result: FAILED. 0 passed, 1 failed, 0 invalidated, 1 total (18.8s; workers: 11s building, 8s testing)
13:38:25 [toyos] this red run's serial logs are kept at <worktree>/target/red-run-serial/toyos-tmp-3401-0
error: test failed, to rerun pass `--test toyos-build`

Caused by:
  process didn't exit successfully: `<worktree>/target/debug/deps/toyos_build-497f416569e2c965 netstack_stream_ends` (exit status: 1)
EXIT=1
The measurement patch (applies to `96f48560f`; not landed)
diff --git a/tests/toyos.rs b/tests/toyos.rs
index ed832b7f6..90eb1472d 100644
--- a/tests/toyos.rs
+++ b/tests/toyos.rs
@@ -2,6 +2,8 @@
 extern crate toyos_build;
 
 mod common;
+#[path = "toyos-rust-tests/src/stream_ends.rs"]
+mod stream_ends;
 
 use std::collections::{BTreeMap, BTreeSet};
 use std::fs;
@@ -137,6 +139,9 @@ const RUST_SKIP: &[&str] = &[
     // It needs a host that dials its listeners when it says they wait:
     // `libc_sockets` runs it on `tests/netcase`.
     "nodelay_accepted",
+    // It needs a host peer that ends each stream as the stream asks:
+    // `netstack_stream_ends` runs it on `tests/netcase`.
+    "stream_ends",
     // It asserts nothing at all: it holds `dump_nmi_probe`'s boot open for
     // twenty seconds. On a shared boot it would be twenty seconds of nothing.
     "lan_hold",
@@ -270,6 +275,11 @@ const MACHINE_TESTS: &[&str] = &[
     // peer that answers: the calls are requests on netstack's port, netstack
     // has no host build, and the T14's peer is the bench's network.
     "libc_sockets",
+    // How std reads each way a stream ends, against the host kernel's TCP
+    // running the same code: the end is what netstack, std and the pipes
+    // between them say together, netstack has no host build, and the T14's
+    // bench has no peer that resets on cue.
+    "netstack_stream_ends",
     // The nested-NMI report is a raw write to the 16550, which the T14 does not
     // have.
     "nested_nmi_is_loud",
@@ -3079,6 +3089,108 @@ fn libc_sockets() -> Result<(), String> {
     Ok(())
 }
 
+/// Every way `stream_ends.rs` ends a stream, read by std in a guest through
+/// netstack and by the same code on this host through its kernel's TCP, both
+/// against one peer here: the guest's report is the host's, line for line, and
+/// the host's is the one the job spells.
+fn netstack_stream_ends() -> Result<(), String> {
+    const JOB: &str = "stream_ends";
+    let peer = stream_ends_peer()?;
+    let mut host = Vec::new();
+    stream_ends::run("127.0.0.1", peer, |line| host.push(line));
+    if host != stream_ends::EXPECTED {
+        return Err(format!("the oracle: this host's TCP reported\n{}\nand not\n{}", host.join("\n"), stream_ends::EXPECTED.join("\n")));
+    }
+
+    let bin = qemu::build_toyos_bin(qemu::SUITE_ARCH, &compile::repo_root().join("tests/toyos-rust-tests"), JOB);
+    let case = compile::repo_root().join("tests/netcase");
+    let c_bins = vec![("shut_first".to_string(), compile::link_toyos(&compile::compile_own_c(&case, "shut_first"), "shut_first"))];
+    let mut qemu = boot_netcase(&c_bins, &[(JOB.to_string(), bin)], BootOptions::default())?;
+    let c = qemu.run_test(&format!("test_c_shut_first 10.0.2.2 {peer}"), Duration::from_secs(120));
+    println!("the C job, exit {:?}, error {:?}:\n{}", c.exit_code, c.error, c.stdout);
+    let result = qemu.run_test(&format!("test_rs_{JOB} 10.0.2.2 {peer}"), Duration::from_secs(120));
+    println!("the Rust job, exit {:?}:\n{}", result.exit_code, result.stdout);
+    println!("this host's TCP:\n{}", host.join("\n"));
+    if let Some(why) = &result.error {
+        return Err(format!("{why}\nthe job said:\n{}", result.stdout));
+    }
+    let guest: Vec<&str> = result.stdout.lines().filter_map(|l| l.trim_end().strip_prefix("stream_ends: ")).collect();
+    if guest.last() != Some(&"done") || guest[..guest.len() - 1] != host {
+        return Err(format!("the guest reported\n{}\nwhere this host's TCP reported\n{}", guest.join("\n"), host.join("\n")));
+    }
+    if result.exit_code != Some(0) {
+        return Err(format!("the job ended {:?}:\n{}", result.exit_code, result.stdout));
+    }
+    Ok(())
+}
+
+/// The peer `stream_ends.rs` dials, for as long as the process lives: its
+/// port. Each connection's first byte names how it ends, and each end waits
+/// on what the client did before it.
+fn stream_ends_peer() -> Result<u16, String> {
+    use std::io::{Read, Write};
+    use std::os::fd::AsRawFd;
+    /// A close that sends a reset (RFC 9293 §3.10.4's ABORT): a zero linger.
+    fn reset(stream: std::net::TcpStream) {
+        let linger = libc::linger { l_onoff: 1, l_linger: 0 };
+        // SAFETY: a live socket, and an option of the size its type says.
+        let set = unsafe {
+            libc::setsockopt(
+                stream.as_raw_fd(),
+                libc::SOL_SOCKET,
+                libc::SO_LINGER,
+                (&raw const linger).cast(),
+                size_of::<libc::linger>() as libc::socklen_t,
+            )
+        };
+        assert_eq!(set, 0, "a zero linger: {}", std::io::Error::last_os_error());
+    }
+    fn serve(mut stream: std::net::TcpStream) {
+        let mut what = [0u8; 1];
+        if stream.read_exact(&mut what).is_err() {
+            return;
+        }
+        match what[0] {
+            stream_ends::HALF_CLOSE => {
+                let mut all = Vec::new();
+                if stream.read_to_end(&mut all).is_ok() {
+                    let _ = stream.write_all(&all);
+                }
+            }
+            stream_ends::PEER_FIN_FIRST => {}
+            stream_ends::RESET_MID_STREAM => {
+                let ahead: Vec<u8> = (0..stream_ends::AHEAD).map(stream_ends::pattern).collect();
+                let mut answer = [0u8; 1];
+                if stream.write_all(&ahead).is_ok() && stream.read_exact(&mut answer).is_ok() {
+                    reset(stream);
+                }
+            }
+            stream_ends::RESET_AFTER_HALF_CLOSE => {
+                if stream.read_to_end(&mut Vec::new()).is_ok() {
+                    reset(stream);
+                }
+            }
+            stream_ends::BOTH_CLOSE => {
+                let _ = stream.write_all(b"bye");
+            }
+            stream_ends::SHUT_FIRST => {
+                if stream.write_all(b".").is_ok() && stream.read_to_end(&mut Vec::new()).is_ok() {
+                    let _ = stream.write_all(b"late");
+                }
+            }
+            _ => {}
+        }
+    }
+    let peer = std::net::TcpListener::bind(("127.0.0.1", 0)).map_err(|e| format!("the peer: {e}"))?;
+    let port = peer.local_addr().map_err(|e| format!("the peer's port: {e}"))?.port();
+    thread::spawn(move || {
+        for stream in peer.incoming().flatten() {
+            thread::spawn(move || serve(stream));
+        }
+    });
+    Ok(port)
+}
+
 /// Run the machine-shape test, which owns its QEMU: the machine shape *is* the
 /// test.
 fn run_machine_test(name: &str, test_config: &Path) -> Result<(), String> {
@@ -3086,6 +3198,7 @@ fn run_machine_test(name: &str, test_config: &Path) -> Result<(), String> {
         "iommu_virtio_platform" => common::iommu::iommu_virtio_platform(test_config),
         "netstack_socket_churn" => netstack_socket_churn(),
         "libc_sockets" => libc_sockets(),
+        "netstack_stream_ends" => netstack_stream_ends(),
         "nested_nmi_is_loud" => faults::nested_nmi_is_loud(test_config),
         "machine_shutdown" => power::machine_shutdown(test_config),
         "acpi_power_button" => power::acpi_power_button(test_config),
diff --git a/tests/netcase/shut_first.c b/tests/netcase/shut_first.c
new file mode 100644
index 000000000..8a9f568f0
--- /dev/null
+++ b/tests/netcase/shut_first.c
@@ -0,0 +1,32 @@
+/* A client that shuts its sending half down with nothing pending, then reads
+   the peer's four bytes and its FIN. argv: the address and the port of the
+   stream_ends peer. */
+#include <arpa/inet.h>
+#include <errno.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+
+int main(int argc, char **argv) {
+    if (argc != 3) return 2;
+    struct sockaddr_in to;
+    memset(&to, 0, sizeof to);
+    to.sin_family = AF_INET;
+    to.sin_port = htons((uint16_t)atoi(argv[2]));
+    inet_pton(AF_INET, argv[1], &to.sin_addr);
+    int fd = socket(AF_INET, SOCK_STREAM, 0);
+    if (connect(fd, (struct sockaddr *)&to, sizeof to) != 0) { printf("shut_first_c: connect failed\n"); return 1; }
+    char c = 'S';
+    printf("shut_first_c: send %ld\n", (long)send(fd, &c, 1, 0));
+    printf("shut_first_c: the answer %ld\n", (long)recv(fd, &c, 1, 0));
+    printf("shut_first_c: shutdown(SHUT_WR) %d\n", shutdown(fd, SHUT_WR));
+    char buf[64];
+    long got = 0, r;
+    while ((r = recv(fd, buf, sizeof buf, 0)) > 0) got += r;
+    int e = errno;
+    printf("shut_first_c: %ld bytes came, then recv %ld%s%s\n", got, r, r < 0 ? ", errno " : "",
+           r < 0 ? (e == ECONNRESET ? "ECONNRESET" : strerror(e)) : "");
+    return r == 0 && got == 4 ? 0 : 1;
+}
diff --git a/tests/toyos-rust-tests/src/stream_ends.rs b/tests/toyos-rust-tests/src/stream_ends.rs
new file mode 100644
index 000000000..e03cc6a21
--- /dev/null
+++ b/tests/toyos-rust-tests/src/stream_ends.rs
@@ -0,0 +1,146 @@
+//! Each way a stream can end, as std reports it to a client: the same code
+//! runs in a guest against netstack and on the harness's host against the host
+//! kernel's TCP, and the two reports are compared line for line.
+//!
+//! The peer is the harness's: a connection's first byte names what it does.
+//! Every end it makes waits on something the client did, so no line depends
+//! on how long anything took.
+//!
+//! Std alone: the harness compiles this file too.
+
+use std::io::{ErrorKind, Read, Write};
+use std::net::{Shutdown, TcpStream};
+
+/// What a client half-closes after, and reads back.
+pub const BULK: usize = 1 << 20;
+/// What the peer sends before the reset of `reset_mid_stream`.
+pub const AHEAD: usize = 1 << 16;
+
+/// The peer reads to the client's end, sends every byte back, then its FIN.
+pub const HALF_CLOSE: u8 = b'H';
+/// The peer sends its FIN before the client writes.
+pub const PEER_FIN_FIRST: u8 = b'F';
+/// The peer sends [`AHEAD`] bytes, reads the client's one, then resets.
+pub const RESET_MID_STREAM: u8 = b'R';
+/// The peer reads to the client's end, then resets.
+pub const RESET_AFTER_HALF_CLOSE: u8 = b'D';
+/// The peer sends three bytes and its FIN; the client then sends its own.
+pub const BOTH_CLOSE: u8 = b'B';
+/// The peer answers one byte; the client then shuts its sending half with
+/// nothing pending, and the peer, at that FIN, sends four bytes and its own.
+pub const SHUT_FIRST: u8 = b'S';
+
+/// The report a host's TCP gives, and so the one netstack owes.
+pub const EXPECTED: [&str; 8] = [
+    "half_close: wrote 1048576 bytes, then shut the sending half: Ok",
+    "half_close: 1048576 bytes came back as sent, then Ok(0)",
+    "peer_fin_first: the read Ok(0), then a write Ok(5)",
+    "reset_mid_stream: 65536 bytes came as sent, the answer Ok(1), then Err(ConnectionReset)",
+    "reset_after_half_close: shut the sending half: Ok, then Err(ConnectionReset)",
+    "both_close: 3 bytes came as sent, then Ok(0)",
+    "both_close: shut the sending half: Ok, then Ok(0)",
+    "shut_first: the answer Ok(1), shut the sending half: Ok, 4 bytes came as sent, then Ok(0)",
+];
+
+/// Byte `i` of anything sent here: a pattern no shift or cut reproduces.
+pub fn pattern(i: usize) -> u8 {
+    (i % 251) as u8
+}
+
+fn kind<T: std::fmt::Debug>(result: std::io::Result<T>) -> String {
+    match result {
+        Ok(_) => "Ok".to_string(),
+        Err(e) => format!("Err({:?})", e.kind()),
+    }
+}
+
+/// What one more read answers: the end, or a byte that is not one.
+fn the_end(stream: &mut TcpStream) -> String {
+    let mut byte = [0u8; 1];
+    match stream.read(&mut byte) {
+        Ok(n) => format!("Ok({n})"),
+        Err(e) => format!("Err({:?})", e.kind()),
+    }
+}
+
+/// Reads until `want` bytes, the end or a refusal: how many matched
+/// `expect`, and what stopped it, if anything did before `want`.
+fn take(stream: &mut TcpStream, want: usize, expect: impl Fn(usize) -> u8) -> (String, Option<String>) {
+    let mut buf = vec![0u8; 65536];
+    let mut got = 0;
+    while got < want {
+        let room = buf.len().min(want - got);
+        match stream.read(&mut buf[..room]) {
+            Ok(0) => return (format!("{got} bytes came"), Some("Ok(0)".to_string())),
+            Ok(n) => {
+                if let Some(at) = (0..n).find(|&i| buf[i] != expect(got + i)) {
+                    return (format!("byte {} differs after {got} bytes", got + at), None);
+                }
+                got += n;
+            }
+            Err(e) if e.kind() == ErrorKind::Interrupted => {}
+            Err(e) => return (format!("{got} bytes came"), Some(format!("Err({:?})", e.kind()))),
+        }
+    }
+    (format!("{got} bytes came"), None)
+}
+
+fn dial(host: &str, port: u16, what: u8) -> TcpStream {
+    let mut stream = TcpStream::connect((host, port)).unwrap_or_else(|e| panic!("the peer at {host}:{port}: {e}"));
+    stream.write_all(&[what]).unwrap_or_else(|e| panic!("naming the end to the peer: {e}"));
+    stream
+}
+
+/// Every end, in order: one line each as [`EXPECTED`] spells them.
+pub fn run(host: &str, port: u16, mut say: impl FnMut(String)) {
+    let mut stream = dial(host, port, HALF_CLOSE);
+    let bulk: Vec<u8> = (0..BULK).map(pattern).collect();
+    let wrote = kind(stream.write_all(&bulk));
+    say(format!("half_close: wrote {BULK} bytes, then shut the sending half: {}", match wrote.as_str() {
+        "Ok" => kind(stream.shutdown(Shutdown::Write)),
+        _ => format!("the write {wrote}"),
+    }));
+    let (came, end) = take(&mut stream, BULK, pattern);
+    let end = end.unwrap_or_else(|| the_end(&mut stream));
+    say(format!("half_close: {} as sent, then {end}", came.replace("came", "came back")));
+
+    let mut stream = dial(host, port, PEER_FIN_FIRST);
+    let read = the_end(&mut stream);
+    let write = match stream.write(b"after") {
+        Ok(n) => format!("Ok({n})"),
+        Err(e) => format!("Err({:?})", e.kind()),
+    };
+    say(format!("peer_fin_first: the read {read}, then a write {write}"));
+
+    let mut stream = dial(host, port, RESET_MID_STREAM);
+    let (came, end) = take(&mut stream, AHEAD, pattern);
+    let line = match end {
+        Some(end) => format!("{came}, then {end}"),
+        None => {
+            let answer = match stream.write(b"k") {
+                Ok(n) => format!("Ok({n})"),
+                Err(e) => format!("Err({:?})", e.kind()),
+            };
+            format!("{came} as sent, the answer {answer}, then {}", the_end(&mut stream))
+        }
+    };
+    say(format!("reset_mid_stream: {line}"));
+
+    let mut stream = dial(host, port, RESET_AFTER_HALF_CLOSE);
+    let shut = kind(stream.shutdown(Shutdown::Write));
+    say(format!("reset_after_half_close: shut the sending half: {shut}, then {}", the_end(&mut stream)));
+
+    let mut stream = dial(host, port, BOTH_CLOSE);
+    let (came, end) = take(&mut stream, 3, |i| b"bye"[i]);
+    let end = end.unwrap_or_else(|| the_end(&mut stream));
+    say(format!("both_close: {came} as sent, then {end}"));
+    let shut = kind(stream.shutdown(Shutdown::Write));
+    say(format!("both_close: shut the sending half: {shut}, then {}", the_end(&mut stream)));
+
+    let mut stream = dial(host, port, SHUT_FIRST);
+    let answer = the_end(&mut stream);
+    let shut = kind(stream.shutdown(Shutdown::Write));
+    let (came, end) = take(&mut stream, 4, |i| b"late"[i]);
+    let end = end.unwrap_or_else(|| the_end(&mut stream));
+    say(format!("shut_first: the answer {answer}, shut the sending half: {shut}, {came} as sent, then {end}"));
+}
diff --git a/tests/toyos-rust-tests/src/bin/stream_ends.rs b/tests/toyos-rust-tests/src/bin/stream_ends.rs
new file mode 100644
index 000000000..293c7fccd
--- /dev/null
+++ b/tests/toyos-rust-tests/src/bin/stream_ends.rs
@@ -0,0 +1,22 @@
+//! `stream_ends.rs`'s report, in a guest: one line each, then `done`, and an
+//! exit of 1 where it is not the one a host's TCP gives.
+//!
+//! argv: the address and port of the harness's peer.
+
+#[path = "../stream_ends.rs"]
+mod stream_ends;
+
+fn main() {
+    let args: Vec<String> = std::env::args().collect();
+    let [_, host, port] = &args[..] else { panic!("usage: stream_ends <host> <peer port>") };
+    let port = port.parse().unwrap_or_else(|e| panic!("the port {port}: {e}"));
+    let mut report = Vec::new();
+    stream_ends::run(host, port, |line| {
+        println!("stream_ends: {line}");
+        report.push(line);
+    });
+    println!("stream_ends: done");
+    if report != stream_ends::EXPECTED {
+        std::process::exit(1);
+    }
+}

@Japabu

Japabu commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator Author

Round 2, step 1, second arm: the same measurement with userland/libc back to main's (the log's first lines show the empty diff against the base 1621281ae), everything else as in the comment above. EXIT=1 (the std rows). The C client reads recv 0 with main's libc and ECONNRESET with this branch's: the regression BLOCKER 1 named, measured. Std on main's shell reads shut_first and both_close's second line as ConnectionReset in both runs, where this Mac's TCP reads Ok(0).

Log r2-measure-mainlibc.log:

15:38  up 10 days,  3:23, 6 users, load averages: 18.60 22.52 27.12
96f48560f2d5ee70d69e6224d1c7edbba1d2df5c
libc as main's: git diff 1621281ae -- userland/libc:
(end of diff)
    Finished `test` profile [optimized + debuginfo] target(s) in 0.41s
     Running tests/toyos.rs (target/debug/deps/toyos_build-497f416569e2c965)

13:39:00 running 1 tests, 12 wide

13:39:00   RUN   netstack_stream_ends
13:39:00   BUILD x86_64 stream_ends of tests/toyos-rust-tests, for netstack_stream_ends
13:39:03 external deps changed: cleaning <worktree>/tests/toyos-rust-tests/target/aarch64-unknown-toyos
13:39:03 external deps changed: cleaning <worktree>/tests/toyos-rust-tests/target/x86_64-unknown-toyos
13:39:05   BUILT x86_64 stream_ends of tests/toyos-rust-tests, for netstack_stream_ends  (5s)
13:39:07   BUILD x86_64 kernel, loader, ROOT of tests/netcase, for netstack_stream_ends
13:39:10 external deps changed: cleaning <worktree>/target/aarch64-unknown-toyos
13:39:11 external deps changed: cleaning <worktree>/target/x86_64-unknown-toyos
13:39:19   BUILT x86_64 kernel, loader, ROOT of tests/netcase, for netstack_stream_ends  (11s)
the C job, exit Some(0), error None:
shut_first_c: send 1
shut_first_c: the answer 1
shut_first_c: shutdown(SHUT_WR) 0
shut_first_c: 4 bytes came, then recv 0

the Rust job, exit Some(1):
stream_ends: half_close: wrote 1048576 bytes, then shut the sending half: Ok
stream_ends: half_close: 65535 bytes came back as sent, then Err(ConnectionReset)
stream_ends: peer_fin_first: the read Ok(0), then a write Ok(5)
stream_ends: reset_mid_stream: 65536 bytes came as sent, the answer Ok(1), then Err(ConnectionReset)
stream_ends: reset_after_half_close: shut the sending half: Ok, then Err(ConnectionReset)
stream_ends: both_close: 3 bytes came as sent, then Ok(0)
stream_ends: both_close: shut the sending half: Ok, then Err(ConnectionReset)
stream_ends: shut_first: the answer Ok(1), shut the sending half: Ok, 4 bytes came as sent, then Err(ConnectionReset)
stream_ends: done

this host's TCP:
half_close: wrote 1048576 bytes, then shut the sending half: Ok
half_close: 1048576 bytes came back as sent, then Ok(0)
peer_fin_first: the read Ok(0), then a write Ok(5)
reset_mid_stream: 65536 bytes came as sent, the answer Ok(1), then Err(ConnectionReset)
reset_after_half_close: shut the sending half: Ok, then Err(ConnectionReset)
both_close: 3 bytes came as sent, then Ok(0)
both_close: shut the sending half: Ok, then Ok(0)
shut_first: the answer Ok(1), shut the sending half: Ok, 4 bytes came as sent, then Ok(0)
13:39:24 FAIL netstack_stream_ends: the guest reported
half_close: wrote 1048576 bytes, then shut the sending half: Ok
half_close: 65535 bytes came back as sent, then Err(ConnectionReset)
peer_fin_first: the read Ok(0), then a write Ok(5)
reset_mid_stream: 65536 bytes came as sent, the answer Ok(1), then Err(ConnectionReset)
reset_after_half_close: shut the sending half: Ok, then Err(ConnectionReset)
both_close: 3 bytes came as sent, then Ok(0)
both_close: shut the sending half: Ok, then Err(ConnectionReset)
shut_first: the answer Ok(1), shut the sending half: Ok, 4 bytes came as sent, then Err(ConnectionReset)
done
where this host's TCP reported
half_close: wrote 1048576 bytes, then shut the sending half: Ok
half_close: 1048576 bytes came back as sent, then Ok(0)
peer_fin_first: the read Ok(0), then a write Ok(5)
reset_mid_stream: 65536 bytes came as sent, the answer Ok(1), then Err(ConnectionReset)
reset_after_half_close: shut the sending half: Ok, then Err(ConnectionReset)
both_close: 3 bytes came as sent, then Ok(0)
both_close: shut the sending half: Ok, then Ok(0)
shut_first: the answer Ok(1), shut the sending half: Ok, 4 bytes came as sent, then Ok(0)
13:39:24   FAIL  netstack_stream_ends  (7s)
13:39:24   --- 1 guests, 0 of them not the shipping kernel, 1 kernel build(s): [""]

13:39:24 host: fastest boot 3114 ms against the reference 1424 ms — liveness ceilings paid at 2.19x
13:39:24 host: 14 core(s); a guest wider than that waits vcpus/cores longer again
13:39:24 failures:
13:39:24     netstack_stream_ends: the guest reported

13:39:24 test result: FAILED. 0 passed, 1 failed, 0 invalidated, 1 total (23.5s; workers: 17s building, 7s testing)
13:39:24 [toyos] this red run's serial logs are kept at <worktree>/target/red-run-serial/toyos-tmp-16350-0
error: test failed, to rerun pass `--test toyos-build`

Caused by:
  process didn't exit successfully: `<worktree>/target/debug/deps/toyos_build-497f416569e2c965 netstack_stream_ends` (exit status: 1)
EXIT=1

@Japabu

Japabu commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator Author

Round 2, step 2: libc's half of round 1, removed from this branch, for the move to carry. It reads right only on the node: on main's smoltcp shell it turns a C client's shutdown(SHUT_WR) then recv to the end from 0 into ECONNRESET (the two measurement comments above). Against the base 1621281ae, git apply; its host test is cargo test -p toyos-libc-copies (48 tests with it). The move lands it together with the guest C case on tests/netcase that issues/a-netstack-client-cannot-tell-a-reset-from-the-peers-fin.md's exit names: recv 0 at the peer's FIN after SHUT_WR; ECONNRESET on a reset mid-stream and after SHUT_WR; send EPIPE after SHUT_WR; recv 0 after SHUT_RD; and red under each of the review's three mutations (0 => Ok(0) in place of the read_end probe in recv; entry.write_shut |= … deleted; if entry.read_shut { return 0; } deleted). tests/netcase/shut_first.c in the step-1 patch is a start on its first row.

diff --git a/tests/libc-arch/src/lib.rs b/tests/libc-arch/src/lib.rs
index a6fa47e5d..fa2c1def0 100644
--- a/tests/libc-arch/src/lib.rs
+++ b/tests/libc-arch/src/lib.rs
@@ -9,7 +9,7 @@
 //! `long double` widening against compiler-builtins', the errno codes against
 //! `include/errno.h`, IPv4 addresses and their texts against the host C
 //! library's, and the socket option rule against the host's `setsockopt` and
-//! `getsockopt`.
+//! `getsockopt`, and the rule that reads a stream's end.
 
 #[cfg(test)]
 extern crate alloc;
@@ -48,6 +48,9 @@ mod sigmask;
 #[path = "../../../userland/libc/src/sockopt.rs"]
 mod sockopt;
 #[cfg(test)]
+#[path = "../../../userland/libc/src/streamend.rs"]
+mod streamend;
+#[cfg(test)]
 #[path = "../../../userland/libc/src/strtonum.rs"]
 mod strtonum;
 #[cfg(test)]
@@ -88,6 +91,8 @@ mod signal_masks;
 #[cfg(test)]
 mod socket_options;
 #[cfg(test)]
+mod stream_ends;
+#[cfg(test)]
 mod strtonum_differential;
 #[cfg(test)]
 mod text_differential;
diff --git a/tests/libc-arch/src/stream_ends.rs b/tests/libc-arch/src/stream_ends.rs
new file mode 100644
index 000000000..d25480b32
--- /dev/null
+++ b/tests/libc-arch/src/stream_ends.rs
@@ -0,0 +1,30 @@
+//! libc's rule for a stream's end (`streamend.rs`): a read of 0 is the peer's FIN only while the
+//! send pipe still has its reader, a write the send pipe refuses for its reader's leaving is a
+//! reset, and nothing else either pipe says is read as one.
+
+use toyos_abi::syscall::SyscallError;
+
+use crate::streamend::{read_end, write_refused, Refusal};
+
+/// Every refusal but the two a pipe answers on purpose.
+const OTHERS: [SyscallError; 4] =
+    [SyscallError::InvalidArgument, SyscallError::PermissionDenied, SyscallError::BadAddress, SyscallError::Io];
+
+#[test]
+fn a_read_of_zero_is_the_peers_fin_only_while_the_send_pipe_is_read() {
+    assert_eq!(read_end(Ok(0)), Ok(()));
+    assert_eq!(read_end(Err(SyscallError::WouldBlock)), Ok(()), "a full send pipe still has its reader");
+    assert_eq!(read_end(Err(SyscallError::Gone)), Err(Refusal::Reset));
+    for other in OTHERS {
+        assert_eq!(read_end(Err(other)), Err(Refusal::Other), "{other:?}");
+    }
+}
+
+#[test]
+fn a_write_the_send_pipe_refuses_for_its_reader_is_a_reset() {
+    assert_eq!(write_refused(SyscallError::Gone), Refusal::Reset);
+    assert_eq!(write_refused(SyscallError::WouldBlock), Refusal::Again);
+    for other in OTHERS {
+        assert_eq!(write_refused(other), Refusal::Other, "{other:?}");
+    }
+}
diff --git a/userland/libc/src/lib.rs b/userland/libc/src/lib.rs
index 94e87be16..ef7401847 100644
--- a/userland/libc/src/lib.rs
+++ b/userland/libc/src/lib.rs
@@ -28,6 +28,7 @@ mod sigmask;
 mod socket;
 mod sockopt;
 mod stdio;
+mod streamend;
 mod string;
 mod strtonum;
 mod text;
diff --git a/userland/libc/src/socket.rs b/userland/libc/src/socket.rs
index 1a5986a94..0992bf218 100644
--- a/userland/libc/src/socket.rs
+++ b/userland/libc/src/socket.rs
@@ -8,9 +8,10 @@ use toyos_abi::syscall;
 use toyos::net::{NetError, TcpOptions, TcpSocketId, UdpSocketId, OPT_BROADCAST, OPT_NODELAY};
 
 use crate::errno::{
-    EACCES, EADDRINUSE, EAFNOSUPPORT, EBADF, ECONNREFUSED, ECONNRESET, EFAULT, EINVAL, EIO, ENOMEM, ENOPROTOOPT, ENOSPC,
-    ENOTCONN, EOPNOTSUPP, ETIMEDOUT,
+    EACCES, EADDRINUSE, EAFNOSUPPORT, EAGAIN, EBADF, ECONNREFUSED, ECONNRESET, EFAULT, EINVAL, EIO, ENOMEM, ENOPROTOOPT,
+    ENOSPC, ENOTCONN, EOPNOTSUPP, EPIPE, ETIMEDOUT,
 };
+use crate::streamend::{self, Refusal};
 use crate::inaddr::{self, SockaddrIn, AF_INET};
 use crate::sockopt::{self, Kept};
 
@@ -69,6 +70,9 @@ struct SocketEntry {
     // after its `connect`.
     nodelay: bool,
     broadcast: bool,
+    /// `shutdown` was asked for this half of a stream.
+    read_shut: bool,
+    write_shut: bool,
 }
 
 const MAX_SOCKETS: usize = 128;
@@ -178,6 +182,8 @@ pub unsafe extern "C" fn socket(domain: i32, sock_type: i32, _protocol: i32) ->
         notify_fd: 0,
         nodelay: false,
         broadcast: false,
+        read_shut: false,
+        write_shut: false,
     };
     let fd = alloc_socket(entry);
     if fd < 0 {
@@ -319,6 +325,8 @@ pub unsafe extern "C" fn accept(
         notify_fd: 0,
         nodelay: accepted.options.nodelay(),
         broadcast: false,
+        read_shut: false,
+        write_shut: false,
     };
     let new_fd = alloc_socket(new_entry);
     if new_fd < 0 {
@@ -346,10 +354,14 @@ pub unsafe extern "C" fn send(fd: i32, buf: *const u8, len: usize, _flags: i32)
 
     match entry.kind {
         SocketKind::Tcp => {
+            if entry.write_shut {
+                set_errno(EPIPE);
+                return -1;
+            }
             let data = core::slice::from_raw_parts(buf, len);
             match syscall::write(RawHandle(entry.tx_fd as u32), data) {
                 Ok(n) => n as isize,
-                Err(_) => { set_errno(EIO); -1 }
+                Err(e) => { set_errno(stream_errno(streamend::write_refused(e))); -1 }
             }
         }
         SocketKind::Udp => {
@@ -380,10 +392,17 @@ pub unsafe extern "C" fn recv(fd: i32, buf: *mut u8, len: usize, _flags: i32) ->
 
     match entry.kind {
         SocketKind::Tcp => {
+            if entry.read_shut {
+                return 0;
+            }
             let data = core::slice::from_raw_parts_mut(buf, len);
-            match syscall::read(RawHandle(entry.rx_fd as u32), data) {
+            let read = syscall::read(RawHandle(entry.rx_fd as u32), data).map_err(|_| Refusal::Other).and_then(|n| match n {
+                0 => streamend::read_end(syscall::write_nonblock(RawHandle(entry.tx_fd as u32), &[])).map(|()| 0),
+                n => Ok(n),
+            });
+            match read {
                 Ok(n) => n as isize,
-                Err(_) => { set_errno(EIO); -1 }
+                Err(refusal) => { set_errno(stream_errno(refusal)); -1 }
             }
         }
         SocketKind::Udp => {
@@ -504,7 +523,7 @@ pub unsafe extern "C" fn shutdown(fd: i32, how: i32) -> i32 {
         Some(s) => s,
         None => { set_errno(EBADF); return -1; }
     };
-    let entry = match slot.as_ref() {
+    let entry = match slot.as_mut() {
         Some(e) => e,
         None => { set_errno(EBADF); return -1; }
     };
@@ -514,10 +533,24 @@ pub unsafe extern "C" fn shutdown(fd: i32, how: i32) -> i32 {
             set_errno(net_err_to_errno(e));
             return -1;
         }
+        entry.read_shut |= matches!(how, SHUT_RD | SHUT_RDWR);
+        entry.write_shut |= matches!(how, SHUT_WR | SHUT_RDWR);
     }
     0
 }
 
+const SHUT_RD: i32 = 0;
+const SHUT_WR: i32 = 1;
+const SHUT_RDWR: i32 = 2;
+
+fn stream_errno(refusal: Refusal) -> i32 {
+    match refusal {
+        Refusal::Reset => ECONNRESET,
+        Refusal::Again => EAGAIN,
+        Refusal::Other => EIO,
+    }
+}
+
 
 // close (for socket fds)
 
diff --git a/userland/libc/src/streamend.rs b/userland/libc/src/streamend.rs
new file mode 100644
index 000000000..0831218f8
--- /dev/null
+++ b/userland/libc/src/streamend.rs
@@ -0,0 +1,38 @@
+//! How a stream socket's end reaches its caller. A stream is two of netstack's pipes, and the
+//! receive pipe says only that it ended: netstack lets the send pipe go first when the connection
+//! failed, and keeps it after an orderly end for as long as its writer holds it, so what a
+//! zero-byte write into it answers after the end is the end's kind. It reads nothing but what it
+//! is handed, so the host tests it (`toyos-libc-copies`).
+
+use toyos_abi::syscall::SyscallError;
+
+/// What a call on a stream answers instead of bytes.
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub(crate) enum Refusal {
+    /// `ECONNRESET`: the connection failed.
+    Reset,
+    /// `EAGAIN`.
+    Again,
+    /// `EIO`: the handle is not the pipe end the socket was made with.
+    Other,
+}
+
+/// A read of 0 from the receive pipe, given what a zero-byte write into the send pipe then
+/// answered: `Ok` is the peer's FIN, after its last byte.
+pub(crate) fn read_end(probe: Result<usize, SyscallError>) -> Result<(), Refusal> {
+    match probe {
+        Ok(_) | Err(SyscallError::WouldBlock) => Ok(()),
+        Err(SyscallError::Gone) => Err(Refusal::Reset),
+        Err(_) => Err(Refusal::Other),
+    }
+}
+
+/// A write the send pipe refused. One after a shutdown of the sending half is refused before it
+/// reaches the pipe, which netstack reads no more but keeps.
+pub(crate) fn write_refused(refused: SyscallError) -> Refusal {
+    match refused {
+        SyscallError::Gone => Refusal::Reset,
+        SyscallError::WouldBlock => Refusal::Again,
+        _ => Refusal::Other,
+    }
+}

Japabu and others added 3 commits October 9, 2026 15:41
… it reads a half-close's FIN as a reset

libc's recv probed the send pipe at a read of 0, as std does, and answered
ECONNRESET where it found no reader. That reads right only on the node. On
main, netstack on smoltcp closes both pipes in one pass once a connection is
done in both directions, so a client that shut its sending half down reads
its peer's FIN with the send pipe already gone. Measured in a guest at
96f4856 on tests/netcase: a C client that shuts its sending half down with
nothing pending and reads the peer's four bytes and FIN read recv -1
ECONNRESET with this libc and recv 0 with main's; std read ConnectionReset
in both, where the host's TCP read Ok(0).

So userland/libc goes back to main's, with streamend.rs and its host test in
toyos-libc-copies. The removed diff is posted on pull request #803 for the
move to carry with the guest C case that the issue's exit now names.

The issues say what main does by that measurement: the reset after a
half-close is bridge_piped's, not the dropped bytes'; the race between the
two closes was met (two runs of four); libc reads no end's kind; and libc's
EPIPE without SIGPIPE is recorded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C
…d a writer that leaves after the shutdown is tested

[tcp]'s status answers writable 0 once the FIN is queued, so the pass's
read loop already breaks on room == 0 and the watch's readable is already
false: the two fin_queued conjuncts held nothing, and go.

a_writer_that_leaves_after_the_shutdown_leaves_the_stream_to_its_reader
plays the order no test played: the client shuts its sending half down, its
writer leaves before the peer's FIN, the send pipe goes first and the stream
lives on its to-client pipe until the peer's text and FIN.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C
@Japabu

Japabu commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator Author

Round 2: the mutations and the negative control at the final head 052679491, each a checked git apply, cargo test -p toyos-net-node --no-fail-fast, reversed, and the tree checked clean after each (the runner below; the run's exit 0). m1–m8 are round 1's, m5's context moved by the deleted conjunct; m9 and m10 are this round's, for the new test. The control is git diff origin/main HEAD -- userland/netstack/node/src/streams.rs applied reversed.

Log r2-mutations.log:

head 052679491c939699dc21b16532c93181be65aa53
m1-failure-drops-to-client-first EXIT=101 red: a_reset_ends_both_pipes_and_the_stream a_reset_after_the_clients_shutdown_lets_the_send_pipe_go_first 
m2-fields-to-client-first EXIT=101 red: a_pipe_that_is_no_pipe_resets_its_connection 
m3-over-drops-send-pipe EXIT=101 red: a_shutdown_after_the_peers_fin_keeps_the_send_pipe_past_the_connections_end the_peers_fin_after_the_clients_shutdown_leaves_the_send_pipe_to_its_writer 
m4-fin-drops-send-pipe EXIT=101 red: a_finished_send_pipe_goes_with_its_reader a_shutdown_after_the_peers_fin_keeps_the_send_pipe_past_the_connections_end a_shutdown_sends_what_the_pipe_held_and_then_the_fin the_peers_fin_after_the_clients_shutdown_leaves_the_send_pipe_to_its_writer 
m5-kept-pipe-unwatched EXIT=101 red: a_shutdown_sends_what_the_pipe_held_and_then_the_fin the_peers_fin_after_the_clients_shutdown_leaves_the_send_pipe_to_its_writer 
m6-close-keeps-pipe EXIT=101 red: a_wake_is_owed_only_for_a_connection_there_is_a_place_for a_close_with_text_unread_resets a_close_sends_what_the_pipe_held_and_then_the_fin 
m7-kept-pipe-timed EXIT=101 red: the_peers_fin_after_the_clients_shutdown_leaves_the_send_pipe_to_its_writer 
m8-reader-gone-keeps-pipe EXIT=101 red: a_finished_send_pipe_goes_with_its_reader 
m9-writer-leaving-takes-the-stream EXIT=101 red: a_writer_that_leaves_after_the_shutdown_leaves_the_stream_to_its_reader 
m10-writer-leaving-unheard EXIT=101 red: a_writer_that_leaves_after_the_shutdown_leaves_the_stream_to_its_reader a_shutdown_after_the_peers_fin_keeps_the_send_pipe_past_the_connections_end the_peers_fin_after_the_clients_shutdown_leaves_the_send_pipe_to_its_writer 
control EXIT=101 red: a_finished_send_pipe_goes_with_its_reader a_pipe_that_is_no_pipe_resets_its_connection a_shutdown_after_the_peers_fin_keeps_the_send_pipe_past_the_connections_end a_reset_ends_both_pipes_and_the_stream a_shutdown_sends_what_the_pipe_held_and_then_the_fin the_peers_fin_after_the_clients_shutdown_leaves_the_send_pipe_to_its_writer 
DONE

m1-failure-drops-to-client-first

--- a/userland/netstack/node/src/streams.rs
+++ b/userland/netstack/node/src/streams.rs
@@ -256,8 +256,8 @@
                 // The connection's failure: the client's send pipe goes first, so that the end it
                 // reads finds no reader behind it.
                 (Err(Error::Failed(_)), _) => {
-                    self.from_client = None;
                     self.to_client = None;
+                    self.from_client = None;
                 }
                 (Err(Error::WouldBlock), _) => break,
                 (Err(refusal), _) => unreachable!("[tcp] refused the holder of a connection a read: {refusal:?}"),

m10-writer-leaving-unheard

diff --git a/userland/netstack/node/src/streams.rs b/userland/netstack/node/src/streams.rs
index 6e5bc08f4..bb9a6ea2c 100644
--- a/userland/netstack/node/src/streams.rs
+++ b/userland/netstack/node/src/streams.rs
@@ -443,7 +443,6 @@ impl Node {
                 stream.left = true;
             }
             // A finished send pipe was kept for its writer alone.
-            PipeEnd::FromClient if stream.fin_queued => stream.from_client = None,
             PipeEnd::FromClient => stream.done_writing = true,
         }
         self.bridge(now);

m2-fields-to-client-first

--- a/userland/netstack/node/src/streams.rs
+++ b/userland/netstack/node/src/streams.rs
@@ -157,8 +157,8 @@
     remote: Ipv4Addr,
     /// Before `to_client`, so a stream dropped whole lets its send pipe go first: the order a
     /// failure owes its client.
-    from_client: Option<Box<dyn FromClient>>,
     to_client: Option<Box<dyn ToClient>>,
+    from_client: Option<Box<dyn FromClient>>,
     /// The connect is not answered yet, and no byte moves.
     connecting: bool,
     /// The connect's, while connecting; then the cut's, once the client can see the stream no

m3-over-drops-send-pipe

--- a/userland/netstack/node/src/streams.rs
+++ b/userland/netstack/node/src/streams.rs
@@ -301,7 +301,7 @@
         // A connection that failed takes no more of the client's bytes: its writes fail rather
         // than fill a pipe nobody drains, and its end reads as a failure. One that ended in order
         // ended after the client's FIN, so its writing was over already.
-        if status.failure.is_some() {
+        if matches!(status.state, State::Closed | State::TimeWait) {
             self.from_client = None;
         }
         // A finished send pipe is kept only for a client that can still read the end and look

m4-fin-drops-send-pipe

--- a/userland/netstack/node/src/streams.rs
+++ b/userland/netstack/node/src/streams.rs
@@ -287,9 +287,7 @@
             if read == 0 {
                 stack.tcp_shutdown_write(now, conn);
                 self.fin_queued = true;
-                if writer_gone {
-                    self.from_client = None;
-                }
+                self.from_client = None;
                 break;
             }
             let Some(bytes) = space.get(..read) else { unreachable!("a pipe read {read} bytes into room for {room}") };

m5-kept-pipe-unwatched

--- a/userland/netstack/node/src/streams.rs
+++ b/userland/netstack/node/src/streams.rs
@@ -470,7 +470,7 @@
             let (from, to) = (stream.from_client.is_some(), stream.to_client.is_some());
             (*id, Watch {
                 readable: from && stream.room,
-                writer: from && (!stream.done_writing || stream.fin_queued),
+                writer: from && !stream.done_writing,
                 writable: to && stream.held,
                 reader: to,
             })

m6-close-keeps-pipe

--- a/userland/netstack/node/src/streams.rs
+++ b/userland/netstack/node/src/streams.rs
@@ -408,7 +408,6 @@
         }
         stream.to_client = None;
         stream.done_writing = true;
-        stream.left = true;
         self.bridge(now);
     }
 

m7-kept-pipe-timed

--- a/userland/netstack/node/src/streams.rs
+++ b/userland/netstack/node/src/streams.rs
@@ -316,7 +316,7 @@
         }
         // Nothing of the stream reaches its client again, alive or not, and its pipe still holds
         // what may be bytes to send.
-        if self.to_client.is_none() && self.done_writing && !self.fin_queued {
+        if self.to_client.is_none() && self.done_writing {
             if !self.extended {
                 let kept = extended.entry(self.remote).or_insert(0);
                 if *kept < OWNERLESS_PER_PEER {

m8-reader-gone-keeps-pipe

--- a/userland/netstack/node/src/streams.rs
+++ b/userland/netstack/node/src/streams.rs
@@ -438,10 +438,7 @@
     pub fn pipe_gone(&mut self, now: Instant, id: StreamId, end: PipeEnd) {
         let Some(stream) = self.streams.live.get_mut(&id).filter(|stream| !stream.connecting) else { return };
         match end {
-            PipeEnd::ToClient => {
-                stream.to_client = None;
-                stream.left = true;
-            }
+            PipeEnd::ToClient => stream.to_client = None,
             // A finished send pipe was kept for its writer alone.
             PipeEnd::FromClient if stream.fin_queued => stream.from_client = None,
             PipeEnd::FromClient => stream.done_writing = true,

m9-writer-leaving-takes-the-stream

diff --git a/userland/netstack/node/src/streams.rs b/userland/netstack/node/src/streams.rs
index 6e5bc08f4..1e3f090b8 100644
--- a/userland/netstack/node/src/streams.rs
+++ b/userland/netstack/node/src/streams.rs
@@ -443,7 +443,10 @@ impl Node {
                 stream.left = true;
             }
             // A finished send pipe was kept for its writer alone.
-            PipeEnd::FromClient if stream.fin_queued => stream.from_client = None,
+            PipeEnd::FromClient if stream.fin_queued => {
+                stream.from_client = None;
+                stream.to_client = None;
+            }
             PipeEnd::FromClient => stream.done_writing = true,
         }
         self.bridge(now);
Runner
#!/bin/zsh
# Each patch: checked apply, node tests, reverse; the tree is checked clean after each.
cd <home path> || exit 9
M=<home path>
echo "head $(git rev-parse HEAD)"
for p in $M/m1-*.patch $M/m2-*.patch $M/m3-*.patch $M/m4-*.patch $M/m5-*.patch $M/m6-*.patch $M/m7-*.patch $M/m8-*.patch $M/m9-*.patch $M/m10-*.patch control; do
  if [[ $p == control ]]; then name=control; git apply -R --check $M/control-node-change.patch || { echo "$name: does not apply"; exit 8; }; git apply -R $M/control-node-change.patch
  else name=$(basename $p .patch); git apply --check $p || { echo "$name: does not apply"; exit 8; }; git apply $p; fi
  log=$M/$name.log
  cargo test -p toyos-net-node --no-fail-fast > $log 2>&1; ex=$?
  if [[ $p == control ]]; then git apply $M/control-node-change.patch; else git apply -R $p; fi
  reds=$(grep -E '^test .* FAILED$' $log | sed -e 's/^test //' -e 's/ \.\.\. FAILED$//' | tr '\n' ' ')
  echo "$name EXIT=$ex red: $reds"
  [[ -z $(git status --porcelain) ]] || { echo "tree not clean after $name"; git status --porcelain; exit 7; }
done
echo DONE

@Japabu

Japabu commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator Author

Round 2 gates at 052679491, part 1: the node (157 passed, EXIT=0), [tcp] (385, EXIT=0), toyos-libc-copies (46, main's count, EXIT=0), clippy (24 invocations clean, EXIT=0). Runner: r2-gates.sh, each log's first lines are uptime, the head and the command.

r2-node.log
15:44  up 10 days,  3:28, 6 users, load averages: 15.51 21.42 25.33
052679491c939699dc21b16532c93181be65aa53
$ cargo test -p toyos-net-node --no-fail-fast
   Compiling toyos-net-node v0.1.0 (<worktree>/userland/netstack/node)
    Finished `test` profile [optimized + debuginfo] target(s) in 4.84s
     Running unittests src/lib.rs (target/debug/deps/toyos_net_node-aa2c64cc625e7067)

running 1 test
test lease::tests::the_log_lines_are_taken_and_the_report_before_them_keeps_its_turn ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running tests/datagram.rs (target/debug/deps/datagram-e86a84e5f5c26267)

running 9 tests
test a_clients_datagram_for_a_host_nobody_answers_for_is_dropped_and_the_client_is_told_nothing ... ok
test a_refusal_is_logged_by_the_call_that_met_it ... ok
test a_closed_socket_frees_its_port_and_what_it_accepted_still_leaves ... ok
test a_socket_holds_the_port_it_named_or_the_one_its_draw_picked ... ok
test a_broadcast_needs_its_permission ... ok
test a_datagram_accepted_for_a_host_is_no_broadcast_after_the_server_renews_a_narrower_prefix ... ok
test a_datagram_leaves_as_the_call_named_it ... ok
test a_datagram_for_a_bound_port_is_handed_over_with_where_it_came_from ... ok
test each_refusal_is_answered_in_the_pipes_word_for_it ... ok

test result: ok. 9 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running tests/host.rs (target/debug/deps/host-803046cba786c7e9)

running 1 test
test a_host_gives_a_connection_the_nodelay_its_listener_had_when_it_began ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running tests/lease.rs (target/debug/deps/lease-b278d53b751b0a1a)

running 20 tests
test a_message_with_no_link_is_counted_and_not_sent ... ok
test a_reply_without_the_cookie_is_refused_by_name ... ok
test a_probe_the_link_drops_under_is_abandoned ... ok
test a_conflict_under_the_probe_declines_and_holds_nothing ... ok
test a_lease_that_runs_out_under_the_probe_leaves_no_address ... ok
test a_second_conflict_takes_a_held_lease_and_declines_it ... ok
test a_link_that_returns_with_no_lease_starts_discovery_over_at_once ... ok
test a_link_that_returns_keeps_a_held_lease_and_announces_it ... ok
test a_nak_takes_address_route_and_resolvers_in_one_step ... ok
test a_router_ip_refuses_is_no_router ... ok
test a_renewed_prefix_reaches_ip_before_its_router ... ok
test a_renewal_for_a_server_nobody_answers_for_waits_and_is_dropped_and_the_client_is_told_nothing ... ok
test a_renewal_without_a_router_takes_the_route_and_keeps_the_address ... ok
test expiry_takes_address_route_and_resolvers_in_one_step ... ok
test next_deadline_is_the_earliest_of_the_clients_and_the_stacks ... ok
test no_cut_of_the_ack_is_an_ack ... ok
test no_frame_is_from_the_address_before_it_is_verified ... ok
test the_lease_end_to_end ... ok
test a_listener_stands_while_its_address_is_lost_and_answers_when_it_is_back ... ok
test no_flipped_bit_of_the_ack_yields_another_lease ... ok

test result: ok. 20 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.02s

     Running tests/listeners.rs (target/debug/deps/listeners-0155aace57c521d2)

running 31 tests
test a_listener_holds_a_place_and_a_listen_without_one_makes_nothing ... ok
test a_listen_at_every_address_takes_no_port_held_at_the_machines_address ... ok
test a_connect_between_two_accepts_is_queued_not_reset ... ok
test a_listener_is_at_the_address_it_named_and_only_one_the_machine_holds ... ok
test a_listener_ended_for_its_wake_gives_its_place_to_another_in_the_same_pass ... ok
test a_datagram_socket_holds_a_place_and_a_bind_without_one_makes_nothing ... ok
test a_listener_holds_the_option_its_listen_named_from_its_first_connection ... ok
test a_handshake_nobody_finishes_leaves_the_port_open_and_is_given_up ... ok
test a_connect_past_the_places_is_refused_and_sends_nothing ... ok
test a_listen_on_a_taken_port_is_refused_and_a_drawn_port_listens ... ok
test a_listener_answers_a_syn_and_wakes_its_owner_when_the_handshake_ends ... ok
test a_listen_at_the_machines_address_takes_no_port_held_at_every_address ... ok
test a_handshake_reset_before_it_ends_leaves_the_next_connection_its_listeners_option ... ok
test a_second_listen_at_the_machines_address_takes_no_port_held_there ... ok
test a_stream_reset_for_its_pipe_gives_its_place_to_a_connection_that_waits ... ok
test a_stream_starts_with_the_options_its_connection_took_from_its_listener ... ok
test a_wake_is_owed_only_for_a_connection_there_is_a_place_for ... ok
test a_wake_left_by_a_connection_its_peer_reset_stands_for_the_next ... ok
test a_request_for_a_stream_that_was_cut_names_nothing ... ok
test a_wake_the_owners_pipe_refuses_ends_the_listener ... ok
test an_accept_answers_the_option_of_a_stream_its_own_pass_let_go ... ok
test a_departed_clients_connection_holds_its_place_until_it_is_cut ... ok
test an_accept_spends_a_wake_whatever_it_answers ... ok
test closing_a_connect_gives_its_place_to_a_connection_that_waits ... ok
test closing_a_listener_gives_its_place_to_a_connection_that_waits_at_another ... ok
test more_places_wake_the_owner_of_a_connection_that_waits ... ok
test closing_a_listener_resets_what_waits_and_frees_its_port ... ok
test no_cut_of_a_syn_is_a_syn ... ok
test peers_at_more_addresses_than_there_are_places_hold_no_stream_past_them ... ok
test an_accepted_stream_is_one_of_its_peers_addresss_sixteen ... ok
test no_flipped_bit_of_a_syn_wakes_an_owner_or_ends_a_listener ... ok

test result: ok. 31 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s

     Running tests/name.rs (target/debug/deps/name-8c603c20311b1485)

running 19 tests
test a_conflicting_response_received_as_the_probing_ends_takes_the_name ... ok
test an_answer_to_a_link_local_asker_goes_to_its_own_link_address ... ok
test a_later_probe_of_another_hosts_makes_the_node_wait_a_second_and_probe_again ... ok
test a_name_has_no_link_while_the_link_is_down_and_the_links_return_is_probed_on ... ok
test every_message_leaves_with_ttl_255 ... ok
test a_query_in_a_frame_to_the_groups_address_reaches_the_responder ... ok
test a_held_name_is_probed_for_again_when_the_link_returns_and_announced_only_then ... ok
test a_query_from_off_the_link_is_not_answered ... ok
test an_answer_goes_to_the_group_or_to_the_asker_as_the_query_asked ... ok
test a_second_query_inside_a_second_waits_for_the_second_to_end ... ok
test a_held_lease_is_probed_for_three_times_and_its_name_announced_only_then ... ok
test a_held_name_is_defended_and_probed_for_again_when_another_host_answers_for_it ... ok
test an_answer_udp_refuses_is_counted_and_dropped ... ok
test another_hosts_answer_under_the_probe_takes_the_name_and_the_node_says_so ... ok
test no_cut_of_a_query_is_answered ... ok
test the_first_probe_follows_the_delay_the_callers_draw_gives ... ok
test the_group_is_joined_and_its_membership_reported ... ok
test the_names_port_is_held_by_one_socket ... ok
test the_name_is_answered_only_while_its_lease_is_held ... ok

test result: ok. 19 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running tests/resolve.rs (target/debug/deps/resolve-94085b0fb47f9deb)

running 33 tests
test a_lookup_udp_refuses_every_resolver_of_ends_in_the_call_that_started_it ... ok
test a_query_is_rfc_1035s_octets_from_a_port_and_with_an_id_of_its_own_draws ... ok
test a_lookup_let_go_after_it_ended_takes_its_answer_with_it ... ok
test a_query_for_a_resolver_ip_has_given_up_ends_its_lookup_in_the_opportunity_that_would_have_carried_it ... ok
test a_query_holds_its_port_while_its_answer_is_read_and_an_ended_lookup_holds_none ... ok
test a_lookup_ends_when_its_leases_resolvers_change ... ok
test a_lookup_ends_when_its_lease_is_lost ... ok
test a_lookup_is_not_carried_by_a_later_ones_schedule ... ok
test a_forged_report_of_the_querys_own_addresses_and_ports_ends_nothing_and_the_answer_behind_it_is_taken ... ok
test a_lookup_outlives_a_renewal_that_names_the_same_resolvers ... ok
test a_lookup_with_no_resolver_to_ask_is_refused_as_not_connected ... ok
test a_clients_datagrams_behind_the_burst_wait_in_their_own_socket_and_take_no_querys_place ... ok
test a_burst_at_a_resolver_not_yet_resolved_is_on_the_wire_once_the_resolver_answers_arp ... ok
test a_lookup_let_go_frees_its_ports_and_its_place_at_once ... ok
test a_query_its_resolver_refuses_by_icmp_is_counted_and_the_next_resolver_is_asked_at_once ... ok
test a_query_the_network_reports_unreachable_is_counted_and_ends_a_lookup_with_no_other_resolver ... ok
test a_query_udp_refuses_is_counted_holds_no_port_and_the_next_resolver_is_asked_at_once ... ok
test a_recorded_reply_of_a_public_resolver_answers_the_nodes_query ... ok
test a_reply_from_port_0_is_not_read ... ok
test a_reply_is_read_only_from_port_53 ... ok
test a_reply_is_read_only_at_the_address_its_query_left_from ... ok
test a_reply_is_read_only_from_the_resolver_its_query_went_to ... ok
test a_reply_on_another_querys_port_is_not_read ... ok
test a_report_that_is_not_about_the_query_or_is_a_hint_ends_no_wait ... ok
test a_resolver_that_never_answers_is_asked_at_each_waits_end ... ok
test a_resolver_the_lease_names_twice_is_asked_once_a_wait_whatever_it_reports ... ok
test an_alias_answered_late_restarts_the_lookup_and_lets_the_old_names_queries_go ... ok
test forged_reports_and_another_resolvers_failures_end_nothing_and_the_answer_behind_them_is_taken ... ok
test reports_alone_end_no_lookup_and_ask_no_resolver_twice_inside_a_wait ... ok
test the_query_asked_in_a_dropped_querys_place_leaves_in_the_same_opportunity ... ok
test nothing_but_its_answer_ends_a_lookup ... ok
test the_lookup_past_the_cap_is_refused_until_an_answer_is_taken ... ok
test a_query_with_no_port_to_leave_from_ends_its_lookup_by_name ... ok

test result: ok. 33 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running tests/slirp.rs (target/debug/deps/slirp-093165c007ff89fc)

running 3 tests
test slirps_offer_is_answered_with_a_request_for_what_it_offered ... ok
test slirps_ack_is_the_lease_its_bytes_name ... ok
test the_lease_is_read_from_the_acks_own_mask ... ok

test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running tests/streams.rs (target/debug/deps/streams-1e6b36f14ca6826f)

running 40 tests
test a_connect_nobody_answers_arp_for_fails_when_tcp_knows ... ok
test a_connect_before_the_lease_is_refused_and_sends_nothing ... ok
test a_client_that_left_is_finished_by_the_stack ... ok
test a_connect_is_a_handshake_and_its_answer_names_the_port ... ok
test a_close_sends_what_the_pipe_held_and_then_the_fin ... ok
test a_close_with_text_unread_resets ... ok
test a_connect_closed_before_its_answer_is_answered_closed ... ok
test a_finished_send_pipe_goes_with_its_reader ... ok
test a_connect_past_its_deadline_is_timed_out_at_the_deadline ... ok
test a_frame_moves_an_established_stream_with_no_opportunity_after_it ... ok
test a_full_pipe_keeps_the_rest_in_the_stack_and_is_watched_for_room ... ok
test a_client_done_writing_after_the_peers_fin_has_100_seconds_without_progress ... ok
test a_reader_that_left_is_not_written_to_again ... ok
test a_deadline_moves_an_established_stream_with_no_opportunity_after_it ... ok
test a_refused_connect_is_answered_with_the_refusal ... ok
test a_refused_watch_resets_only_a_stream_that_holds_the_end ... ok
test a_reset_ends_both_pipes_and_the_stream ... ok
test a_reset_after_the_peers_fin_ends_the_send_pipe ... ok
test a_shutdown_after_the_peers_fin_keeps_the_send_pipe_past_the_connections_end ... ok
test a_reset_after_the_clients_shutdown_lets_the_send_pipe_go_first ... ok
test a_shutdown_sends_what_the_pipe_held_and_then_the_fin ... ok
test a_pipe_that_is_no_pipe_resets_its_connection ... ok
test a_live_client_that_is_done_writing_is_not_timed ... ok
test a_writer_that_leaves_after_the_shutdown_leaves_the_stream_to_its_reader ... ok
test an_established_streams_connect_deadline_is_gone ... ok
test nodelay_reaches_the_stack ... ok
test text_the_pipe_had_no_room_for_outlives_both_fins ... ok
test the_end_of_the_clients_writing_is_a_fin_and_the_peer_still_sends ... ok
test the_peers_fin_after_the_clients_shutdown_leaves_the_send_pipe_to_its_writer ... ok
test the_peers_fin_ends_the_clients_reading_and_not_its_writing ... ok
test room_in_the_pipe_opens_the_window_at_the_next_opportunity ... ok
test a_departed_clients_unsent_bytes_have_100_seconds ... ok
test nothing_leaves_the_pipe_that_the_stack_will_not_take ... ok
test nothing_leaves_the_stack_that_the_pipe_did_not_take ... ok
test a_departed_clients_tail_arrives_whole_while_its_peer_takes_it ... ok
test no_cut_of_a_segment_is_a_segment ... ok
test a_peer_at_another_address_keeps_its_own_sixteen ... ok
test no_flipped_bit_of_a_segment_delivers_other_bytes ... ok
test a_departed_clients_connection_past_the_sixteen_is_one_of_them_once_one_is_done ... ok
test a_peer_keeps_sixteen_departed_clients_connections_alive_and_no_more ... ok

test result: ok. 40 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.06s

EXIT=0
r2-tcp.log
15:44  up 10 days,  3:28, 6 users, load averages: 16.21 21.39 25.27
052679491c939699dc21b16532c93181be65aa53
$ cargo test -p toyos-net-tcp --no-fail-fast
    Finished `test` profile [optimized + debuginfo] target(s) in 0.11s
     Running unittests src/lib.rs (target/debug/deps/toyos_net_tcp-99e833a79442f590)

running 27 tests
test cc::tests::s_cc_009_cubic_values ... ok
test cc::tests::s_cc_010_congestion_events ... ok
test cc::tests::s_cc_007_slow_start_stops_at_ssthresh ... ok
test cc::tests::s_cc_011_reno_friendly_region ... ok
test cc::tests::s_cc_008_the_epoch_after_a_timeout ... ok
test cc::tests::s_cc_014_flight_not_cwnd_at_a_timeout ... ok
test cc::tests::s_cc_013_application_limited_time_is_skipped ... ok
test cc::tests::s_cc_015_prop_avoidance_growth_is_bounded ... ok
test rtt::tests::s_rt_001_first_sample ... ok
test rtt::tests::s_rt_002_later_sample ... ok
test rtt::tests::s_rt_003_the_200_ms_floor ... ok
test rtt::tests::s_rt_004_the_60_s_ceiling ... ok
test rtt::tests::s_rt_012_appendix_g_weights ... ok
test tx::tests::s_lr_015_the_scoreboard_keeps_the_lowest_64 ... ok
test props::s_op_025_prop_timestamps_on_every_segment ... ok
test props::s_rx_026_prop_sacked_bytes_are_delivered ... ok
test props::s_rt_017_prop_no_retransmission_before_an_rto ... ok
test props::s_prop_008_nothing_happens_before_a_deadline ... ok
test props::s_prop_005_the_scoreboard_stays_above_snd_una ... ok
test props::s_mod_004_prop_urgent_is_never_sent ... ok
test props::s_prop_004_the_right_edge_never_retreats ... ok
test props::s_prop_002_delivered_data_is_a_prefix ... ok
test props::s_prop_001_snd_una_at_or_before_snd_nxt_whatever_the_next_hop_answers ... ok
test props::s_prop_001_snd_una_at_or_before_snd_nxt ... ok
test props::s_prop_007_pipe_is_bounded ... ok
test props::s_prop_006_totality_against_an_adversary ... ok
test props::s_prop_003_end_of_stream_after_every_byte ... ok

test result: ok. 27 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.90s

     Running tests/acceptability.rs (target/debug/deps/acceptability-08bd6146f17e566f)

running 27 tests
test s_ac_006_empty_segment_beyond_the_window ... ok
test s_ac_002_at_the_right_edge ... ok
test s_ac_013_syn_is_challenged ... ok
test s_ac_011_rst_outside_the_window ... ok
test s_ac_001_in_order_text_is_acknowledged_late ... ok
test s_ac_010_inexact_rst_is_challenged ... ok
test s_ac_007_front_trim ... ok
test s_ac_014_syn_beyond_the_window ... ok
test s_ac_012_challenge_acks_are_limited ... ok
test s_ac_009_exact_rst ... ok
test s_ac_015_retransmitted_synack ... ok
test s_ac_005_zero_window_rst ... ok
test s_ac_003_zero_window_probe ... ok
test s_ac_016_ack_of_unsent_data ... ok
test s_ac_019_text_without_ack ... ok
test s_ac_021_a_window_may_shrink ... ok
test s_ac_008_back_trim ... ok
test s_ac_017_rfc5961_lower_bound ... ok
test s_ac_018_data_with_the_old_ack_is_no_duplicate ... ok
test s_ac_020_old_segments_do_not_update_the_window ... ok
test s_ac_022_max_snd_wnd ... ok
test s_ac_023_one_ack_per_receive_pass ... ok
test s_ac_004_zero_window_pure_ack ... ok
test s_ac_024_text_after_the_peer_fin ... ok
test s_ac_025_ack_half_the_space_away ... ok
test s_ac_026_rst_with_fin ... ok
test s_ac_027_rst_payload_is_ignored ... ok

test result: ok. 27 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running tests/close.rs (target/debug/deps/close-90a513e18443bf8e)

running 32 tests
test s_cl_012_the_orphan_bound ... ok
test s_cl_005_the_fin_rides_on_data ... ok
test s_cl_008_sending_in_close_wait ... ok
test s_cl_011_an_orphan_in_fin_wait_2 ... ok
test s_cl_009_a_retransmitted_fin_in_close_wait ... ok
test s_cl_003_simultaneous_close ... ok
test s_cl_013_an_orphan_behind_a_shut_window ... ok
test s_cl_007_half_close ... ok
test s_cl_004_fin_acknowledging_ours ... ok
test s_cl_006_the_fin_is_retransmitted ... ok
test s_cl_001_active_close ... ok
test s_cl_002_passive_close ... ok
test s_cl_010_a_retransmitted_fin_restarts_time_wait ... ok
test s_cl_015_data_for_an_orphan ... ok
test s_cl_016_close_with_unread_data ... ok
test s_cl_017_time_wait_is_60_s ... ok
test s_cl_018_reopening_from_time_wait_by_timestamp ... ok
test s_cl_019_reopening_by_sequence_number ... ok
test s_cl_020_rst_in_time_wait_is_ignored ... ok
test s_cl_024_abort ... ok
test s_cl_014_a_user_keeps_a_shut_window_open ... ok
test s_cl_022_syn_in_time_wait_without_a_listener ... ok
test s_cl_025_abort_without_rst ... ok
test s_cl_027_the_reset_stub ... ok
test s_cl_028_close_in_syn_sent ... ok
test s_cl_029_shutdown_read ... ok
test s_cl_026_no_time_wait_after_a_reset ... ok
test s_cl_030_last_ack_waits_for_the_fin ... ok
test s_cl_031_rst_in_close_wait ... ok
test s_cl_032_rst_in_last_ack_and_closing ... ok
test s_cl_021_a_failed_reopening_returns_to_time_wait ... ok
test s_cl_023_time_wait_is_bounded ... ok

test result: ok. 32 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.09s

     Running tests/congestion.rs (target/debug/deps/congestion-a0156dbb7474d796)

running 7 tests
test s_cc_006_no_growth_when_not_cwnd_limited ... ok
test s_cc_003_ten_segments_of_536 ... ok
test s_cc_001_ten_segments ... ok
test s_cc_002_ten_segments_with_timestamps ... ok
test s_cc_004_one_segment_after_two_syn_retransmissions ... ok
test s_cc_005_slow_start ... ok
test s_cc_012_restart_after_idle ... ok

test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running tests/egress.rs (target/debug/deps/egress-962cbeb65a3d5e7e)

running 25 tests
test s_pl_009_a_handshake_gives_up_without_credit ... ok
test s_pl_009_give_up_without_credit ... ok
test s_ka_003_probes_that_cannot_leave_still_give_up ... ok
test s_pl_003_unsent_is_not_in_flight ... ok
test s_ka_002_a_segment_cancels_a_probe_still_owed ... ok
test s_pl_001_the_timer_starts_at_hand_off ... ok
test s_pl_007_credit_bounds_an_opportunity ... ok
test s_pl_008_resets_go_first ... ok
test a_freed_connection_leaves_no_turn_behind ... ok
test s_hs_034_a_child_whose_synack_cannot_leave_gives_up ... ok
test s_pl_004_owed_acks_collapse ... ok
test s_pl_006_a_segment_carries_the_state_of_its_moment ... ok
test s_pl_002_no_expiry_for_a_retransmission_that_never_left ... ok
test s_pl_005_at_most_three_duplicate_acks_are_held ... ok
test s_pl_010_give_up_before_retransmission ... ok
test s_pl_012_a_connection_freed_while_waiting_leaves_no_turn_behind ... ok
test s_pl_012_a_flow_builds_nothing_while_its_next_hop_is_pending ... ok
test s_pl_012_a_window_update_offers_nothing_until_it_leaves ... ok
test s_pl_012_an_ack_owed_in_syn_received_waits_for_its_next_hop ... ok
test s_pl_012_an_owed_ack_waits_for_its_next_hop ... ok
test s_pl_013_a_local_host_unreachable_fails_a_connect_at_once ... ok
test s_pl_014_an_owed_ack_outlasts_an_unreachable_next_hop ... ok
test s_pl_014_a_synchronized_connection_records_host_unreachable_soft ... ok
test s_pl_015_what_is_owed_outside_a_connection_waits_for_its_next_hop ... ok
test s_pl_015_resets_waiting_for_their_next_hop_stay_bounded ... ok

test result: ok. 25 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running tests/handshake.rs (target/debug/deps/handshake-0f5451ee5d775226)

running 42 tests
test s_hs_009_synsent_refused ... ok
test s_hs_014_synsent_fin ... ok
test s_hs_004_passive_open_full_peer ... ok
test s_hs_010_synsent_rst_without_ack ... ok
test s_hs_011_synsent_rst_with_a_bad_ack ... ok
test s_hs_003_passive_open_bare_peer ... ok
test s_hs_013_synsent_bad_ack_without_syn ... ok
test s_hs_006_simultaneous_open_synack_lost ... ok
test s_hs_005_simultaneous_open ... ok
test s_hs_002_active_open_full_peer ... ok
test s_hs_008_synsent_ack_past_snd_nxt ... ok
test s_hs_012_synsent_ack_without_syn ... ok
test s_hs_007_synsent_ack_at_iss ... ok
test s_hs_001_active_open_bare_peer ... ok
test s_hs_015_syn_give_up ... ok
test s_hs_016_one_syn_retransmission ... ok
test s_hs_017_two_syn_retransmissions ... ok
test s_hs_018_syn_payload_is_discarded ... ok
test s_hs_019_synack_payload_is_discarded ... ok
test s_hs_020_listen_rst ... ok
test s_hs_021_listen_ack ... ok
test s_hs_022_listen_synack ... ok
test s_hs_023_listen_without_syn ... ok
test s_hs_024_duplicate_syn_in_syn_received ... ok
test s_hs_025_new_syn_in_syn_received ... ok
test s_hs_026_child_bad_ack ... ok
test s_hs_027_child_rst ... ok
test s_hs_028_active_syn_received_rst ... ok
test s_hs_029_child_fin ... ok
test s_hs_030_fin_waits_for_established ... ok
test s_hs_031_child_text_without_ack ... ok
test s_hs_032_child_inexact_rst ... ok
test s_hs_033_child_completes_with_data ... ok
test s_hs_034_synack_give_up ... ok
test s_hs_035_connect_needs_a_unicast_remote ... ok
test s_hs_037_the_initial_window_is_ten_smss ... ok
test s_hs_038_data_queued_in_syn_sent ... ok
test s_hs_039_active_syn_received_bad_ack ... ok
test s_hs_040_synack_retransmitted ... ok
test s_hs_041_no_socket ... ok
test s_hs_042_no_socket_reset_limit ... ok
test s_hs_043_calls_in_the_wrong_state ... ok

test result: ok. 42 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running tests/held.rs (target/debug/deps/held-8a7311b9115459cc)

running 6 tests
test ready_counts_the_connections_accept_has_yet_to_return ... ok
test options_are_the_ones_the_connection_has ... ok
test an_orphan_is_counted_until_its_idle_reset ... ok
test a_close_that_resets_leaves_no_orphan ... ok
test an_orphan_is_counted_until_both_fins ... ok
test a_close_before_the_handshake_ends_leaves_an_orphan_until_it_gives_up ... ok

test result: ok. 6 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running tests/icmp.rs (target/debug/deps/icmp-8b786e685d8f926e)

running 21 tests
test s_ic_002_a_stale_quote ... ok
test s_ic_001_port_unreachable_refuses_a_connect ... ok
test s_ic_003_host_unreachable_is_soft ... ok
test s_ic_004_administratively_prohibited ... ok
test s_ic_011_path_mtu_lowered ... ok
test s_ic_014_the_mtu_floor ... ok
test s_ic_009_parameter_problem_is_soft ... ok
test s_ic_010_an_unassigned_code_is_soft ... ok
test s_ic_005_soft_in_a_synchronized_state ... ok
test s_ic_012_no_next_hop_mtu ... ok
test s_ic_013_an_mtu_that_is_no_smaller ... ok
test s_ic_006_port_unreachable_is_soft_when_synchronized ... ok
test s_ic_007_quotes_outside_the_flight ... ok
test s_ic_008_time_exceeded_is_soft ... ok
test s_ic_015_an_mtu_not_below_the_refused_datagram ... ok
test s_ic_018_a_listener_is_never_addressed ... ok
test s_ic_016_one_retransmission_per_lowering ... ok
test s_ic_017_time_wait_is_stale ... ok
test s_ic_019_a_pending_child_is_deleted ... ok
test s_ic_020_progress_clears_a_soft_error ... ok
test s_ic_021_path_mtu_with_timestamps ... ok

test result: ok. 21 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running tests/isn.rs (target/debug/deps/isn-4c2818daebf3af43)

running 9 tests
test s_isn_001_siphash_reference_vectors ... ok
test s_isn_003_the_4us_clock ... ok
test s_isn_004_every_field_of_the_tuple_counts ... ok
test s_isn_005_the_key_counts ... ok
test s_isn_006_clock_wrap ... ok
test s_isn_002_isn_at_clock_zero ... ok
test s_isn_008_timestamp_offset ... ok
test s_isn_009_incarnations_share_the_offset ... ok
test s_isn_007_prop_unpredictable ... ok

test result: ok. 9 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running tests/listen.rs (target/debug/deps/listen-416005a2d313f4e2)

running 11 tests
test s_ls_001_one_listener_per_endpoint ... ok
test s_ls_002_connections_do_not_hold_the_port ... ok
test s_ls_007_a_reset_child_is_never_returned ... ok
test s_ls_009_the_specific_address_first ... ok
test s_ls_006_a_closed_child_is_accepted ... ok
test s_ls_011_a_bound_port_without_a_listener ... ok
test s_ls_008_closing_a_listener_resets_its_children ... ok
test s_ls_010_children_inherit_options ... ok
test s_ls_005_first_in_first_out ... ok
test s_ls_004_the_ready_bound ... ok
test s_ls_003_the_pending_bound ... ok

test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running tests/modern.rs (target/debug/deps/modern-ac368f3cc82e0909)

running 14 tests
test s_mod_001_syn_with_fin ... ok
test s_mod_007_fast_open ... ok
test s_mod_002_syn_with_rst ... ok
test s_mod_006_tcp_md5 ... ok
test s_div_003_rst_at_snd_nxt_inside_the_window ... ok
test s_mod_003_urgent_data_inline ... ok
test s_div_002_rst_below_the_offered_edge_behind_snd_una ... ok
test s_div_001_rst_never_below_snd_una ... ok
test s_div_002_rst_at_a_shrunk_edge ... ok
test s_mod_009_out_of_range_acks_are_logged_per_10_s ... ok
test s_mod_008_challenges_are_logged_once ... ok
test s_div_001_rst_at_the_right_edge ... ok
test s_mod_005_one_line_per_rule_per_10_s ... ok
test s_mod_005_a_bulk_peer_leaves_room_for_refusals ... ok

test result: ok. 14 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running tests/net.rs (target/debug/deps/net-b73d1001768f0962)

running 13 tests
test s_net_011_the_fin_overtakes_the_last_data ... ok
test s_net_014_an_abort_is_a_reset ... ok
test s_net_005_lost_acks ... ok
test s_ls_012_a_syn_flood ... ok
test s_net_012_a_slow_reader ... ok
test s_net_016_a_peer_without_options ... ok
test s_net_010_a_lost_retransmission ... ok
test s_net_002_newreno_when_one_side_has_no_sack ... ok
test s_net_008_timestamps_wrap ... ok
test s_net_009_the_last_segment_of_each_burst ... ok
test s_net_007_sequence_numbers_wrap ... ok
test s_net_015_nagle_and_the_delayed_ack ... ok
test s_net_013_a_hundred_connections ... ok

test result: ok. 13 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.63s

     Running tests/options.rs (target/debug/deps/options-3a2766467a740697)

running 30 tests
test s_op_001_the_active_syn ... ok
test s_op_013_a_synack_window_is_never_scaled ... ok
test s_op_012_window_scale_outside_a_syn_is_ignored ... ok
test s_op_004_mss_zero_is_raised ... ok
test s_op_009_window_scaling ... ok
test s_op_007_the_interface_bounds_the_mss ... ok
test s_op_003_synack_to_a_full_syn ... ok
test s_op_010_shift_above_14_is_clamped ... ok
test s_op_006_no_mss_option ... ok
test s_op_011_unscaled_when_the_peer_offers_none ... ok
test s_op_005_mss_100_is_raised ... ok
test s_op_015_timestamps_unnegotiated ... ok
test s_op_008_timestamps_take_twelve_bytes ... ok
test s_op_016_timestamps_on_an_unnegotiated_connection ... ok
test s_op_014_sack_unnegotiated ... ok
test s_op_002_synack_to_a_bare_syn ... ok
test s_op_017_ts_recent_needs_seq_at_or_before_last_ack_sent ... ok
test s_op_018_delayed_ack_echoes_the_earliest ... ok
test s_op_019_ts_across_a_hole ... ok
test s_op_020_paws ... ok
test s_op_021_missing_timestamps_are_dropped ... ok
test s_op_022_no_paws_on_an_rst ... ok
test s_op_023_ts_recent_expires_after_24_days ... ok
test s_op_024_rst_for_no_socket_echoes_the_timestamp ... ok
test s_op_026_mss_outside_a_syn_is_ignored ... ok
test s_op_027_ecn_is_not_negotiated ... ok
test s_op_028_sack_blocks_in_a_syn_are_ignored ... ok
test s_op_031_the_shift_fits_the_buffer ... ok
test s_op_029_the_last_mss_wins ... ok
test s_op_032_tsecr_without_ack_is_ignored ... ok

test result: ok. 30 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running tests/port.rs (target/debug/deps/port-4aecde1497a45557)

running 8 tests
test s_port_007_never_a_self_connection ... ok
test s_port_002_another_destination_another_counter ... ok
test s_port_003_a_listening_port_is_skipped ... ok
test s_port_001_algorithm_4 ... ok
test s_port_004_time_wait_holds_its_4_tuple ... ok
test s_port_006_algorithm_2_for_a_listener ... ok
test s_port_005_exhaustion ... ok
test s_port_008_prop_ports ... ok

test result: ok. 8 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s

     Running tests/receive.rs (target/debug/deps/receive-318f2d6927df8d4d)

running 26 tests
test s_rx_001_every_second_segment ... ok
test s_rx_006_a_later_range_remains ... ok
test s_rx_005_a_filled_hole_is_acknowledged_at_once ... ok
test s_rx_002_the_delayed_ack_is_40_ms ... ok
test s_rx_009_a_fin_beyond_a_hole ... ok
test s_rx_011_unread_bytes_close_the_window ... ok
test s_rx_007_stored_bytes_win ... ok
test s_rx_003_out_of_order_is_acknowledged_at_once ... ok
test s_rx_010_nothing_after_a_remembered_fin ... ok
test s_rx_004_duplicate_acks_are_not_coalesced ... ok
test s_rx_014_the_edge_holds_while_a_hole_is_open ... ok
test s_rx_016_the_ack_rides_on_data ... ok
test s_rx_013_a_window_update_when_the_window_was_small ... ok
test s_rx_012_receiver_silly_window_avoidance ... ok
test s_rx_017_sack_blocks ... ok
test s_rx_015_the_offered_edge_is_honoured_under_scaling ... ok
test s_rx_018_the_triggering_range_first ... ok
test s_rx_019_three_blocks_with_timestamps ... ok
test s_rx_008_at_most_32_ranges ... ok
test s_rx_020_dsack_below_rcv_nxt ... ok
test s_rx_021_dsack_inside_a_range ... ok
test s_rx_022_no_sack_unnegotiated ... ok
test s_rx_023_a_reset_is_never_end_of_stream ... ok
test s_rx_024_data_then_end ... ok
test s_rx_025_push_is_not_a_boundary ... ok
test s_rx_027_half_close_keeps_receiving ... ok

test result: ok. 26 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running tests/recovery.rs (target/debug/deps/recovery-b574193b8d932082)

running 22 tests
test s_lr_006_no_fast_retransmit_after_a_timeout ... ok
test s_lr_007_inflation_is_capped ... ok
test s_lr_009_is_lost_on_the_first_ack ... ok
test s_lr_014_invalid_blocks_and_dsack ... ok
test s_lr_010_a_second_hole ... ok
test s_lr_003_newreno_partial_ack ... ok
test s_lr_012_rescue_retransmission ... ok
test s_lr_011_new_data_during_sack_recovery ... ok
test s_lr_002_limited_transmit_and_inflation ... ok
test s_lr_001_newreno_one_loss ... ok
test s_lr_013_the_recovery_point ... ok
test s_lr_004_full_ack_after_limited_transmit ... ok
test s_lr_008_sack_recovery ... ok
test s_lr_016_timeout ... ok
test s_lr_005_what_is_not_a_duplicate ... ok
test s_lr_017_a_second_timeout ... ok
test s_lr_018_no_sack_recovery_before_the_timeout_point ... ok
test s_lr_019_sacks_after_a_timeout_skip_held_ranges ... ok
test s_lr_020_sacked_bytes_stay_queued ... ok
test s_lr_021_pure_duplicates_without_blocks ... ok
test s_lr_022_limited_transmit_by_pipe ... ok
test s_lr_023_a_lost_fast_retransmission ... ok

test result: ok. 22 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running tests/rtt.rs (target/debug/deps/rtt-d31542d133375b39)

running 11 tests
test s_rt_011_an_echo_from_the_future ... ok
test s_rt_006_three_seconds_after_a_lost_syn ... ok
test s_rt_009_timestamp_samples ... ok
test s_rt_008_one_segment_timed ... ok
test s_rt_005_the_minimum_is_200_ms ... ok
test s_rt_013_backoff_doubles ... ok
test s_rt_015_the_timer_restarts_on_new_acks ... ok
test s_rt_007_karn ... ok
test s_rt_016_no_timer_when_all_is_acknowledged ... ok
test s_rt_014_three_expiries_forget_the_estimate ... ok
test s_rt_010_a_retransmission_echoed ... ok

test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running tests/send.rs (target/debug/deps/send-ac3312157674b9ac)

running 21 tests
test s_tx_001_one_segment_with_push ... ok
test s_tx_012_no_send_after_shutdown ... ok
test s_tx_010_the_initial_window_bounds_a_burst ... ok
test s_tx_006_silly_window_override ... ok
test s_tx_011_the_send_buffer_is_bounded ... ok
test s_tx_007_an_opened_window_cancels_the_override ... ok
test s_tx_002_nagle ... ok
test s_tx_004_full_segments_then_wait ... ok
test s_tx_003_nodelay ... ok
test s_tx_005_push_only_where_the_queue_empties ... ok
test s_tx_009_a_shrunk_window ... ok
test s_tx_013_persist_backs_off ... ok
test s_tx_014_an_opened_window_ends_persist ... ok
test s_tx_008_half_the_largest_window ... ok
test s_tx_015_the_probe_byte_taken ... ok
test s_tx_018_the_fin_as_probe ... ok
test s_tx_017_an_unanswered_persist_gives_up ... ok
test s_tx_019_a_shut_window_stops_the_retransmission_timer ... ok
test s_tx_021_close_pushes ... ok
test s_tx_020_cwnd_does_not_arm_the_override ... ok
test s_tx_016_an_answered_persist_lives ... ok

test result: ok. 21 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running tests/seq.rs (target/debug/deps/seq-dd3e9ffdeb749621)

running 8 tests
test s_seq_003_half_the_space_is_unordered ... ok
test s_seq_004_ack_range_check ... ok
test s_seq_001_offsets ... ok
test s_seq_002_ordering_across_the_wrap ... ok
test s_seq_007_timestamps_compare_modularly ... ok
test s_seq_008_sending_across_the_wrap ... ok
test s_seq_006_acceptability_across_the_wrap ... ok
test s_seq_005_segment_length ... ok

test result: ok. 8 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running tests/take.rs (target/debug/deps/take-094ff4230f111b52)

running 5 tests
test with_nothing_held_the_reader_is_not_asked ... ok
test what_the_reader_did_not_take_stays_held_in_order ... ok
test text_unread_when_both_fins_ended_the_connection_is_still_taken ... ok
test a_take_owes_the_window_update_a_read_does ... ok
test text_across_the_rings_wrap_is_shown_in_two_pieces_and_none_is_lost ... ok

test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running tests/timers.rs (target/debug/deps/timers-4a1adec2f02af4b0)

running 15 tests
test s_ka_001_off_by_default ... ok
test s_ka_006_the_idle_time_is_settable ... ok
test s_ka_002_a_probe_after_two_hours ... ok
test s_gu_001_three_expiries_raise_advice ... ok
test s_ka_004_no_keepalive_with_data_outstanding ... ok
test s_gu_006_a_confirmation_after_a_reverify_is_kept ... ok
test s_ka_005_the_peer_keepalive_is_answered ... ok
test s_gu_006_progress_confirms_the_next_hop ... ok
test s_gu_006_b_advice_is_kept_per_address ... ok
test s_gu_005_user_timeout_ends_an_answered_persist ... ok
test s_gu_003_progress_restarts_the_give_up_clock ... ok
test s_ka_003_nine_unanswered_probes ... ok
test s_gu_002_give_up ... ok
test s_gu_004_user_timeout ... ok
test s_ka_007_close_wait_and_fin_wait_2 ... ok

test result: ok. 15 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests toyos_net_tcp

running 5 tests
test toyos-net-shard/tcp/src/lib.rs - compile_fail::s_op_030_tcp_cannot_choose_dscp_ecn_or_df (line 349) - compile fail ... ok
test toyos-net-shard/tcp/src/lib.rs - compile_fail::s_hs_036_a_connection_cannot_connect_again (line 323) - compile fail ... ok
test toyos-net-shard/tcp/src/lib.rs - compile_fail::s_hs_036_a_connection_cannot_connect_again (line 330) - compile fail ... ok
test toyos-net-shard/tcp/src/lib.rs - compile_fail::s_op_030_tcp_cannot_choose_dscp_ecn_or_df (line 342) ... ok
test toyos-net-shard/tcp/src/lib.rs - compile_fail::s_hs_036_a_connection_cannot_connect_again (line 316) ... ok

test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.62s

EXIT=0
r2-libc-copies.log
15:44  up 10 days,  3:29, 6 users, load averages: 16.04 21.26 25.21
052679491c939699dc21b16532c93181be65aa53
$ cargo test -p toyos-libc-copies --no-fail-fast
   Compiling toyos-libc-copies v0.1.0 (<worktree>/tests/libc-arch)
    Finished `test` profile [optimized + debuginfo] target(s) in 1.68s
     Running unittests src/lib.rs (target/debug/deps/toyos_libc_copies-ee3949aad8583456)

running 46 tests
test dladdr_symbols::an_image_holds_its_load_segment_and_starts_at_its_page ... ok
test descriptor_requests::fcntl_reads_an_int_argument_from_the_lower_half_of_its_register ... ok
test exact_hex::tests::the_formats_corners_and_ties_round_to_their_bits ... ok
test dladdr_symbols::either_count_reaches_the_symbol_at_its_address ... ok
test errno_codes::every_code_libc_answers_with_is_errno_h_s ... ok
test internet_addresses::a_number_with_a_zero_before_another_digit_is_no_dotted_quad ... ok
test internet_addresses::a_number_past_32_bits_or_without_digits_is_no_address ... ok
test internet_addresses::a_buffer_too_short_for_the_text_is_refused_as_the_host_refuses_it ... ok
test descriptor_requests::each_descriptor_keeps_its_own_close_on_exec_mark ... ok
test internet_addresses::a_sockaddr_holds_its_port_and_address_in_network_order ... ok
test fparts_differential::modf_is_the_host_libraries ... ok
test fparts_differential::logb_is_the_host_libraries_and_a_zero_its_pole ... ok
test internet_addresses::an_address_is_truncated_to_the_callers_buffer_as_the_host_truncates_it ... ok
test internet_addresses::another_family_is_refused_as_the_host_refuses_it ... ok
test descriptor_requests::f_dupfd_meets_its_floor_holding_one_duplicate_at_most ... ok
test link_requests::readlink_refuses_an_empty_buffer_and_one_no_ssize_t_counts ... ok
test listing_reader::a_name_fits_d_name_with_its_nul_or_is_refused ... ok
test internet_addresses::an_address_is_written_as_the_host_writes_it ... ok
test listing_reader::an_answer_is_asked_again_until_it_fits ... ok
test memory_refusals::a_length_names_the_pages_it_reaches_into ... ok
test memory_refusals::madvise_takes_linuxs_hints_and_refuses_its_discard ... ok
test memory_refusals::mmap_refuses_what_the_kernel_cannot_map_and_nothing_else ... ok
test memory_refusals::posix_madvise_takes_its_five_and_only_them ... ok
test poll_requests::a_descriptor_named_twice_is_watched_once_for_both_interests ... ok
test poll_requests::each_descriptor_named_once_has_its_own_watch ... ok
test signal_masks::each_how_changes_the_mask_as_posix_says ... ok
test signal_masks::sigaddset_adds_its_signal_alone ... ok
test signal_masks::the_full_set_holds_every_signal ... ok
test tests::the_square_roots_are_the_correctly_rounded_ones ... ok
test text_differential::every_signal_signal_h_numbers_has_glibcs_text ... ok
test strtonum_differential::the_grammar_s_corners_read_as_judged ... ok
test text_differential::strnlen_is_the_host_libraries ... ok
test utf8_differential::a_refusal_starts_the_next_character_afresh ... ok
test socket_options::an_options_value_crosses_as_the_hosts_does ... ok
test tests::a_disjoint_copy_agrees_either_way ... ok
test tests::every_fill_agrees_with_fill ... ok
test long_double::strtold_widens_as_compiler_builtins_does ... ok
test strtonum_differential::random_integers_read_as_the_host_s ... ok
test prototypes::every_prototype_is_a_definition_and_every_definition_is_declared ... ok
test tests::every_copy_agrees_with_copy_within ... ok
test strtonum_differential::random_decimal_numbers_round_as_the_host_s ... ok
test utf8_differential::every_sequence_to_four_bytes_agrees_with_from_utf8 ... ok
test exact_hex::tests::it_rounds_as_the_machine_does ... ok
test internet_addresses::a_dotted_quad_reads_as_the_host_reads_it ... ok
test internet_addresses::numbers_and_dots_read_as_the_host_reads_them ... ok
test strtonum_differential::random_hexadecimal_numbers_round_as_ieee_754_rounds_them ... ok

test result: ok. 46 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.30s

   Doc-tests toyos_libc_copies

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

EXIT=0
r2-clippy.log
15:44  up 10 days,  3:29, 6 users, load averages: 16.43 21.26 25.18
052679491c939699dc21b16532c93181be65aa53
$ cargo run -- --clippy
    Blocking waiting for file lock on package cache
    Blocking waiting for file lock on package cache
   Compiling toyos-build v0.1.0 (<worktree>)
    Finished `dev` profile [optimized + debuginfo] target(s) in 12.69s
     Running `target/debug/toyos-build --clippy`
13:45:04 === clippy: cargo clippy --workspace $GUESTS --all-targets --keep-going -- $ADOPTED -D warnings
    Checking toyos-abi v0.16.0 (<worktree>/toyos-abi)
    Checking toyos-net-wire v0.1.0 (<worktree>/toyos-net-wire)
    Checking toyos-elf v0.1.0 (<worktree>/toyos-elf)
    Checking toyos-fat32-check v0.1.0 (<worktree>/toyos-fat32/check)
    Checking toyos-wallclock v0.1.0 (<worktree>/toyos-wallclock)
    Checking toyos-tmpdir v0.1.0 (<worktree>/toyos-tmpdir)
   Compiling ring v0.17.14
    Checking toyos-elide v0.1.0 (<worktree>/toyos-elide)
    Checking toyos-xhci v0.1.0 (<worktree>/toyos-xhci)
    Checking toyos-keymap v0.1.0 (<worktree>/toyos-keymap)
    Checking toyos-gpt v0.1.0 (<worktree>/toyos-gpt)
    Checking bcachefs v0.1.0 (<worktree>/bcachefs)
    Checking toyos-update v0.1.0 (<worktree>/toyos-update)
    Checking toyos-tco v0.1.0 (<worktree>/toyos-tco)
    Checking toyos-blackbox v0.1.0 (<worktree>/toyos-blackbox)
    Checking toyos-osrelease v0.1.0 (<worktree>/toyos-osrelease)
    Checking toyos-fat32 v0.1.0 (<worktree>/toyos-fat32)
    Checking toyos-quiesce v0.1.0 (<worktree>/toyos-quiesce)
    Checking toyos-untrusted v0.1.0 (<worktree>/toyos-untrusted)
    Checking acpiserver-api v0.1.0 (<worktree>/userland/acpiserver-api)
    Checking toyos-device-memory v0.1.0 (<worktree>/toyos-device-memory)
    Checking kernel-loom v0.1.0 (<worktree>/kernel/loom)
    Checking toyos-net-ip v0.1.0 (<worktree>/toyos-net-ip)
    Checking toyos-net-tcp v0.1.0 (<worktree>/toyos-net-shard/tcp)
    Checking toyos-bootmap v0.1.0 (<worktree>/toyos-bootmap)
    Checking kernel v0.1.0 (<worktree>/kernel)
    Checking toyos-symbols v0.1.0 (<worktree>/toyos-symbols)
    Checking toyos-manifest v0.1.0 (<worktree>/toyos-manifest)
    Checking toyos-logstream v0.1.0 (<worktree>/toyos-logstream)
    Checking toyos-dhcp v0.1.0 (<worktree>/toyos-dhcp)
    Checking toyos-xhci-sim v0.1.0 (<worktree>/toyos-xhci/sim)
    Checking toyos-transport v0.1.0 (<worktree>/toyos-blockring/transport)
    Checking toyos-rootimage v0.1.0 (<worktree>/toyos-rootimage)
    Checking toyos-ps2 v0.1.0 (<worktree>/kernel/ps2)
    Checking toyos-userbound v0.1.0 (<worktree>/toyos-userbound)
    Checking toyos-acpi v0.1.0 (<worktree>/toyos-acpi)
    Checking toyos-net-udp v0.1.0 (<worktree>/toyos-net-udp)
    Checking toyos-blockhold v0.1.0 (<worktree>/toyos-blockhold)
    Checking kernel-sim v0.1.0 (<worktree>/kernel/sim)
    Checking toyos-gicv3 v0.1.0 (<worktree>/kernel/gicv3)
    Checking toyos-swap v0.1.0 (<worktree>/toyos-swap)
    Checking toyos-blockring v0.1.0 (<worktree>/toyos-blockring)
    Checking toyos-net-shard v0.1.0 (<worktree>/toyos-net-shard)
   Compiling rustls v0.23.45
    Checking toyos-net-testnet v0.1.0 (<worktree>/toyos-net-shard/testnet)
    Checking rustls-webpki v0.103.15
    Checking ureq v3.4.2
    Checking toyos-virtio v0.1.0 (<worktree>/toyos-virtio)
    Checking toyos-i219 v0.1.0 (<worktree>/toyos-i219)
    Checking toyos-build v0.1.0 (<worktree>)
    Checking toyos-libc-copies v0.1.0 (<worktree>/tests/libc-arch)
    Checking toyos-trace v0.1.0 (<worktree>/toyos-trace)
    Checking toyos-inspect v0.1.0 (<worktree>/toyos-inspect)
    Checking toyos-pci v0.1.0 (<worktree>/kernel/pci)
    Checking toyos-dma v0.1.0 (<worktree>/kernel/dma)
    Checking toyos-cpuvuln v0.1.0 (<worktree>/toyos-cpuvuln)
    Checking toyos-dns v0.1.0 (<worktree>/toyos-dns)
    Checking toyos-hda v0.1.0 (<worktree>/toyos-hda)
    Checking toyos-tsc v0.1.0 (<worktree>/toyos-tsc)
    Checking toyos-microcode v0.1.0 (<worktree>/toyos-microcode)
    Finished `dev` profile [optimized + debuginfo] target(s) in 14.88s
13:45:19 === clippy: cargo clippy --workspace $GUESTS --all-targets --keep-going --features $CONTROLS -- $ADOPTED -D warnings
    Checking kernel-loom v0.1.0 (<worktree>/kernel/loom)
    Checking kernel v0.1.0 (<worktree>/kernel)
    Checking toyos-transport v0.1.0 (<worktree>/toyos-blockring/transport)
    Checking toyos-blockring v0.1.0 (<worktree>/toyos-blockring)
    Checking kernel-sim v0.1.0 (<worktree>/kernel/sim)
    Checking toyos-build v0.1.0 (<worktree>)
    Finished `dev` profile [optimized + debuginfo] target(s) in 5.29s
13:45:25 === clippy: cargo clippy -p kernel --lib --tests --features sched-check -- $ADOPTED -D warnings
    Blocking waiting for file lock on package cache
    Checking kernel v0.1.0 (<worktree>/kernel)
    Finished `dev` profile [optimized + debuginfo] target(s) in 0.57s
13:45:25 === clippy: cargo clippy -p kernel --lib --tests --features sched-check,protocol-port --features $KERNEL_CONTROLS -- $ADOPTED -D warnings
    Checking kernel v0.1.0 (<worktree>/kernel)
    Finished `dev` profile [optimized + debuginfo] target(s) in 0.56s
13:45:26 === clippy: cargo clippy -p kernel --target x86_64-unknown-none -- $ADOPTED -D warnings
    Checking toyos-abi v0.16.0 (<worktree>/toyos-abi)
    Checking toyos-wallclock v0.1.0 (<worktree>/toyos-wallclock)
    Checking toyos-elf v0.1.0 (<worktree>/toyos-elf)
    Checking toyos-elide v0.1.0 (<worktree>/toyos-elide)
    Checking toyos-blackbox v0.1.0 (<worktree>/toyos-blackbox)
    Checking toyos-tco v0.1.0 (<worktree>/toyos-tco)
    Checking toyos-dma v0.1.0 (<worktree>/kernel/dma)
    Checking toyos-gicv3 v0.1.0 (<worktree>/kernel/gicv3)
    Checking toyos-tsc v0.1.0 (<worktree>/toyos-tsc)
    Checking toyos-xhci v0.1.0 (<worktree>/toyos-xhci)
    Checking bcachefs v0.1.0 (<worktree>/bcachefs)
    Checking toyos-blockhold v0.1.0 (<worktree>/toyos-blockhold)
    Checking toyos-rootimage v0.1.0 (<worktree>/toyos-rootimage)
    Checking toyos-untrusted v0.1.0 (<worktree>/toyos-untrusted)
    Checking toyos-gpt v0.1.0 (<worktree>/toyos-gpt)
    Checking toyos-hda v0.1.0 (<worktree>/toyos-hda)
    Checking toyos-fat32 v0.1.0 (<worktree>/toyos-fat32)
    Checking toyos-cpuvuln v0.1.0 (<worktree>/toyos-cpuvuln)
    Checking toyos-ps2 v0.1.0 (<worktree>/kernel/ps2)
    Checking toyos-quiesce v0.1.0 (<worktree>/toyos-quiesce)
    Checking toyos-bootmap v0.1.0 (<worktree>/toyos-bootmap)
    Checking toyos-pci v0.1.0 (<worktree>/kernel/pci)
    Checking toyos-symbols v0.1.0 (<worktree>/toyos-symbols)
    Checking toyos-acpi v0.1.0 (<worktree>/toyos-acpi)
    Checking toyos-userbound v0.1.0 (<worktree>/toyos-userbound)
    Checking kernel v0.1.0 (<worktree>/kernel)
    Finished `dev` profile [optimized + debuginfo] target(s) in 3.07s
13:45:29 === clippy: cargo clippy -p kernel --target x86_64-unknown-none --features boot-actuators,test-actuators -- $ADOPTED -D warnings
    Blocking waiting for file lock on package cache
    Checking kernel v0.1.0 (<worktree>/kernel)
    Finished `dev` profile [optimized + debuginfo] target(s) in 1.37s
13:45:31 === clippy: cargo clippy -p kernel --target x86_64-unknown-none --features boot-actuators -- $ADOPTED -D warnings
    Checking kernel v0.1.0 (<worktree>/kernel)
    Finished `dev` profile [optimized + debuginfo] target(s) in 1.34s
13:45:32 === clippy: cargo clippy -p kernel --target x86_64-unknown-none --features debug-wait,sched-check,sched-tripwire,heap-tripwire,heap-sweep,pass-spin,stack-witness,switch-witness,switch-witness-mutate-frame,switch-witness-mutate-rsp,df-witness,df-witness-mutate,entry-df-unclean,mask-windows -- $ADOPTED -D warnings
    Checking kernel v0.1.0 (<worktree>/kernel)
    Finished `dev` profile [optimized + debuginfo] target(s) in 1.33s
13:45:33 === clippy: cargo clippy -p kernel --target x86_64-unknown-none --features heap-band-notail,heap-lockspin -- $ADOPTED -D warnings
    Checking kernel v0.1.0 (<worktree>/kernel)
    Finished `dev` profile [optimized + debuginfo] target(s) in 1.32s
13:45:35 === clippy: cargo clippy -p kernel --target x86_64-unknown-none --features heap-band-nohead -- $ADOPTED -D warnings
    Checking kernel v0.1.0 (<worktree>/kernel)
    Finished `dev` profile [optimized + debuginfo] target(s) in 1.39s
13:45:36 === clippy: cargo clippy -p kernel --target aarch64-unknown-none-softfloat -- $ADOPTED -D warnings
    Checking toyos-abi v0.16.0 (<worktree>/toyos-abi)
    Checking toyos-elf v0.1.0 (<worktree>/toyos-elf)
    Checking toyos-wallclock v0.1.0 (<worktree>/toyos-wallclock)
    Checking toyos-elide v0.1.0 (<worktree>/toyos-elide)
    Checking bcachefs v0.1.0 (<worktree>/bcachefs)
    Checking toyos-ps2 v0.1.0 (<worktree>/kernel/ps2)
    Checking toyos-gpt v0.1.0 (<worktree>/toyos-gpt)
    Checking toyos-gicv3 v0.1.0 (<worktree>/kernel/gicv3)
    Checking toyos-quiesce v0.1.0 (<worktree>/toyos-quiesce)
    Checking toyos-tsc v0.1.0 (<worktree>/toyos-tsc)
    Checking toyos-blackbox v0.1.0 (<worktree>/toyos-blackbox)
    Checking toyos-untrusted v0.1.0 (<worktree>/toyos-untrusted)
    Checking toyos-dma v0.1.0 (<worktree>/kernel/dma)
    Checking toyos-hda v0.1.0 (<worktree>/toyos-hda)
    Checking toyos-rootimage v0.1.0 (<worktree>/toyos-rootimage)
    Checking toyos-tco v0.1.0 (<worktree>/toyos-tco)
    Checking toyos-cpuvuln v0.1.0 (<worktree>/toyos-cpuvuln)
    Checking toyos-xhci v0.1.0 (<worktree>/toyos-xhci)
    Checking toyos-blockhold v0.1.0 (<worktree>/toyos-blockhold)
    Checking toyos-fat32 v0.1.0 (<worktree>/toyos-fat32)
    Checking toyos-bootmap v0.1.0 (<worktree>/toyos-bootmap)
    Checking toyos-pci v0.1.0 (<worktree>/kernel/pci)
    Checking toyos-symbols v0.1.0 (<worktree>/toyos-symbols)
    Checking toyos-acpi v0.1.0 (<worktree>/toyos-acpi)
    Checking toyos-userbound v0.1.0 (<worktree>/toyos-userbound)
    Checking kernel v0.1.0 (<worktree>/kernel)
    Finished `dev` profile [optimized + debuginfo] target(s) in 2.55s
13:45:39 === clippy: cargo clippy -p kernel --target aarch64-unknown-none-softfloat --features boot-actuators,test-actuators -- $ADOPTED -D warnings
    Checking kernel v0.1.0 (<worktree>/kernel)
    Finished `dev` profile [optimized + debuginfo] target(s) in 1.20s
13:45:40 === clippy: cargo clippy -p kernel --target aarch64-unknown-none-softfloat --features boot-actuators -- $ADOPTED -D warnings
    Checking kernel v0.1.0 (<worktree>/kernel)
    Finished `dev` profile [optimized + debuginfo] target(s) in 1.19s
13:45:41 === clippy: cargo clippy -p kernel --target aarch64-unknown-none-softfloat --features debug-wait,sched-check,sched-tripwire,heap-tripwire,heap-sweep,pass-spin,stack-witness,switch-witness,switch-witness-mutate-frame,switch-witness-mutate-rsp,mask-windows -- $ADOPTED -D warnings
    Checking kernel v0.1.0 (<worktree>/kernel)
    Finished `dev` profile [optimized + debuginfo] target(s) in 1.16s
13:45:43 === clippy: cargo clippy -p bootloader --target x86_64-unknown-uefi -- $ADOPTED -W clippy::undocumented_unsafe_blocks -D warnings
    Checking toyos-abi v0.16.0 (<worktree>/toyos-abi)
    Checking toyos-tsc v0.1.0 (<worktree>/toyos-tsc)
    Checking toyos-tco v0.1.0 (<worktree>/toyos-tco)
    Checking toyos-gpt v0.1.0 (<worktree>/toyos-gpt)
    Checking toyos-blackbox v0.1.0 (<worktree>/toyos-blackbox)
    Checking toyos-rootimage v0.1.0 (<worktree>/toyos-rootimage)
    Checking toyos-wallclock v0.1.0 (<worktree>/toyos-wallclock)
    Checking toyos-elf v0.1.0 (<worktree>/toyos-elf)
    Checking bcachefs v0.1.0 (<worktree>/bcachefs)
    Checking toyos-update v0.1.0 (<worktree>/toyos-update)
    Checking toyos-bootmap v0.1.0 (<worktree>/toyos-bootmap)
    Checking toyos-acpi v0.1.0 (<worktree>/toyos-acpi)
    Checking bootloader v0.1.0 (<worktree>/bootloader)
    Finished `dev` profile [optimized + debuginfo] target(s) in 1.67s
13:45:44 === clippy: cargo clippy -p bootloader --target aarch64-unknown-uefi -- $ADOPTED -W clippy::undocumented_unsafe_blocks -D warnings
    Blocking waiting for file lock on package cache
    Checking toyos-abi v0.16.0 (<worktree>/toyos-abi)
    Checking toyos-wallclock v0.1.0 (<worktree>/toyos-wallclock)
    Checking bcachefs v0.1.0 (<worktree>/bcachefs)
    Checking toyos-blackbox v0.1.0 (<worktree>/toyos-blackbox)
    Checking toyos-elf v0.1.0 (<worktree>/toyos-elf)
    Checking toyos-gpt v0.1.0 (<worktree>/toyos-gpt)
    Checking toyos-tco v0.1.0 (<worktree>/toyos-tco)
    Checking toyos-rootimage v0.1.0 (<worktree>/toyos-rootimage)
    Checking toyos-tsc v0.1.0 (<worktree>/toyos-tsc)
    Checking toyos-update v0.1.0 (<worktree>/toyos-update)
    Checking toyos-bootmap v0.1.0 (<worktree>/toyos-bootmap)
    Checking toyos-acpi v0.1.0 (<worktree>/toyos-acpi)
    Checking bootloader v0.1.0 (<worktree>/bootloader)
    Finished `dev` profile [optimized + debuginfo] target(s) in 1.40s
13:45:46 === clippy: cargo clippy -p toyos-xhci --all-targets -- $ADOPTED -D warnings
    Blocking waiting for file lock on package cache
    Checking toyos-xhci v0.1.0 (<worktree>/toyos-xhci)
    Finished `dev` profile [optimized + debuginfo] target(s) in 0.62s
13:45:47 === clippy: cargo clippy -p kernel-loom --test loom_mailbox --features victim-retires-mid-probe -- $ADOPTED -D warnings
    Checking kernel-loom v0.1.0 (<worktree>/kernel/loom)
    Finished `dev` profile [optimized + debuginfo] target(s) in 0.56s
13:45:47 === clippy: cargo clippy -p toyos-abi --all-targets --keep-going --target-dir target/clippy-abi -- -W clippy::undocumented_unsafe_blocks -D warnings
    Checking toyos-abi v0.16.0 (<worktree>/toyos-abi)
    Finished `dev` profile [optimized + debuginfo] target(s) in 0.26s
13:45:48 === clippy: cargo clippy --manifest-path userland/acpiserver/aml/Cargo.toml --all-targets -- $ADOPTED -D warnings
    Checking toyos-aml v0.1.0 (<worktree>/userland/acpiserver/aml)
    Finished `dev` profile [optimized + debuginfo] target(s) in 0.66s
13:45:48 === clippy: cargo clippy --manifest-path userland/compositor/desktop/Cargo.toml --all-targets -- $ADOPTED -D warnings
    Checking toyos-desktop v0.1.0 (<worktree>/userland/compositor/desktop)
    Finished `dev` profile [optimized + debuginfo] target(s) in 0.31s
13:45:49 === clippy: cargo clippy --manifest-path userland/netstack/mdns/Cargo.toml --all-targets -- $ADOPTED -D warnings
    Checking toyos-mdns v0.1.0 (<worktree>/userland/netstack/mdns)
    Finished `dev` profile [optimized + debuginfo] target(s) in 0.29s
13:45:49 === clippy: cargo clippy --manifest-path userland/netstack/node/Cargo.toml --all-targets -- $ADOPTED -D warnings
    Checking toyos-net-node v0.1.0 (<worktree>/userland/netstack/node)
    Finished `dev` profile [optimized + debuginfo] target(s) in 0.91s
13:45:50 === clippy: cargo clippy --manifest-path userland/soundserver/mixer/Cargo.toml --all-targets -- $ADOPTED -D warnings
    Checking toyos-mixer v0.1.0 (<worktree>/userland/soundserver/mixer)
    Finished `dev` profile [optimized + debuginfo] target(s) in 0.33s
13:45:50 clippy: 24 invocations clean
EXIT=0

@Japabu

Japabu commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator Author

Round 2 gates at 052679491, part 2: cargo run -- --ci host, EXIT=0, [ci] Host: 78 step(s), all green. The log is 455 KB, past a comment's limit: whole, home paths redacted, at https://gist.github.com/Japabu/bd73c9874d8f4234d82be7d5c2a3d7f4. Its first and last lines:

15:45  up 10 days,  3:30, 6 users, load averages: 14.38 20.08 24.51
052679491c939699dc21b16532c93181be65aa53
$ cargo run -- --ci host
...
13:50:16 === [ci] nothing left in $TMPDIR or /tmp
13:50:16 [ci] nothing left in $TMPDIR or /tmp: every test took its scratch with it
13:50:16 [ci] Host: 78 step(s), all green
EXIT=0

@Japabu

Japabu commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator Author

Round 2 gates at 052679491, part 3: cargo run -- --build-only EXIT=0; cargo test --test toyos-build -- netstack_socket_churn libc_sockets EXIT=0, 2 passed, 2 total.

r2-build.log
15:50  up 10 days,  3:34, 6 users, load averages: 20.58 19.93 23.16
052679491c939699dc21b16532c93181be65aa53
$ cargo run -- --build-only
    Blocking waiting for file lock on package cache
    Blocking waiting for file lock on package cache
    Finished `dev` profile [optimized + debuginfo] target(s) in 0.29s
     Running `target/debug/toyos-build --build-only`
    Finished `toyos` profile [optimized + debuginfo] target(s) in 0.36s
    Finished `toyos` profile [optimized + debuginfo] target(s) in 0.21s
   Compiling typenum v1.20.1
   Compiling subtle v2.6.1
   Compiling zeroize v1.9.0
   Compiling cmov v0.5.3
   Compiling rand_core v0.10.0
   Compiling getrandom v0.4.2 (https://github.com/ToyOSOrg/getrandom?branch=toyos-0.4#f0286c8b)
   Compiling getrandom v0.2.17 (https://github.com/ToyOSOrg/getrandom?branch=toyos-0.2-sdk-0.12#2092d1cc)
   Compiling const-oid v0.10.2
   Compiling num-traits v0.2.19
   Compiling base64ct v1.8.3
   Compiling const-oid v0.9.6
   Compiling cpubits v0.1.0
   Compiling cpufeatures v0.3.0
   Compiling simd-adler32 v0.3.10
   Compiling toyos-keymap v0.1.0 (<worktree>/toyos-keymap)
   Compiling toyos-font v0.2.0 (<worktree>/userland/toyos-font)
   Compiling pin-project-lite v0.2.17
   Compiling rand_core v0.6.4
   Compiling adler2 v2.0.1
   Compiling ctutils v0.4.2
   Compiling crc32fast v1.5.0
   Compiling bitflags v2.11.0
   Compiling miniz_oxide v0.9.1
   Compiling toyos-window v0.20.0 (<worktree>/userland/toyos-window)
   Compiling rustcrypto-ff v0.14.0-rc.1
   Compiling memchr v2.8.0
   Compiling smallvec v1.15.1
   Compiling log v0.4.29
   Compiling base16ct v1.0.0
   Compiling rustcrypto-group v0.14.0-rc.1
   Compiling zerocopy v0.8.47
   Compiling byteorder v1.5.0
   Compiling arrayvec v0.7.8
   Compiling pem-rfc7468 v1.0.0
   Compiling bytemuck v1.25.0
   Compiling opaque-debug v0.3.1
   Compiling bytes v1.12.1
   Compiling float-cmp v0.9.0
   Compiling libm v0.2.16
   Compiling strict-num v0.1.1
   Compiling tracing-core v0.1.36
   Compiling der v0.8.0
   Compiling arrayref v0.3.9
   Compiling serde_core v1.0.228
   Compiling kurbo v0.13.0
   Compiling tiny-skia-path v0.12.0
   Compiling pem-rfc7468 v0.7.0
   Compiling miniz_oxide v0.8.9
   Compiling flate2 v1.1.10
   Compiling hybrid-array v0.4.8
   Compiling generic-array v0.14.7
   Compiling num-integer v0.1.46
   Compiling tracing v0.1.44
   Compiling fdeflate v0.3.7
   Compiling futures-sink v0.3.32
   Compiling raw-window-handle v0.6.2 (https://github.com/ToyOSOrg/raw-window-handle?branch=toyos-0.6.2#2a5d102b)
   Compiling siphasher v1.0.2
   Compiling futures-core v0.3.32
   Compiling serde v1.0.228
   Compiling curve25519-dalek v5.0.0-pre.6
   Compiling roxmltree v0.21.1
   Compiling futures-channel v0.3.32
   Compiling png v0.18.1
   Compiling winit v0.30.13 (https://github.com/ToyOSOrg/winit?branch=toyos-0.30.13#f58e1f3b)
   Compiling svgtypes v0.16.1
   Compiling crypto-common v0.1.7
   Compiling block-buffer v0.10.4
   Compiling block-padding v0.3.3
   Compiling universal-hash v0.5.1
   Compiling aead v0.5.2
   Compiling digest v0.10.7
   Compiling inout v0.1.4
   Compiling polyval v0.6.2
   Compiling simplecss v0.2.2
   Compiling cipher v0.4.4
   Compiling imagesize v0.14.0
   Compiling spki v0.8.0-rc.4
   Compiling sha2 v0.10.9
   Compiling crypto-common v0.2.1
   Compiling block-buffer v0.12.0
   Compiling block-padding v0.4.2
   Compiling inout v0.2.2
   Compiling crypto-bigint v0.7.3
   Compiling sec1 v0.8.1
   Compiling ssh-encoding v0.2.0
   Compiling ctr v0.9.2
   Compiling aes v0.8.4
   Compiling hmac v0.12.1
   Compiling ghash v0.5.1
   Compiling slab v0.4.12
   Compiling base64 v0.22.1
   Compiling dpi v0.1.1 (https://github.com/ToyOSOrg/winit?branch=toyos-0.30.13#f58e1f3b)
   Compiling futures-io v0.3.32
   Compiling cursor-icon v1.2.0
   Compiling smol_str v0.2.2
   Compiling strength_reduce v0.2.4
   Compiling xmlwriter v0.1.0
   Compiling spin v0.9.8
   Compiling pico-args v0.5.0
   Compiling data-url v0.3.2
   Compiling futures-task v0.3.32
   Compiling lazy_static v1.5.0
   Compiling aes-gcm v0.10.3
   Compiling futures-util v0.3.32
   Compiling digest v0.11.1
   Compiling cipher v0.5.1
   Compiling universal-hash v0.6.1
   Compiling aead v0.6.0-rc.10
   Compiling usvg v0.47.0
   Compiling polyval v0.7.1
   Compiling transpose v0.2.3
   Compiling hmac v0.13.0-rc.5
   Compiling ghash v0.6.0
   Compiling sha2 v0.11.0-rc.5
   Compiling pbkdf2 v0.13.0-rc.9
   Compiling ctr v0.10.0-rc.4
   Compiling salsa20 v0.11.0
   Compiling aes v0.9.0-rc.4
   Compiling cbc v0.2.0-rc.4
   Compiling signature v3.0.0-rc.10
   Compiling hkdf v0.13.0-rc.5
   Compiling rfc6979 v0.5.0-rc.5
   Compiling num-bigint-dig v0.8.6
   Compiling tiny-skia v0.12.0
   Compiling pbkdf2 v0.12.2
   Compiling cbc v0.1.2
   Compiling chacha20 v0.9.1
   Compiling blowfish v0.9.1
   Compiling pkcs1 v0.8.0-rc.4
   Compiling curve25519-dalek v4.1.3
   Compiling blake2 v0.10.6
   Compiling poly1305 v0.8.0
   Compiling scrypt v0.12.0-rc.10
   Compiling aes-gcm v0.11.0-rc.3
   Compiling softbuffer v0.4.8 (https://github.com/ToyOSOrg/softbuffer?branch=toyos-0.4.8#b36854df)
   Compiling pkcs5 v0.8.0-rc.13
   Compiling ppv-lite86 v0.2.21
   Compiling primal-check v0.3.4
   Compiling num-iter v0.1.45
   Compiling num-complex v0.4.6
   Compiling rgb v0.8.53
   Compiling password-hash v0.5.0
   Compiling chacha20 v0.10.0
   Compiling socket2 v0.6.3 (https://github.com/ToyOSOrg/socket2?branch=toyos#1d902509)
   Compiling rand_chacha v0.3.1
   Compiling mio v1.2.0 (https://github.com/ToyOSOrg/mio?branch=toyos#d1edcaad)
   Compiling keccak v0.2.0
   Compiling fiat-crypto v0.3.0
   Compiling pkcs8 v0.11.0-rc.11
   Compiling rand v0.8.5
   Compiling cpal v0.18.0 (https://github.com/ToyOSOrg/cpal?branch=toyos-0.18.0#f46381d2)
   Compiling thiserror v2.0.18
   Compiling tokio v1.51.1 (https://github.com/ToyOSOrg/tokio?branch=toyos#902fb974)
   Compiling resvg v0.47.0
   Compiling sha3 v0.11.0
   Compiling ed25519 v3.0.0-rc.4
   Compiling rustfft v6.4.1
   Compiling ed25519-dalek v3.0.0-pre.6
   Compiling rand v0.10.0
   Compiling argon2 v0.5.3
   Compiling elliptic-curve v0.14.0-rc.29
   Compiling primefield v0.14.0-rc.8
   Compiling crypto-primes v0.7.0
   Compiling futures-executor v0.3.32
   Compiling ed25519-dalek v2.2.0
   Compiling ssh-cipher v0.2.0
   Compiling bcrypt-pbkdf v0.10.0
   Compiling primeorder v0.14.0-rc.8
   Compiling ecdsa v0.17.0-rc.16
   Compiling sha1 v0.11.0-rc.5
   Compiling kem v0.3.0
   Compiling module-lattice v0.2.1
   Compiling hash32 v0.3.1
   Compiling filepicker-api v0.1.0 (<worktree>/userland/filepicker-api)
   Compiling toyos-bootmap v0.1.0 (<worktree>/toyos-bootmap)
   Compiling toyos-device-memory v0.1.0 (<worktree>/toyos-device-memory)
   Compiling dasp_sample v0.11.0
   Compiling hex v0.4.3
   Compiling toyos-acpi v0.1.0 (<worktree>/toyos-acpi)
   Compiling heapless v0.8.0
   Compiling ml-kem v0.3.0-rc.2
   Compiling p256 v0.14.0-rc.8
   Compiling p521 v0.14.0-rc.8
   Compiling calc v0.1.0 (<worktree>/userland/calc)
   Compiling generic-array v1.3.5
   Compiling rsa v0.10.0-rc.17
   Compiling internal-russh-num-bigint v0.5.0
   Compiling toyos-update v0.1.0 (<worktree>/toyos-update)
   Compiling futures v0.3.32
   Compiling sprite v0.1.0 (<worktree>/userland/sprite)
   Compiling russh-cryptovec v0.59.0 (https://github.com/ToyOSOrg/russh?branch=toyos#389804d3)
   Compiling toyos-swap v0.1.0 (<worktree>/toyos-swap)
   Compiling sha1 v0.10.6
   Compiling toyos-elf v0.1.0 (<worktree>/toyos-elf)
   Compiling rustc-demangle v0.1.27
   Compiling md5 v0.7.0
   Compiling hex-literal v1.1.0
   Compiling data-encoding v2.10.0
   Compiling smoltcp v0.12.0
   Compiling russh-util v0.52.0 (https://github.com/ToyOSOrg/russh?branch=toyos#389804d3)
   Compiling toyos-symbols v0.1.0 (<worktree>/toyos-symbols)
   Compiling snake v0.1.0 (<worktree>/userland/snake)
   Compiling doom v0.1.0 (<worktree>/userland/doom)
   Compiling toyos-aml v0.1.0 (<worktree>/userland/acpiserver/aml)
   Compiling toyos-virtio v0.1.0 (<worktree>/toyos-virtio)
   Compiling toyos-i219 v0.1.0 (<worktree>/toyos-i219)
   Compiling pkg v0.1.0 (<worktree>/userland/pkg)
   Compiling inspect v0.1.0 (<worktree>/userland/inspect)
   Compiling terminal v0.1.0 (<worktree>/userland/terminal)
   Compiling toyos-trace v0.1.0 (<worktree>/toyos-trace)
   Compiling toyos-desktop v0.1.0 (<worktree>/userland/compositor/desktop)
   Compiling rustysynth v1.3.6
   Compiling toyos-mixer v0.1.0 (<worktree>/userland/soundserver/mixer)
   Compiling toyos-hda v0.1.0 (<worktree>/toyos-hda)
   Compiling acpiserver-api v0.1.0 (<worktree>/userland/acpiserver-api)
   Compiling supervisor v0.1.0 (<worktree>/userland/supervisor)
   Compiling compositor v0.1.0 (<worktree>/userland/compositor)
   Compiling realfft v3.5.0
   Compiling rubato v0.16.2
   Compiling netstack v0.0.0 (<worktree>/userland/netstack)
   Compiling trace v0.1.0 (<worktree>/userland/trace)
   Compiling files v0.1.0 (<worktree>/userland/files)
   Compiling symbolize v0.1.0 (<worktree>/userland/symbolize)
   Compiling soundserver v0.0.0 (<worktree>/userland/soundserver)
   Compiling update v0.1.0 (<worktree>/userland/update)
   Compiling swap v0.1.0 (<worktree>/userland/swap)
   Compiling toybox v0.1.0 (<worktree>/userland/toybox)
   Compiling paint v0.1.0 (<worktree>/userland/paint)
   Compiling filepicker v0.1.0 (<worktree>/userland/filepicker)
   Compiling editor v0.1.0 (<worktree>/userland/editor)
   Compiling shell v0.1.0 (<worktree>/userland/shell)
   Compiling host v0.1.0 (<worktree>/userland/host)
   Compiling acpiserver v0.1.0 (<worktree>/userland/acpiserver)
   Compiling proctest v0.1.0 (<worktree>/userland/proctest)
   Compiling p384 v0.14.0-rc.8
   Compiling internal-russh-forked-ssh-key v0.6.18+upstream-0.6.7
   Compiling russh v0.60.0 (https://github.com/ToyOSOrg/russh?branch=toyos#389804d3)
   Compiling sshserver v0.0.0 (<worktree>/userland/sshserver)
    Finished `toyos` profile [optimized + debuginfo] target(s) in 53.98s
warning: the following packages contain code that will be rejected by a future version of Rust: winit v0.30.13 (https://github.com/ToyOSOrg/winit?branch=toyos-0.30.13#f58e1f3b)
note: to see what the problems were, use the option `--future-incompat-report`, or run `cargo report future-incompatibilities --id 1`
13:51:15 root: adding 'bin/acpiserver' (1003536 bytes)
13:51:15 root: adding 'bin/calc' (1286288 bytes)
13:51:15 root: adding 'bin/compositor' (2580880 bytes)
13:51:15 root: adding 'bin/diskserver' (753688 bytes)
13:51:15 root: adding 'bin/doom' (1830656 bytes)
13:51:15 root: adding 'bin/editor' (788368 bytes)
13:51:15 root: adding 'bin/filepicker' (741624 bytes)
13:51:15 root: adding 'bin/files' (2393064 bytes)
13:51:15 root: adding 'bin/fileserver' (1464048 bytes)
13:51:15 root: adding 'bin/host' (565264 bytes)
13:51:15 root: adding 'bin/inspect' (722008 bytes)
13:51:15 root: adding 'bin/logkeeper' (910632 bytes)
13:51:15 root: adding 'bin/netstack' (1057896 bytes)
13:51:15 root: adding 'bin/paint' (733784 bytes)
13:51:15 root: adding 'bin/pkg' (810168 bytes)
13:51:15 root: adding 'bin/proctest' (686136 bytes)
13:51:15 root: adding 'bin/shell' (791712 bytes)
13:51:15 root: adding 'bin/snake' (997968 bytes)
13:51:15 root: adding 'bin/soundserver' (916656 bytes)
13:51:15 root: adding 'bin/sshserver' (4799616 bytes)
13:51:15 root: adding 'bin/supervisor' (1299608 bytes)
13:51:15 root: adding 'bin/swap' (708304 bytes)
13:51:15 root: adding 'bin/symbolize' (692864 bytes)
13:51:15 root: adding 'bin/terminal' (810616 bytes)
13:51:15 root: adding 'bin/toybox' (1106216 bytes)
13:51:15 root: adding 'bin/trace' (563088 bytes)
13:51:15 root: adding 'bin/update' (795504 bytes)
13:51:15 root: adding 'etc/os-release' (287 bytes)
13:51:15 root: adding 'etc/system.manifest' (2912 bytes)
13:51:15 root: adding 'share/doom1.wad' (4196020 bytes)
13:51:15 root: adding 'share/fonts/JetBrainsMono-Regular-8x16.font' (66404 bytes)
13:51:15 root: adding 'share/fonts/ofl.txt' (4216 bytes)
13:51:15 root: adding 'share/fonts/opensans-bold.ttf' (147264 bytes)
13:51:15 root: adding 'share/fonts/opensans-bolditalic.ttf' (153308 bytes)
13:51:15 root: adding 'share/fonts/opensans-italic.ttf' (153256 bytes)
13:51:15 root: adding 'share/fonts/opensans-regular.ttf' (147528 bytes)
13:51:15 root: adding 'share/hello.rs' (55 bytes)
13:51:15 root: adding 'share/icons/arrow-down-right-bold.svg' (211 bytes)
13:51:15 root: adding 'share/icons/crosshair-simple-bold.svg' (809 bytes)
13:51:15 root: adding 'share/icons/cursor-bold.svg' (560 bytes)
13:51:15 root: adding 'share/icons/file-bold.svg' (293 bytes)
13:51:15 root: adding 'share/icons/folder-bold.svg' (303 bytes)
13:51:15 root: adding 'share/icons/minus-bold.svg' (253 bytes)
13:51:15 root: adding 'share/icons/square-bold.svg' (264 bytes)
13:51:15 root: adding 'share/icons/x-bold.svg' (392 bytes)
13:51:15 root: adding 'share/soundfont.sf2' (15546764 bytes)
13:51:15 root: adding 'share/wallpaper.rgb' (6220808 bytes)
13:51:15 root: symlink 'bin/cat' -> '/system/bin/toybox'
13:51:15 root: symlink 'bin/cp' -> '/system/bin/toybox'
13:51:15 root: symlink 'bin/echo' -> '/system/bin/toybox'
13:51:15 root: symlink 'bin/free' -> '/system/bin/toybox'
13:51:15 root: symlink 'bin/grep' -> '/system/bin/toybox'
13:51:15 root: symlink 'bin/hexdump' -> '/system/bin/toybox'
13:51:15 root: symlink 'bin/locale' -> '/system/bin/toybox'
13:51:15 root: symlink 'bin/ls' -> '/system/bin/toybox'
13:51:15 root: symlink 'bin/mkdir' -> '/system/bin/toybox'
13:51:15 root: symlink 'bin/mv' -> '/system/bin/toybox'
13:51:15 root: symlink 'bin/net' -> '/system/bin/toybox'
13:51:15 root: symlink 'bin/ps' -> '/system/bin/toybox'
13:51:15 root: symlink 'bin/pwd' -> '/system/bin/toybox'
13:51:15 root: symlink 'bin/reboot' -> '/system/bin/toybox'
13:51:15 root: symlink 'bin/rm' -> '/system/bin/toybox'
13:51:15 root: symlink 'bin/screen' -> '/system/bin/toybox'
13:51:15 root: symlink 'bin/shutdown' -> '/system/bin/toybox'
13:51:15 root: symlink 'bin/spin' -> '/system/bin/toybox'
13:51:15 root: symlink 'bin/stats' -> '/system/bin/toybox'
13:51:15 root: symlink 'bin/tone' -> '/system/bin/toybox'
13:51:15 Signed with this checkout's throwaway key SHA256:ap8HotedCoz9sq3xJlblF/g6mshMX9L7csa4i46PPmw at version 1791553817
13:51:16 Build finished.
Boot image: <worktree>/target/bootable.img
EXIT=0
r2-netguest.log
15:51  up 10 days,  3:35, 6 users, load averages: 34.87 24.39 24.63
052679491c939699dc21b16532c93181be65aa53
$ cargo test --test toyos-build -- netstack_socket_churn libc_sockets
   Compiling toyos-build v0.1.0 (<worktree>)
    Finished `test` profile [optimized + debuginfo] target(s) in 6.06s
     Running tests/toyos.rs (target/debug/deps/toyos_build-497f416569e2c965)

13:51:22 running 2 tests, 12 wide

13:51:22   RUN   libc_sockets
13:51:22   RUN   netstack_socket_churn
13:51:22   BUILD x86_64 netstack_socket_churn of tests/toyos-rust-tests, for netstack_socket_churn
13:51:25   BUILD x86_64 nodelay_accepted of tests/toyos-rust-tests, for libc_sockets
13:51:25   BUILT x86_64 netstack_socket_churn of tests/toyos-rust-tests, for netstack_socket_churn  (3s)
13:51:25   BUILD x86_64 kernel, loader, ROOT of tests/netcase, for netstack_socket_churn
13:51:27 [build-lock] waiting for the artifact lock (artifact staging) — held by pid 15952 (artifact staging), 0s so far
13:51:27   BUILT x86_64 nodelay_accepted of tests/toyos-rust-tests, for libc_sockets  (2s)
13:51:27 [build-lock] artifact staging acquired after 199.0ms
13:51:27   BUILD x86_64 kernel, loader, ROOT of tests/netcase, for libc_sockets
13:51:29 [build-lock] waiting for the artifact lock (artifact staging) — held by pid 15952 (artifact staging), 1s so far
13:51:29 [build-lock] artifact staging acquired after 522.6ms
13:51:29   BUILT x86_64 kernel, loader, ROOT of tests/netcase, for netstack_socket_churn  (4s)
13:51:29   BUILT x86_64 kernel, loader, ROOT of tests/netcase, for libc_sockets  (2s)
13:51:35   PASS  netstack_socket_churn  (6s)
13:51:36   PASS  libc_sockets  (9s)
13:51:36   --- 2 guests, 0 of them not the shipping kernel, 1 kernel build(s): [""]

13:51:36 host: fastest boot 4676 ms against the reference 1424 ms — liveness ceilings paid at 3.28x
13:51:36 host: 14 core(s); a guest wider than that waits vcpus/cores longer again
13:51:36 test result: ok. 2 passed, 2 total (13.2s; workers: 11s building, 15s testing)
EXIT=0

@Japabu

Japabu commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator Author

Round 2 gates at 052679491, part 4: cargo test, the whole guest suite, EXIT=0, test result: ok. 38 passed, 38 total; load average 27.64 at its start (the log's first line). The log is 73 KB, past a comment's limit: whole, home paths redacted, with the runner r2-gates.sh for all four parts, at https://gist.github.com/Japabu/99dad814f02919156cfd08a9a3dce8f1. Its guest lines:

15:51  up 10 days,  3:35, 6 users, load averages: 27.64 23.41 24.28
052679491c939699dc21b16532c93181be65aa53
13:51:48 running 38 tests, 12 wide
13:52:04   PASS  nested_nmi_is_loud  (3s)
13:52:05   PASS  console_image_boots  (4s)
13:52:05   PASS  acpi_power_button  (6s)
13:52:06   PASS  machine_shutdown  (6s)
13:52:07   PASS  acpi_supply_outlives_holder  (12s)
13:52:08   PASS  acpi_lock_given_back_on_one_cpu  (12s)
13:52:08   PASS  machine_shutdown_short_stop  (7s)
13:52:08   PASS  acpi_mediated_access  (7s)
13:52:08   PASS  bar_map_again  (7s)
13:52:09   PASS  netstack_socket_churn  (7s)
13:52:09   PASS  screen_panic_muted  (4s)
13:52:11   PASS  libc_sockets  (12s)
13:52:14   PASS  screen_loader_clears  (6s)
13:52:16   PASS  screen_fatal_behind_a_painter  (10s)
13:52:20   PASS  iommu_virtio_platform  (19s)
13:52:22   PASS  nvme_disk_keeps_log_and_home  (18s)
13:52:23   PASS  screen_fatal_halt_composited  (9s)
13:52:26   PASS  virt_early_fault  (724ms)
13:52:26   PASS  virt_early_panic  (713ms)
13:52:26   PASS  virt_el2_drop  (1s)
13:52:29   [virt] [ 2.781 cpu0 kernel] timer-floor: PASS span=10000 floor=10000 ticks: the comparator past the counter it was set from
13:52:29   PASS  virt_timer_floor  (3s)
13:52:29   PASS  virt_user_mode  (3s)
13:52:33   PASS  virt_timer_preempts  (3s)
13:52:33   PASS  virt_fp_isolation  (15s)
13:52:33   PASS  virt_first_entry  (13s)
13:52:33   PASS  virt_unmap_touch  (10s)
13:52:33   PASS  virt_readonly_copyout  (7s)
13:52:34   PASS  virt_debug_refused  (8s)
13:52:34   PASS  virt_ring0_timer_in_syscall  (4s)
13:52:38   PASS  virt_smp  (9s)
13:52:39   PASS  virt_el1_smp  (7s)
13:52:40   PASS  virt_mask_windows  (5s)
13:52:40   PASS  virt_reboot  (5s)
13:52:40   PASS  virt_reboot_refused_without_psci  (5s)
13:52:42   PASS  virt_failed_ap_leaves_no_hole  (11s)
13:52:42   PASS  virt_fatal_halts_the_others_first  (4s)
13:52:43   [virt] [16.582 cpu0 kernel] irq-storm: PASS sgis=6528434/6528434 ticks=1000: the timer fired through the flood, and every SGI sent was taken
13:52:43   PASS  virt_irq_storm  (17s)
13:52:46   PASS  virt_off_names_the_cpus_left_on  (10s)
13:52:46 test result: ok. 38 passed, 38 total (57.4s; workers: 321s building, 292s testing)

@Japabu Japabu changed the title The node keeps the pipe order a stream's end is read by: a FIN after a shutdown reads as a FIN, a failure as a reset The node keeps the pipe order std reads a stream's end by: a FIN after a shutdown reads as a FIN, a failure as a reset Oct 9, 2026
@Japabu

Japabu commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator Author

Answer to the round-1 review (comment 6082008828), head 052679491 (main at 558283168 merged in). The orchestrator's decision on both BLOCKERs: libc's half leaves this pull request and goes to the move.

BLOCKER 1 — fixed by removal, after measuring

The row you asked for was measured at 96f48560f on main's stack, in a guest on tests/netcase and on this Mac's TCP (comment 6082065591, exit 1). The client sends one byte, reads the peer's one-byte answer, and shuts its sending half down with nothing pending. The peer then sends four bytes and its FIN.

  • Std reads the four bytes and then ConnectionReset, where the host reads Ok(0).
  • A C client with this branch's libc read recv -1 ECONNRESET.
  • The same C client with main's libc read recv 0 (comment 6082078719, the second run).

Your reading of bridge_piped holds, and so does the regression. In addition, both_close's second line, read after the peer's FIN, came back ConnectionReset in both of this round's runs. It was Ok(0) in round 1's two runs, so the race was met.

What changed:

  • userland/libc and tests/libc-arch are main's again (8fee90a95).
  • The removed diff and its host test are posted for the move (comment 6082081208).
  • Both issues' attribution is corrected by this measurement, and so is their "neither met the race". The reset after a half-close belongs to bridge_piped and is recorded in a-netstack-client-cannot-tell-a-reset-from-the-peers-fin.md. The cut count belongs to a-shutdown-of-the-sending-half-drops-what-the-send-pipe-still-holds.md, which now records 65,536 in two runs and 65,535 in two.
  • toyos-libc-copies is back to 46 tests, main's count.

BLOCKER 2 — moved with the code it tested

The libc wiring is no longer on this branch, so nothing here is left unreached. The exit of issues/a-netstack-client-cannot-tell-a-reset-from-the-peers-fin.md now names the guest C case on tests/netcase that the move carries with the libc patch. The case reads:

  • recv 0 at the peer's FIN after SHUT_WR;
  • ECONNRESET on a reset mid-stream and on one after SHUT_WR;
  • send EPIPE after SHUT_WR;
  • recv 0 after SHUT_RD.

It must be red under each of your three mutations. The track's line says the same.

NOTEs

  • The two dead conjuncts in streams.rs: both deleted (de98ea4ab). The loop's comment says that [tcp] has no room once the FIN is queued.

  • A writer that leaves after the shutdown and before the peer's FIN: a_writer_that_leaves_after_the_shutdown_leaves_the_stream_to_its_reader now plays that order.

    • The send pipe goes first.
    • The watch is reader alone.
    • The peer's text and FIN still reach the client.
    • No reset follows.

    Two mutations turn it red, each with exit 101 (comment 6082159813). m9 makes the writer's leaving take the whole stream. m10 makes the leaving unheard. The test is green on the base, because that order was already right there.

  • EPIPE without SIGPIPE: filed as issues/libc-answers-epipe-and-raises-no-sigpipe.md, owned by libc. Its exit is the decision, recorded at posix_io.rs, plus a guest C case on both paths.

  • Prose: the body is rewritten. Both sentences you named are gone. In their place is the measured table of what main does, including the shut_first row and the race.

Gates at 052679491

Each log is posted.

gate exit result comment
node tests 0 157 tests 6082190310
[tcp] 0 385 tests 6082190310
toyos-libc-copies 0 46 tests 6082190310
clippy 0 24 invocations clean 6082190310
--ci host 0 78 steps 6082273747
--build-only 0 6082291835
netstack_socket_churn and libc_sockets 0 2 of 2 6082291835
whole guest suite 0 38 of 38, load 27.64 at start 6082313797

The control has exit 101 with the six reds of round 1. m1 to m10 each have exit 101 (comment 6082159813).

@Japabu

Japabu commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator Author

Two secret gists linked above held the round's long host and suite logs. Both are deleted: their build lines carried this machine's per-user temporary-directory path, which identifies the machine and may not be posted. The pull request rests on the exits and lines quoted in its body and comments; the full logs stay on the development machine.

@Japabu

Japabu commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator Author

Review, round 2, head 052679491. Judged: git diff 96f48560f 052679491, less the merge of main at 558283168. The merge is clean: git show --remerge-diff shows no hand resolution, and it touches none of this branch's files.

Net: git diff --shortstat origin/main...052679491 gives 7 files, +336 / -49.

  • Production: userland/netstack/node/src/streams.rs, +67 / -21. Unchanged since round 1, apart from the two deleted conjuncts.
  • Tests: userland/netstack/node/tests/streams.rs, +134 / -5.
  • issues/: +135 / -23.

Round 1's BLOCKERs

  1. CLOSED. The row that was asked for is measured on main's smoltcp shell, in comments 6082065591 and 6082078719, exit 1 each, with the logs on disk matching. The client shuts down with nothing pending, then the peer writes and sends its FIN:

    • std reads 4 bytes ... then Err(ConnectionReset) in both runs;
    • this host's TCP reads Ok(0);
    • C reads recv -1, errno ECONNRESET with the removed libc and recv 0 with main's libc. That run's log opens with an empty git diff 1621281ae -- userland/libc.

    both_close's second line reads Ok(0) in round 1's logged run at 1e303178b and Err(ConnectionReset) in both of this round's runs.

    The libc probe is gone. git diff origin/main 052679491 -- userland/libc tests/libc-arch is empty, and so is the three-dot diff. The posted patch (comment 6082081208) is, line for line, git diff 8fee90a95 96f48560f -- userland/libc tests/libc-arch, and it still applies at this head (git apply --check).

    Both issues' attributions now read true against bridge_piped (userland/netstack/src/main.rs:1177-1189):

    • In shut_first, the connection reaches TIME-WAIT at the peer's FIN, so both pipes close in one pass and the client finds both gone.
    • In both_close, the receive pipe closes at the peer's FIN and the send pipe at CLOSED after the peer acknowledges the client's FIN. A read that lands in that window reads Ok(0), and one after it reads a reset.
    • The cut count is the other issue's, and that issue now says the reset after it is not its own.
  2. CLOSED. The libc wiring that nothing reached has left the branch. issues/a-netstack-client-cannot-tell-a-reset-from-the-peers-fin.md's exit names the guest C case on tests/netcase with all five rows and the three mutations it must turn red. The track's line in issues/toyos-has-its-own-network-stack.md points at that exit and at the patch.

Round 1's NOTEs:

  • The two dead conjuncts are deleted (de98ea4ab). [tcp]'s status (toyos-net-shard/tcp/src/stack.rs:1127-1137) answers writable 0 for Sync with a FIN queued and for Ended. SynSent's room is unreachable here, because a connecting stream moves no byte.

  • a_writer_that_leaves_after_the_shutdown_leaves_the_stream_to_its_reader plays the order. Under m9 it is the one red, panicking at tests/streams.rs:893. Under m10 it is red together with two more. Both runs exit 101 (r2-mut/m9-*.log, m10-*.log).

  • issues/libc-answers-epipe-and-raises-no-sigpipe.md is true of the tree:

    • posix_io.rs maps Gone to EPIPE;
    • signal, sigaction and raise in misc.rs do nothing;
    • send answers EIO (socket.rs:352);
    • the child issue imitates SIGCHLD, not SIGPIPE.

    It names an owner the way its libc siblings do and has an exit a guest case can fail.

  • The two false sentences are gone from the body.

Evidence at 052679491

The exits and lines quoted in the comments match the logs on disk (r2-*), each opening with uptime and the head 052679491c939699dc21b16532c93181be65aa53:

check exit result
node 0 157 tests (1+9+1+20+31+19+33+3+40)
[tcp] 0 385 tests
toyos-libc-copies 0 46 tests
clippy 0 24 invocations clean
--ci host 0 Host: 78 step(s), all green; it runs userland/netstack/node's tests, the new one among them (r2-host.log:7767)
--build-only 0
netstack_socket_churn and libc_sockets 0 2 passed, 2 total
the whole guest suite 0 38 passed, 38 total; no FAIL line

The control exits 101 with the six named reds. m1 to m10 each exit 101 at the tests the body names, with the tree checked clean after each.

BLOCKER

None.

NOTE

  • The run counts in two issues rest on a run whose log is gone. In issues/a-netstack-client-cannot-tell-a-reset-from-the-peers-fin.md: "Ok(0) in two runs", "in each of four runs". In issues/a-shutdown-of-the-sending-half-drops-what-the-send-pipe-still-holds.md: "65,536 bytes ... in two runs". One of each count is round 1's run at the base, whose log was lost, as the body says; three runs are logged. Either state three, or re-run and post the run. Every conclusion stands on the three logged runs.
  • In the body, "each log posted on this pull request as it was made" is no longer true of --ci host and the guest suite. Comments 6082273747 and 6082313797 link gists that were deleted. Their exits and their lines stand quoted.

What host and guest / suite must show at the head that lands

main has moved to a944746d5, past this head's merge of 558283168; #799 changed the suite's boots. The head that lands is that merge, the merge queue's group commit.

  • host must be green at that exact head. It must run userland/netstack/node's tests (157 here, 40 of them in streams.rs) with a_writer_that_leaves_after_the_shutdown_leaves_the_stream_to_its_reader among them. This check is the one gate that carries the change's claims.
  • guest / suite must be green at that exact head. No guest boots the node until the move, so the suite reaches nothing of this change. Its green says only that the merged tree regresses nothing.
  • The pull request is a draft, so ci.yml's host has not run on it. It must be marked ready first.

The orchestrator may land on reading those two checks green at that head. No BLOCKER is open, and both NOTEs concern records he corrects himself.

LAND

@Japabu
Japabu marked this pull request as ready for review October 9, 2026 13:59
@Japabu

Japabu commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator Author

CI at 052679491, read from each job's log: host 79 steps all green, including a_writer_that_leaves_after_the_shutdown_leaves_the_stream_to_its_reader; guest suite 38/38 ok. Merges clean with main 8dbccd3ab. Queued.

@Japabu
Japabu added this pull request to the merge queue Oct 9, 2026
Merged via the queue into main with commit ee9e067 Oct 9, 2026
6 checks passed
@Japabu
Japabu deleted the wt/toyos-finreset branch October 9, 2026 14:54
Japabu added a commit that referenced this pull request Oct 9, 2026
…m end's order (#803) and the .local name's re-probe (#800), into the app grants

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C
Japabu added a commit that referenced this pull request Oct 9, 2026
…nd's order (#803) and the .local name's probing (#800), into the HTTPS client

ring_kat's line in DRIVEN_AND_SHARED met random_draws' there; the merge keeps
main's, and the next commit decides ring_kat's place.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C
Japabu added a commit that referenced this pull request Oct 9, 2026
… the stream end's order (#803), into the stop's hold of the console wire

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant