Repository navigation
toyos-net-node: listeners on the own stack's accept queue, and one bound on the streams, listeners and datagram sockets clients make the node hold (stage E) - #777
Conversation
A listener's owner is woken for the connections that finished their handshake, and nothing told a caller how many wait short of accepting them: `Tcp::ready` does. A connection `close` leaves [tcp] to finish alone is still two buffers and its peer decides for how long, so a caller that bounds what it holds has to count them: `Tcp::orphans`, kept as the user changes hands and as the connection is freed. The shard forwards both, and `listener_port` and `close_listener`, which it had left out. None of them settles: no flow becomes eligible, and the resets a closed listener owes leave outside the round. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
…hat clients make the node hold Stage E of the cut of the track's stage 5. A listener is a passive open of [tcp]'s and the pipe its owner reads its wakes from; the queues stay [tcp]'s. The owner holds one unspent wake for each connection that waits and that there is a place for, written from what [tcp] and the bound say when a pass ends, so no wake depends on the pass that saw a connection arrive. An accept makes the oldest waiting connection a stream on its pipes. Places are the stream cap stage D left out, and the listeners' too: a stream, a listener and a connection [tcp] is finishing alone each hold one, and a connect, a listen and an accept past the number are refused with nothing made. The number is the shell's and a new node has none, so `connect` answers a refusal of the node's own beside the stack's. The two recorded listener failures of the stack this replaces are tests here that read the other way: a second peer is answered while a first handshake hangs, and a connect between two accepts is queued. Neither issue closes: what ships still has both until the move. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
The 23 mutations of stage E, in 24 runsRun by the orchestrator at
The patches
diff --git a/userland/netstack/node/src/streams.rs b/userland/netstack/node/src/streams.rs
index c142eb558..450c0f39e 100644
--- a/userland/netstack/node/src/streams.rs
+++ b/userland/netstack/node/src/streams.rs
@@ -296,7 +296,6 @@ impl Node {
let Streams { live, events, .. } = &mut self.streams;
let stack = &mut self.stack;
live.retain(|id, stream| stream.pass(*id, now, stack, events));
- self.wake_owners(now);
}
/// The client lets go of the stream: nobody reads it, and what its pipe still holds is sent
diff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index ced370d03..8e7032e24 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -106,7 +106,6 @@ impl Node {
listener.unspent = listener.unspent.saturating_sub(1);
let bound = listener.bound;
let answer = self.take(bound, pipes);
- self.bridge(now);
answer
}
diff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index ced370d03..999eaf136 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -150,7 +150,7 @@ impl Node {
let stack = &mut self.stack;
let refused = self.listeners.live.iter_mut().find_map(|(id, listener)| {
let owed = stack.tcp_ready(listener.bound).min(room);
- while listener.unspent < owed {
+ while listener.unspent < owed.min(1) {
if let Err(refusal) = listener.owner.wake() {
return Some((*id, refusal));
}
diff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index ced370d03..695fc1c84 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -103,9 +103,11 @@ impl Node {
/// and what it already received moves at once.
pub fn accept(&mut self, now: Instant, id: ListenerId, pipes: Option<Pipes>) -> Result<Accepted, AcceptRefused> {
let Some(listener) = self.listeners.live.get_mut(&id) else { return Err(AcceptRefused::NoListener) };
- listener.unspent = listener.unspent.saturating_sub(1);
let bound = listener.bound;
let answer = self.take(bound, pipes);
+ if let (Ok(_), Some(listener)) = (&answer, self.listeners.live.get_mut(&id)) {
+ listener.unspent = listener.unspent.saturating_sub(1);
+ }
self.bridge(now);
answer
}
diff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index ced370d03..ab4900dc6 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -149,7 +149,7 @@ impl Node {
let room = self.room();
let stack = &mut self.stack;
let refused = self.listeners.live.iter_mut().find_map(|(id, listener)| {
- let owed = stack.tcp_ready(listener.bound).min(room);
+ let owed = stack.tcp_ready(listener.bound);
while listener.unspent < owed {
if let Err(refusal) = listener.owner.wake() {
return Some((*id, refusal));
diff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index ced370d03..00914dadc 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -112,9 +112,6 @@ impl Node {
fn take(&mut self, bound: toyos_net_tcp::ListenerId, pipes: Option<Pipes>) -> Result<Accepted, AcceptRefused> {
let Some(pipes) = pipes else { return Err(AcceptRefused::NoPipes) };
- if self.room() == 0 {
- return Err(AcceptRefused::Full);
- }
let Some((conn, tuple)) = self.stack.tcp_accept(bound) else { return Err(AcceptRefused::Nothing) };
let id = self.streams.accepted(conn, pipes);
Ok(Accepted { id, remote: tuple.remote, local: tuple.local.port })
diff --git a/userland/netstack/node/src/places.rs b/userland/netstack/node/src/places.rs
index 4f0a8362d..9220d676e 100644
--- a/userland/netstack/node/src/places.rs
+++ b/userland/netstack/node/src/places.rs
@@ -25,7 +25,7 @@ impl Node {
/// The places taken: streams, listeners, and connections [tcp] is finishing alone.
pub fn held(&self) -> usize {
- self.streams().saturating_add(self.listeners()).saturating_add(self.stack.tcp_orphans())
+ self.streams().saturating_add(self.listeners())
}
pub(crate) fn room(&self) -> usize {
diff --git a/userland/netstack/node/src/streams.rs b/userland/netstack/node/src/streams.rs
index c142eb558..d5cdaec26 100644
--- a/userland/netstack/node/src/streams.rs
+++ b/userland/netstack/node/src/streams.rs
@@ -307,7 +307,6 @@ impl Node {
self.stack.abort(now, stream.conn);
self.streams.live.remove(&id);
self.streams.events.push_back(StreamEvent::Closed { id });
- self.wake_owners(now);
return;
}
stream.to_client = None;
diff --git a/userland/netstack/node/src/streams.rs b/userland/netstack/node/src/streams.rs
index c142eb558..2bbf70a7b 100644
--- a/userland/netstack/node/src/streams.rs
+++ b/userland/netstack/node/src/streams.rs
@@ -358,7 +358,6 @@ impl Node {
if held {
self.stack.abort(now, stream.conn);
self.streams.live.remove(&id);
- self.wake_owners(now);
}
}
diff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index ced370d03..f95f2d48a 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -124,9 +124,6 @@ impl Node {
/// says nobody holds the other end of the wake pipe. `false` is an id that names no listener.
pub fn close_listener(&mut self, now: Instant, id: ListenerId) -> bool {
let closed = self.end_listener(now, id);
- if closed {
- self.wake_owners(now);
- }
closed
}
diff --git a/userland/netstack/node/src/places.rs b/userland/netstack/node/src/places.rs
index 4f0a8362d..e2efd9b4c 100644
--- a/userland/netstack/node/src/places.rs
+++ b/userland/netstack/node/src/places.rs
@@ -20,7 +20,6 @@ impl Node {
/// How many places the node has from here on. Nothing held is let go for a smaller number.
pub fn set_places(&mut self, now: Instant, places: usize) {
self.places = places;
- self.wake_owners(now);
}
/// The places taken: streams, listeners, and connections [tcp] is finishing alone.
diff --git a/userland/netstack/node/src/streams.rs b/userland/netstack/node/src/streams.rs
index c142eb558..39315b7a2 100644
--- a/userland/netstack/node/src/streams.rs
+++ b/userland/netstack/node/src/streams.rs
@@ -283,9 +283,6 @@ impl Node {
/// An active open to `remote`, answered by a [`StreamEvent`] once the handshake ends or
/// `timeout` passes. Refused, nothing was sent and the pipe ends are dropped.
pub fn connect(&mut self, now: Instant, remote: Endpoint, timeout: Option<Duration>, pipes: Pipes) -> Result<StreamId, ConnectRefused> {
- if self.room() == 0 {
- return Err(ConnectRefused::Full);
- }
let conn = self.stack.connect(now, remote).map_err(ConnectRefused::Stack)?;
let deadline = timeout.map(|within| now.after(within));
Ok(self.streams.hold(Stream { connecting: true, deadline, ..Stream::established(conn, pipes) }))
diff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index ced370d03..e0caa7e1e 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -85,9 +85,6 @@ impl Node {
/// the interface holds or comes to hold. Answers the listener and its port. Refused, nothing
/// was made and `owner` is dropped.
pub fn listen(&mut self, port: Option<Port>, owner: Box<dyn Wake>, mut draw: impl FnMut() -> u32) -> Result<(ListenerId, Port), ListenRefused> {
- if self.room() == 0 {
- return Err(ListenRefused::Full);
- }
let candidate = || {
let [low, high, ..] = draw().to_le_bytes();
u16::from_le_bytes([low, high])
diff --git a/userland/netstack/node/src/places.rs b/userland/netstack/node/src/places.rs
index 4f0a8362d..fc53aa263 100644
--- a/userland/netstack/node/src/places.rs
+++ b/userland/netstack/node/src/places.rs
@@ -25,7 +25,7 @@ impl Node {
/// The places taken: streams, listeners, and connections [tcp] is finishing alone.
pub fn held(&self) -> usize {
- self.streams().saturating_add(self.listeners()).saturating_add(self.stack.tcp_orphans())
+ self.streams().saturating_add(self.stack.tcp_orphans())
}
pub(crate) fn room(&self) -> usize {
diff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index ced370d03..e40b919ad 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -132,7 +132,6 @@ impl Node {
fn end_listener(&mut self, now: Instant, id: ListenerId) -> bool {
let Some(listener) = self.listeners.live.remove(&id) else { return false };
- self.stack.tcp_close_listener(now, listener.bound);
true
}
diff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index ced370d03..59fcf045c 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -151,8 +151,8 @@ impl Node {
let refused = self.listeners.live.iter_mut().find_map(|(id, listener)| {
let owed = stack.tcp_ready(listener.bound).min(room);
while listener.unspent < owed {
- if let Err(refusal) = listener.owner.wake() {
- return Some((*id, refusal));
+ if listener.owner.wake().is_err() {
+ break;
}
listener.unspent = listener.unspent.saturating_add(1);
}
diff --git a/userland/netstack/node/src/places.rs b/userland/netstack/node/src/places.rs
index 4f0a8362d..133ee496d 100644
--- a/userland/netstack/node/src/places.rs
+++ b/userland/netstack/node/src/places.rs
@@ -25,7 +25,7 @@ impl Node {
/// The places taken: streams, listeners, and connections [tcp] is finishing alone.
pub fn held(&self) -> usize {
- self.streams().saturating_add(self.listeners()).saturating_add(self.stack.tcp_orphans())
+ self.listeners().saturating_add(self.stack.tcp_orphans())
}
pub(crate) fn room(&self) -> usize {
diff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index ced370d03..680d269f2 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -89,7 +89,7 @@ impl Node {
return Err(ListenRefused::Full);
}
let candidate = || {
- let [low, high, ..] = draw().to_le_bytes();
+ let [.., low, high] = draw().to_le_bytes();
u16::from_le_bytes([low, high])
};
let Some((bound, port)) = self.stack.tcp_listen(port, candidate) else { return Err(ListenRefused::InUse) };
diff --git a/toyos-net-shard/tcp/src/stack.rs b/toyos-net-shard/tcp/src/stack.rs
index 4f71e2fb4..d7fd6b6b5 100644
--- a/toyos-net-shard/tcp/src/stack.rs
+++ b/toyos-net-shard/tcp/src/stack.rs
@@ -749,7 +749,7 @@ impl Tcp {
return;
}
let Some(listener) = value(&mut self.listeners, index) else { return };
- if listener.pending.len() >= limits::LISTEN_PENDING || listener.ready.len() >= limits::LISTEN_READY {
+ if !listener.pending.is_empty() || !listener.ready.is_empty() {
self.log.count(Counter::ListenOverflow);
return;
}
diff --git a/toyos-net-shard/tcp/src/stack.rs b/toyos-net-shard/tcp/src/stack.rs
index 4f71e2fb4..996fc0cd7 100644
--- a/toyos-net-shard/tcp/src/stack.rs
+++ b/toyos-net-shard/tcp/src/stack.rs
@@ -449,9 +449,6 @@ impl Tcp {
fn free(&mut self, index: u32) {
let Some(generation) = self.conns.get(usize::try_from(index).unwrap_or(usize::MAX)).map(|s| s.generation) else { return };
let Some(conn) = release(&mut self.conns, &mut self.free_conns, index) else { return };
- if conn.user == User::Orphan {
- self.orphans = self.orphans.saturating_sub(1);
- }
let remote = conn.tuple.remote.addr;
let parked = self.parked.get(&remote).is_some_and(|p| p.conns.contains(&index));
// Its index may name another connection next.
diff --git a/toyos-net-shard/tcp/src/stack.rs b/toyos-net-shard/tcp/src/stack.rs
index 4f71e2fb4..9856e5858 100644
--- a/toyos-net-shard/tcp/src/stack.rs
+++ b/toyos-net-shard/tcp/src/stack.rs
@@ -1085,7 +1085,6 @@ impl Tcp {
sync.shutdown_write(now);
sync.orphan(now);
conn.user = User::Orphan;
- self.orphans = self.orphans.saturating_add(1);
self.settle(id.index, now);
}
}
diff --git a/toyos-net-shard/tcp/src/stack.rs b/toyos-net-shard/tcp/src/stack.rs
index 4f71e2fb4..65ba099a1 100644
--- a/toyos-net-shard/tcp/src/stack.rs
+++ b/toyos-net-shard/tcp/src/stack.rs
@@ -1072,7 +1072,6 @@ impl Tcp {
Tcb::SynRcvd(rcvd) => {
rcvd.shutdown_write();
conn.user = User::Orphan;
- self.orphans = self.orphans.saturating_add(1);
self.settle(id.index, now);
}
Tcb::Sync(sync) if sync.rx.unread() > 0 => {
diff --git a/toyos-net-shard/tcp/src/stack.rs b/toyos-net-shard/tcp/src/stack.rs
index 4f71e2fb4..178b9d5a8 100644
--- a/toyos-net-shard/tcp/src/stack.rs
+++ b/toyos-net-shard/tcp/src/stack.rs
@@ -585,7 +585,7 @@ impl Tcp {
/// How many children completed their handshake and wait for [`Self::accept`].
pub fn ready(&mut self, id: ListenerId) -> Result<usize, Error> {
- slot(&mut self.listeners, id.index, id.generation).map(|l| l.ready.len()).ok_or(Error::NoSuchSocket)
+ slot(&mut self.listeners, id.index, id.generation).map(|l| l.pending.len()).ok_or(Error::NoSuchSocket)
}
/// The oldest child that completed its handshake. |
The wake hook moves to the end of `Node::pass`, which a frame, a deadline and now a transmit opportunity end in, so a connect that fails inside an opportunity gives its place to a connection that waits. A wake's refusal is a write's (`WriteRefusal`), an accepted stream carries its peer's address as a connected one does, and the test node has 32 places, above the 17 streams stage D's per-peer test holds. A departed client's connection its peer keeps alive holds its place through both of its lives, as a stream while its pipe holds bytes and as [tcp]'s to finish after: a test here holds one at the bound until stage D's rule cuts it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
The 23 mutations of stage E, in 24 runs, at
|
| mutation | test that turned red | exit |
|---|---|---|
t1-an-orphan-that-ends-stays-counted |
an_orphan_is_counted_until_both_fins |
101 |
t2-a-closed-connection-is-not-counted |
an_orphan_is_counted_until_both_fins |
101 |
t3-a-close-in-syn-received-is-not-counted |
a_close_before_the_handshake_ends_leaves_an_orphan_until_it_gives_up |
101 |
t4-ready-counts-handshakes-in-progress |
ready_counts_the_connections_accept_has_yet_to_return |
101 |
o1-a-listener-is-one-connection |
a_handshake_nobody_finishes_leaves_the_port_open_and_is_given_up |
101 |
o1-a-listener-is-one-connection |
a_connect_between_two_accepts_is_queued_not_reset |
101 |
e01-a-pass-wakes-nobody |
a_listener_answers_a_syn_and_wakes_its_owner_when_the_handshake_ends |
101 |
e02-an-accept-ends-in-no-pass |
a_listener_answers_a_syn_and_wakes_its_owner_when_the_handshake_ends |
101 |
e03-an-owner-holds-one-wake-at-most |
a_connect_between_two_accepts_is_queued_not_reset |
101 |
e04-only-an-accept-that-takes-spends-a-wake |
an_accept_spends_a_wake_whatever_it_answers |
101 |
e19-a-drawn-port-reads-the-draws-high-half |
a_listen_on_a_taken_port_is_refused_and_a_drawn_port_listens |
101 |
e05-a-wake-ignores-places |
a_wake_is_owed_only_for_a_connection_there_is_a_place_for |
101 |
e06-an-accept-ignores-places |
a_wake_is_owed_only_for_a_connection_there_is_a_place_for |
101 |
e07-a-finishing-connection-holds-no-place |
a_wake_is_owed_only_for_a_connection_there_is_a_place_for |
101 |
e13-a-connect-ignores-places |
a_connect_past_the_places_is_refused_and_sends_nothing |
101 |
e18-a-stream-holds-no-place |
a_connect_past_the_places_is_refused_and_sends_nothing |
101 |
e14-a-listen-ignores-places |
a_listener_holds_a_place_and_a_listen_without_one_makes_nothing |
101 |
e15-a-listener-holds-no-place |
a_listener_holds_a_place_and_a_listen_without_one_makes_nothing |
101 |
e09-a-closed-connect-wakes-nobody |
closing_a_connect_gives_its_place_to_a_connection_that_waits |
101 |
e10-a-stream-reset-for-its-pipe-wakes-nobody |
a_stream_reset_for_its_pipe_gives_its_place_to_a_connection_that_waits |
101 |
e11-a-closed-listener-wakes-nobody |
closing_a_listener_gives_its_place_to_a_connection_that_waits_at_another |
101 |
e12-more-places-wake-nobody |
more_places_wake_the_owner_of_a_connection_that_waits |
101 |
e16-a-closed-listener-stays-in-the-stack |
closing_a_listener_resets_what_waits_and_frees_its_port |
101 |
e17-a-refused-wake-is-ignored |
a_wake_the_owners_pipe_refuses_ends_the_listener |
101 |
The patches
e01-a-pass-wakes-nobody.patch
diff --git a/userland/netstack/node/src/streams.rs b/userland/netstack/node/src/streams.rs
index 1346df63c..8d9618378 100644
--- a/userland/netstack/node/src/streams.rs
+++ b/userland/netstack/node/src/streams.rs
@@ -353,7 +353,6 @@ impl Node {
*peers = peers.saturating_add(1);
}
live.retain(|id, stream| (connects && !stream.connecting) || stream.pass(*id, now, stack, events, &mut extended));
- self.wake_owners(now);
}
/// The client lets go of the stream: nobody reads it, and what its pipe still holds is sente02-an-accept-ends-in-no-pass.patch
diff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 0df6d315e..abb6b1e40 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -106,7 +106,6 @@ impl Node {
listener.unspent = listener.unspent.saturating_sub(1);
let bound = listener.bound;
let answer = self.take(bound, pipes);
- self.bridge(now);
answer
}
e03-an-owner-holds-one-wake-at-most.patch
diff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 0df6d315e..da391fd88 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -150,7 +150,7 @@ impl Node {
let stack = &mut self.stack;
let refused = self.listeners.live.iter_mut().find_map(|(id, listener)| {
let owed = stack.tcp_ready(listener.bound).min(room);
- while listener.unspent < owed {
+ while listener.unspent < owed.min(1) {
if let Err(refusal) = listener.owner.wake() {
return Some((*id, refusal));
}e04-only-an-accept-that-takes-spends-a-wake.patch
diff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 0df6d315e..f131032af 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -103,9 +103,11 @@ impl Node {
/// and what it already received moves at once.
pub fn accept(&mut self, now: Instant, id: ListenerId, pipes: Option<Pipes>) -> Result<Accepted, AcceptRefused> {
let Some(listener) = self.listeners.live.get_mut(&id) else { return Err(AcceptRefused::NoListener) };
- listener.unspent = listener.unspent.saturating_sub(1);
let bound = listener.bound;
let answer = self.take(bound, pipes);
+ if let (Ok(_), Some(listener)) = (&answer, self.listeners.live.get_mut(&id)) {
+ listener.unspent = listener.unspent.saturating_sub(1);
+ }
self.bridge(now);
answer
}e05-a-wake-ignores-places.patch
diff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 0df6d315e..efa6d38aa 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -149,7 +149,7 @@ impl Node {
let room = self.room();
let stack = &mut self.stack;
let refused = self.listeners.live.iter_mut().find_map(|(id, listener)| {
- let owed = stack.tcp_ready(listener.bound).min(room);
+ let owed = stack.tcp_ready(listener.bound);
while listener.unspent < owed {
if let Err(refusal) = listener.owner.wake() {
return Some((*id, refusal));e06-an-accept-ignores-places.patch
diff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 0df6d315e..7b914b1dc 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -112,9 +112,6 @@ impl Node {
fn take(&mut self, bound: toyos_net_tcp::ListenerId, pipes: Option<Pipes>) -> Result<Accepted, AcceptRefused> {
let Some(pipes) = pipes else { return Err(AcceptRefused::NoPipes) };
- if self.room() == 0 {
- return Err(AcceptRefused::Full);
- }
let Some((conn, tuple)) = self.stack.tcp_accept(bound) else { return Err(AcceptRefused::Nothing) };
let id = self.streams.accepted(conn, tuple.remote.addr, pipes);
Ok(Accepted { id, remote: tuple.remote, local: tuple.local.port })e07-a-finishing-connection-holds-no-place.patch
diff --git a/userland/netstack/node/src/places.rs b/userland/netstack/node/src/places.rs
index 33bae3276..a9b8d34d1 100644
--- a/userland/netstack/node/src/places.rs
+++ b/userland/netstack/node/src/places.rs
@@ -28,7 +28,7 @@ impl Node {
/// The places taken: streams, listeners, and connections [tcp] is finishing alone.
pub fn held(&self) -> usize {
- self.streams().saturating_add(self.listeners()).saturating_add(self.stack.tcp_orphans())
+ self.streams().saturating_add(self.listeners())
}
pub(crate) fn room(&self) -> usize {e09-a-closed-connect-wakes-nobody.patch
diff --git a/userland/netstack/node/src/streams.rs b/userland/netstack/node/src/streams.rs
index 1346df63c..d7bd4849d 100644
--- a/userland/netstack/node/src/streams.rs
+++ b/userland/netstack/node/src/streams.rs
@@ -364,7 +364,6 @@ impl Node {
self.stack.tcp_abort(now, stream.conn);
self.streams.live.remove(&id);
self.streams.events.push_back(StreamEvent::Closed { id });
- self.wake_owners(now);
return;
}
stream.to_client = None;e10-a-stream-reset-for-its-pipe-wakes-nobody.patch
diff --git a/userland/netstack/node/src/streams.rs b/userland/netstack/node/src/streams.rs
index 1346df63c..0669d43a0 100644
--- a/userland/netstack/node/src/streams.rs
+++ b/userland/netstack/node/src/streams.rs
@@ -419,7 +419,6 @@ impl Node {
if held {
self.stack.tcp_abort(now, stream.conn);
self.streams.live.remove(&id);
- self.wake_owners(now);
}
}
e11-a-closed-listener-wakes-nobody.patch
diff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 0df6d315e..cf1255229 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -124,9 +124,6 @@ impl Node {
/// says nobody holds the other end of the wake pipe. `false` is an id that names no listener.
pub fn close_listener(&mut self, now: Instant, id: ListenerId) -> bool {
let closed = self.end_listener(now, id);
- if closed {
- self.wake_owners(now);
- }
closed
}
e12-more-places-wake-nobody.patch
diff --git a/userland/netstack/node/src/places.rs b/userland/netstack/node/src/places.rs
index 33bae3276..e66c87073 100644
--- a/userland/netstack/node/src/places.rs
+++ b/userland/netstack/node/src/places.rs
@@ -23,7 +23,6 @@ impl Node {
/// How many places the node has from here on. Nothing held is let go for a smaller number.
pub fn set_places(&mut self, now: Instant, places: usize) {
self.places = places;
- self.wake_owners(now);
}
/// The places taken: streams, listeners, and connections [tcp] is finishing alone.e13-a-connect-ignores-places.patch
diff --git a/userland/netstack/node/src/streams.rs b/userland/netstack/node/src/streams.rs
index 1346df63c..43accad69 100644
--- a/userland/netstack/node/src/streams.rs
+++ b/userland/netstack/node/src/streams.rs
@@ -330,9 +330,6 @@ impl Node {
/// An active open to `remote`, answered by a [`StreamEvent`] once the handshake ends or
/// `timeout` passes. Refused, nothing was sent and the pipe ends are dropped.
pub fn connect(&mut self, now: Instant, remote: Endpoint, timeout: Option<Duration>, pipes: Pipes) -> Result<StreamId, ConnectRefused> {
- if self.room() == 0 {
- return Err(ConnectRefused::Full);
- }
let conn = self.stack.tcp_connect(now, remote).map_err(ConnectRefused::Stack)?;
let deadline = timeout.map(|within| now.after(within));
Ok(self.streams.hold(Stream { connecting: true, deadline, ..Stream::established(conn, remote.addr, pipes) }))e14-a-listen-ignores-places.patch
diff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 0df6d315e..b51dc693a 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -85,9 +85,6 @@ impl Node {
/// the interface holds or comes to hold. Answers the listener and its port. Refused, nothing
/// was made and `owner` is dropped.
pub fn listen(&mut self, port: Option<Port>, owner: Box<dyn Wake>, mut draw: impl FnMut() -> u32) -> Result<(ListenerId, Port), ListenRefused> {
- if self.room() == 0 {
- return Err(ListenRefused::Full);
- }
let candidate = || {
let [low, high, ..] = draw().to_le_bytes();
u16::from_le_bytes([low, high])e15-a-listener-holds-no-place.patch
diff --git a/userland/netstack/node/src/places.rs b/userland/netstack/node/src/places.rs
index 33bae3276..1f6665268 100644
--- a/userland/netstack/node/src/places.rs
+++ b/userland/netstack/node/src/places.rs
@@ -28,7 +28,7 @@ impl Node {
/// The places taken: streams, listeners, and connections [tcp] is finishing alone.
pub fn held(&self) -> usize {
- self.streams().saturating_add(self.listeners()).saturating_add(self.stack.tcp_orphans())
+ self.streams().saturating_add(self.stack.tcp_orphans())
}
pub(crate) fn room(&self) -> usize {e16-a-closed-listener-stays-in-the-stack.patch
diff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 0df6d315e..341d5ee6a 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -132,7 +132,6 @@ impl Node {
fn end_listener(&mut self, now: Instant, id: ListenerId) -> bool {
let Some(listener) = self.listeners.live.remove(&id) else { return false };
- self.stack.tcp_close_listener(now, listener.bound);
true
}
e17-a-refused-wake-is-ignored.patch
diff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 0df6d315e..43e18cff3 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -151,8 +151,8 @@ impl Node {
let refused = self.listeners.live.iter_mut().find_map(|(id, listener)| {
let owed = stack.tcp_ready(listener.bound).min(room);
while listener.unspent < owed {
- if let Err(refusal) = listener.owner.wake() {
- return Some((*id, refusal));
+ if listener.owner.wake().is_err() {
+ break;
}
listener.unspent = listener.unspent.saturating_add(1);
}e18-a-stream-holds-no-place.patch
diff --git a/userland/netstack/node/src/places.rs b/userland/netstack/node/src/places.rs
index 33bae3276..5cd96add3 100644
--- a/userland/netstack/node/src/places.rs
+++ b/userland/netstack/node/src/places.rs
@@ -28,7 +28,7 @@ impl Node {
/// The places taken: streams, listeners, and connections [tcp] is finishing alone.
pub fn held(&self) -> usize {
- self.streams().saturating_add(self.listeners()).saturating_add(self.stack.tcp_orphans())
+ self.listeners().saturating_add(self.stack.tcp_orphans())
}
pub(crate) fn room(&self) -> usize {e19-a-drawn-port-reads-the-draws-high-half.patch
diff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 0df6d315e..269f61d2b 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -89,7 +89,7 @@ impl Node {
return Err(ListenRefused::Full);
}
let candidate = || {
- let [low, high, ..] = draw().to_le_bytes();
+ let [.., low, high] = draw().to_le_bytes();
u16::from_le_bytes([low, high])
};
let Some((bound, port)) = self.stack.tcp_listen(port, candidate) else { return Err(ListenRefused::InUse) };o1-a-listener-is-one-connection.patch
diff --git a/toyos-net-shard/tcp/src/stack.rs b/toyos-net-shard/tcp/src/stack.rs
index 4f71e2fb4..d7fd6b6b5 100644
--- a/toyos-net-shard/tcp/src/stack.rs
+++ b/toyos-net-shard/tcp/src/stack.rs
@@ -749,7 +749,7 @@ impl Tcp {
return;
}
let Some(listener) = value(&mut self.listeners, index) else { return };
- if listener.pending.len() >= limits::LISTEN_PENDING || listener.ready.len() >= limits::LISTEN_READY {
+ if !listener.pending.is_empty() || !listener.ready.is_empty() {
self.log.count(Counter::ListenOverflow);
return;
}t1-an-orphan-that-ends-stays-counted.patch
diff --git a/toyos-net-shard/tcp/src/stack.rs b/toyos-net-shard/tcp/src/stack.rs
index 4f71e2fb4..996fc0cd7 100644
--- a/toyos-net-shard/tcp/src/stack.rs
+++ b/toyos-net-shard/tcp/src/stack.rs
@@ -449,9 +449,6 @@ impl Tcp {
fn free(&mut self, index: u32) {
let Some(generation) = self.conns.get(usize::try_from(index).unwrap_or(usize::MAX)).map(|s| s.generation) else { return };
let Some(conn) = release(&mut self.conns, &mut self.free_conns, index) else { return };
- if conn.user == User::Orphan {
- self.orphans = self.orphans.saturating_sub(1);
- }
let remote = conn.tuple.remote.addr;
let parked = self.parked.get(&remote).is_some_and(|p| p.conns.contains(&index));
// Its index may name another connection next.t2-a-closed-connection-is-not-counted.patch
diff --git a/toyos-net-shard/tcp/src/stack.rs b/toyos-net-shard/tcp/src/stack.rs
index 4f71e2fb4..9856e5858 100644
--- a/toyos-net-shard/tcp/src/stack.rs
+++ b/toyos-net-shard/tcp/src/stack.rs
@@ -1085,7 +1085,6 @@ impl Tcp {
sync.shutdown_write(now);
sync.orphan(now);
conn.user = User::Orphan;
- self.orphans = self.orphans.saturating_add(1);
self.settle(id.index, now);
}
}t3-a-close-in-syn-received-is-not-counted.patch
diff --git a/toyos-net-shard/tcp/src/stack.rs b/toyos-net-shard/tcp/src/stack.rs
index 4f71e2fb4..65ba099a1 100644
--- a/toyos-net-shard/tcp/src/stack.rs
+++ b/toyos-net-shard/tcp/src/stack.rs
@@ -1072,7 +1072,6 @@ impl Tcp {
Tcb::SynRcvd(rcvd) => {
rcvd.shutdown_write();
conn.user = User::Orphan;
- self.orphans = self.orphans.saturating_add(1);
self.settle(id.index, now);
}
Tcb::Sync(sync) if sync.rx.unread() > 0 => {t4-ready-counts-handshakes-in-progress.patch
diff --git a/toyos-net-shard/tcp/src/stack.rs b/toyos-net-shard/tcp/src/stack.rs
index 4f71e2fb4..178b9d5a8 100644
--- a/toyos-net-shard/tcp/src/stack.rs
+++ b/toyos-net-shard/tcp/src/stack.rs
@@ -585,7 +585,7 @@ impl Tcp {
/// How many children completed their handshake and wait for [`Self::accept`].
pub fn ready(&mut self, id: ListenerId) -> Result<usize, Error> {
- slot(&mut self.listeners, id.index, id.generation).map(|l| l.ready.len()).ok_or(Error::NoSuchSocket)
+ slot(&mut self.listeners, id.index, id.generation).map(|l| l.pending.len()).ok_or(Error::NoSuchSocket)
}
/// The oldest child that completed its handshake.Stage D at 47231f0 carries origin/main (26f5ef2, stage A landed) and its rounds 2 and 3: Stream::reader_left is gone, the cut arms on a stream its client can see no more, and `extended` is decided in every pass. Resolved by hand: - streams.rs, Node::connect: stage E's body (the places check, then Streams::hold of Stream::established) over stage D's, which built the Stream field by field without reader_left. Stage E's Stream::established, merged without a conflict, still named reader_left: that line is deleted. - the track, "What the node does not yet meet": stage E's three lines (the places are one number, a listener's handshakes in progress, the unspent wake) are kept; stage D's line on a node without a bound is dropped, since the places are that bound; stage D's rewritten lines on the stream its client can see no more, the reset stream's options and the pass over every stream replace stage E's copies of the older ones. Two sentences are rewritten for the merged tree: the places line points at the line it means by name, and the departed-client line names places.rs as the bound across addresses. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
…, and the places bound is tested across peer addresses Stage D's round 2 times any stream its client can see no more, an accepted one included, by its peer's address, and writes a stream's whole option set from the copy the stream holds. Three things about a stream an accept makes follow. Its options. Stream::established seeded every stream with Options::default(). That was true only while nothing gave a listener an option. Stream::established now takes the options, and Streams::accepted writes the listener's to [tcp] and seeds the stream with what it wrote, so the copy set_nodelay rewrites is the connection's by construction. The listener's options are the node's (Node::set_listener_nodelay, listener_nodelay): [tcp] hands its own copy for a listener to a connection at the SYN and has no call that reads a connection's back, so a seed taken from there is wrong for a connection begun before the option was set. A listening socket that takes TCP_NODELAY and hands it to what it accepts is what a host does; netstack on smoltcp refuses the request for a listener. Its peer address. Already tuple.remote.addr; the test that says so accepts seventeen from one address and one from another. Its id after a cut. A stream can now be cut while its client holds it. Every request for that id names nothing, and no id is used twice, so not the stream that took its place either: tested, with the place the cut gave back going to the connection that waited. The track's exit for the bound across addresses is the last test: peers at four addresses bring sixteen connections each to a node with three places. The track line and places.rs's header say what holds. The listeners tests' peers have an address: a segment to an address no peer is at fails the test, as in the streams tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
The 27 mutations of stage E, in 31 runs, at
|
| mutation | test that turned red | exit |
|---|---|---|
t1-an-orphan-that-ends-stays-counted |
an_orphan_is_counted_until_both_fins |
101 |
t2-a-closed-connection-is-not-counted |
an_orphan_is_counted_until_both_fins |
101 |
t3-a-close-in-syn-received-is-not-counted |
a_close_before_the_handshake_ends_leaves_an_orphan_until_it_gives_up |
101 |
t4-ready-counts-handshakes-in-progress |
ready_counts_the_connections_accept_has_yet_to_return |
101 |
o1-a-listener-is-one-connection |
a_handshake_nobody_finishes_leaves_the_port_open_and_is_given_up |
101 |
o1-a-listener-is-one-connection |
a_connect_between_two_accepts_is_queued_not_reset |
101 |
e01-a-pass-wakes-nobody |
a_listener_answers_a_syn_and_wakes_its_owner_when_the_handshake_ends |
101 |
e02-an-accept-ends-in-no-pass |
a_listener_answers_a_syn_and_wakes_its_owner_when_the_handshake_ends |
101 |
e03-an-owner-holds-one-wake-at-most |
a_connect_between_two_accepts_is_queued_not_reset |
101 |
e04-only-an-accept-that-takes-spends-a-wake |
an_accept_spends_a_wake_whatever_it_answers |
101 |
e19-a-drawn-port-reads-the-draws-high-half |
a_listen_on_a_taken_port_is_refused_and_a_drawn_port_listens |
101 |
e05-a-wake-ignores-places |
a_wake_is_owed_only_for_a_connection_there_is_a_place_for |
101 |
e06-an-accept-ignores-places |
a_wake_is_owed_only_for_a_connection_there_is_a_place_for |
101 |
e07-a-finishing-connection-holds-no-place |
a_wake_is_owed_only_for_a_connection_there_is_a_place_for |
101 |
e13-a-connect-ignores-places |
a_connect_past_the_places_is_refused_and_sends_nothing |
101 |
e18-a-stream-holds-no-place |
a_connect_past_the_places_is_refused_and_sends_nothing |
101 |
e14-a-listen-ignores-places |
a_listener_holds_a_place_and_a_listen_without_one_makes_nothing |
101 |
e15-a-listener-holds-no-place |
a_listener_holds_a_place_and_a_listen_without_one_makes_nothing |
101 |
e09-a-closed-connect-wakes-nobody |
closing_a_connect_gives_its_place_to_a_connection_that_waits |
101 |
e10-a-stream-reset-for-its-pipe-wakes-nobody |
a_stream_reset_for_its_pipe_gives_its_place_to_a_connection_that_waits |
101 |
e11-a-closed-listener-wakes-nobody |
closing_a_listener_gives_its_place_to_a_connection_that_waits_at_another |
101 |
e12-more-places-wake-nobody |
more_places_wake_the_owner_of_a_connection_that_waits |
101 |
e16-a-closed-listener-stays-in-the-stack |
closing_a_listener_resets_what_waits_and_frees_its_port |
101 |
e17-a-refused-wake-is-ignored |
a_wake_the_owners_pipe_refuses_ends_the_listener |
101 |
e20-an-accept-writes-its-stream-no-option |
a_stream_starts_with_the_options_its_listener_has_when_it_is_accepted |
101 |
e21-an-accepted-stream-holds-no-option |
a_stream_starts_with_the_options_its_listener_has_when_it_is_accepted |
101 |
e22-an-accepted-stream-is-counted-by-the-nodes-address |
an_accepted_stream_is_one_of_its_peers_addresss_sixteen |
101 |
e23-an-id-is-used-twice |
a_request_for_a_stream_that_was_cut_names_nothing |
101 |
e05-a-wake-ignores-places |
peers_at_more_addresses_than_there_are_places_hold_no_stream_past_them |
101 |
e06-an-accept-ignores-places |
peers_at_more_addresses_than_there_are_places_hold_no_stream_past_them |
101 |
e18-a-stream-holds-no-place |
peers_at_more_addresses_than_there_are_places_hold_no_stream_past_them |
101 |
e01-a-pass-wakes-nobody
diff --git a/userland/netstack/node/src/streams.rs b/userland/netstack/node/src/streams.rs
index 1346df63c..8d9618378 100644
--- a/userland/netstack/node/src/streams.rs
+++ b/userland/netstack/node/src/streams.rs
@@ -353,7 +353,6 @@ impl Node {
*peers = peers.saturating_add(1);
}
live.retain(|id, stream| (connects && !stream.connecting) || stream.pass(*id, now, stack, events, &mut extended));
- self.wake_owners(now);
}
/// The client lets go of the stream: nobody reads it, and what its pipe still holds is sente02-an-accept-ends-in-no-pass
diff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index b2e2e64ad..4f9914cc9 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -126,7 +126,6 @@ impl Node {
listener.unspent = listener.unspent.saturating_sub(1);
let (bound, options) = (listener.bound, listener.options);
let answer = self.take(now, bound, options, pipes);
- self.bridge(now);
answer
}
e03-an-owner-holds-one-wake-at-most
diff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 0df6d315e..da391fd88 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -150,7 +150,7 @@ impl Node {
let stack = &mut self.stack;
let refused = self.listeners.live.iter_mut().find_map(|(id, listener)| {
let owed = stack.tcp_ready(listener.bound).min(room);
- while listener.unspent < owed {
+ while listener.unspent < owed.min(1) {
if let Err(refusal) = listener.owner.wake() {
return Some((*id, refusal));
}e04-only-an-accept-that-takes-spends-a-wake
diff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index b2e2e64ad..d9ea0f6b1 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -123,9 +123,11 @@ impl Node {
/// and what it already received moves at once.
pub fn accept(&mut self, now: Instant, id: ListenerId, pipes: Option<Pipes>) -> Result<Accepted, AcceptRefused> {
let Some(listener) = self.listeners.live.get_mut(&id) else { return Err(AcceptRefused::NoListener) };
- listener.unspent = listener.unspent.saturating_sub(1);
let (bound, options) = (listener.bound, listener.options);
let answer = self.take(now, bound, options, pipes);
+ if let (Ok(_), Some(listener)) = (&answer, self.listeners.live.get_mut(&id)) {
+ listener.unspent = listener.unspent.saturating_sub(1);
+ }
self.bridge(now);
answer
}e05-a-wake-ignores-places
diff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 0df6d315e..efa6d38aa 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -149,7 +149,7 @@ impl Node {
let room = self.room();
let stack = &mut self.stack;
let refused = self.listeners.live.iter_mut().find_map(|(id, listener)| {
- let owed = stack.tcp_ready(listener.bound).min(room);
+ let owed = stack.tcp_ready(listener.bound);
while listener.unspent < owed {
if let Err(refusal) = listener.owner.wake() {
return Some((*id, refusal));e06-an-accept-ignores-places
diff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index b2e2e64ad..e2cec69ba 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -132,9 +132,6 @@ impl Node {
fn take(&mut self, now: Instant, bound: toyos_net_tcp::ListenerId, options: Options, pipes: Option<Pipes>) -> Result<Accepted, AcceptRefused> {
let Some(pipes) = pipes else { return Err(AcceptRefused::NoPipes) };
- if self.room() == 0 {
- return Err(AcceptRefused::Full);
- }
let Some((conn, tuple)) = self.stack.tcp_accept(bound) else { return Err(AcceptRefused::Nothing) };
// The peer's address is what `streams` counts a stream its client can see no more by.
let id = self.streams.accepted(now, &mut self.stack, conn, tuple.remote.addr, options, pipes);e07-a-finishing-connection-holds-no-place
diff --git a/userland/netstack/node/src/places.rs b/userland/netstack/node/src/places.rs
index 33bae3276..a9b8d34d1 100644
--- a/userland/netstack/node/src/places.rs
+++ b/userland/netstack/node/src/places.rs
@@ -28,7 +28,7 @@ impl Node {
/// The places taken: streams, listeners, and connections [tcp] is finishing alone.
pub fn held(&self) -> usize {
- self.streams().saturating_add(self.listeners()).saturating_add(self.stack.tcp_orphans())
+ self.streams().saturating_add(self.listeners())
}
pub(crate) fn room(&self) -> usize {e09-a-closed-connect-wakes-nobody
diff --git a/userland/netstack/node/src/streams.rs b/userland/netstack/node/src/streams.rs
index 1346df63c..d7bd4849d 100644
--- a/userland/netstack/node/src/streams.rs
+++ b/userland/netstack/node/src/streams.rs
@@ -364,7 +364,6 @@ impl Node {
self.stack.tcp_abort(now, stream.conn);
self.streams.live.remove(&id);
self.streams.events.push_back(StreamEvent::Closed { id });
- self.wake_owners(now);
return;
}
stream.to_client = None;e10-a-stream-reset-for-its-pipe-wakes-nobody
diff --git a/userland/netstack/node/src/streams.rs b/userland/netstack/node/src/streams.rs
index 1346df63c..0669d43a0 100644
--- a/userland/netstack/node/src/streams.rs
+++ b/userland/netstack/node/src/streams.rs
@@ -419,7 +419,6 @@ impl Node {
if held {
self.stack.tcp_abort(now, stream.conn);
self.streams.live.remove(&id);
- self.wake_owners(now);
}
}
e11-a-closed-listener-wakes-nobody
diff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 0df6d315e..cf1255229 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -124,9 +124,6 @@ impl Node {
/// says nobody holds the other end of the wake pipe. `false` is an id that names no listener.
pub fn close_listener(&mut self, now: Instant, id: ListenerId) -> bool {
let closed = self.end_listener(now, id);
- if closed {
- self.wake_owners(now);
- }
closed
}
e12-more-places-wake-nobody
diff --git a/userland/netstack/node/src/places.rs b/userland/netstack/node/src/places.rs
index 33bae3276..e66c87073 100644
--- a/userland/netstack/node/src/places.rs
+++ b/userland/netstack/node/src/places.rs
@@ -23,7 +23,6 @@ impl Node {
/// How many places the node has from here on. Nothing held is let go for a smaller number.
pub fn set_places(&mut self, now: Instant, places: usize) {
self.places = places;
- self.wake_owners(now);
}
/// The places taken: streams, listeners, and connections [tcp] is finishing alone.e13-a-connect-ignores-places
diff --git a/userland/netstack/node/src/streams.rs b/userland/netstack/node/src/streams.rs
index 091d3fb23..4aec759a6 100644
--- a/userland/netstack/node/src/streams.rs
+++ b/userland/netstack/node/src/streams.rs
@@ -334,9 +334,6 @@ impl Node {
/// An active open to `remote`, answered by a [`StreamEvent`] once the handshake ends or
/// `timeout` passes. Refused, nothing was sent and the pipe ends are dropped.
pub fn connect(&mut self, now: Instant, remote: Endpoint, timeout: Option<Duration>, pipes: Pipes) -> Result<StreamId, ConnectRefused> {
- if self.room() == 0 {
- return Err(ConnectRefused::Full);
- }
let conn = self.stack.tcp_connect(now, remote).map_err(ConnectRefused::Stack)?;
let deadline = timeout.map(|within| now.after(within));
// An active open has [tcp]'s defaults until `set_nodelay`.e14-a-listen-ignores-places
diff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 0df6d315e..b51dc693a 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -85,9 +85,6 @@ impl Node {
/// the interface holds or comes to hold. Answers the listener and its port. Refused, nothing
/// was made and `owner` is dropped.
pub fn listen(&mut self, port: Option<Port>, owner: Box<dyn Wake>, mut draw: impl FnMut() -> u32) -> Result<(ListenerId, Port), ListenRefused> {
- if self.room() == 0 {
- return Err(ListenRefused::Full);
- }
let candidate = || {
let [low, high, ..] = draw().to_le_bytes();
u16::from_le_bytes([low, high])e15-a-listener-holds-no-place
diff --git a/userland/netstack/node/src/places.rs b/userland/netstack/node/src/places.rs
index 33bae3276..1f6665268 100644
--- a/userland/netstack/node/src/places.rs
+++ b/userland/netstack/node/src/places.rs
@@ -28,7 +28,7 @@ impl Node {
/// The places taken: streams, listeners, and connections [tcp] is finishing alone.
pub fn held(&self) -> usize {
- self.streams().saturating_add(self.listeners()).saturating_add(self.stack.tcp_orphans())
+ self.streams().saturating_add(self.stack.tcp_orphans())
}
pub(crate) fn room(&self) -> usize {e16-a-closed-listener-stays-in-the-stack
diff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 0df6d315e..341d5ee6a 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -132,7 +132,6 @@ impl Node {
fn end_listener(&mut self, now: Instant, id: ListenerId) -> bool {
let Some(listener) = self.listeners.live.remove(&id) else { return false };
- self.stack.tcp_close_listener(now, listener.bound);
true
}
e17-a-refused-wake-is-ignored
diff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 0df6d315e..43e18cff3 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -151,8 +151,8 @@ impl Node {
let refused = self.listeners.live.iter_mut().find_map(|(id, listener)| {
let owed = stack.tcp_ready(listener.bound).min(room);
while listener.unspent < owed {
- if let Err(refusal) = listener.owner.wake() {
- return Some((*id, refusal));
+ if listener.owner.wake().is_err() {
+ break;
}
listener.unspent = listener.unspent.saturating_add(1);
}e18-a-stream-holds-no-place
diff --git a/userland/netstack/node/src/places.rs b/userland/netstack/node/src/places.rs
index 33bae3276..5cd96add3 100644
--- a/userland/netstack/node/src/places.rs
+++ b/userland/netstack/node/src/places.rs
@@ -28,7 +28,7 @@ impl Node {
/// The places taken: streams, listeners, and connections [tcp] is finishing alone.
pub fn held(&self) -> usize {
- self.streams().saturating_add(self.listeners()).saturating_add(self.stack.tcp_orphans())
+ self.listeners().saturating_add(self.stack.tcp_orphans())
}
pub(crate) fn room(&self) -> usize {e19-a-drawn-port-reads-the-draws-high-half
diff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 0df6d315e..269f61d2b 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -89,7 +89,7 @@ impl Node {
return Err(ListenRefused::Full);
}
let candidate = || {
- let [low, high, ..] = draw().to_le_bytes();
+ let [.., low, high] = draw().to_le_bytes();
u16::from_le_bytes([low, high])
};
let Some((bound, port)) = self.stack.tcp_listen(port, candidate) else { return Err(ListenRefused::InUse) };e20-an-accept-writes-its-stream-no-option
diff --git a/userland/netstack/node/src/streams.rs b/userland/netstack/node/src/streams.rs
index 091d3fb23..885586362 100644
--- a/userland/netstack/node/src/streams.rs
+++ b/userland/netstack/node/src/streams.rs
@@ -321,7 +321,6 @@ impl Streams {
/// Holds a connection a listener's owner accepted, established, on the pipes its accept
/// handed over, and writes it its listener's `options`.
pub(crate) fn accepted(&mut self, now: Instant, stack: &mut Stack, conn: ConnId, remote: Ipv4Addr, options: Options, pipes: Pipes) -> StreamId {
- stack.tcp_set_options(now, conn, options);
self.hold(Stream::established(conn, remote, options, pipes))
}
e21-an-accepted-stream-holds-no-option
diff --git a/userland/netstack/node/src/streams.rs b/userland/netstack/node/src/streams.rs
index 091d3fb23..ee8071d91 100644
--- a/userland/netstack/node/src/streams.rs
+++ b/userland/netstack/node/src/streams.rs
@@ -322,7 +322,7 @@ impl Streams {
/// handed over, and writes it its listener's `options`.
pub(crate) fn accepted(&mut self, now: Instant, stack: &mut Stack, conn: ConnId, remote: Ipv4Addr, options: Options, pipes: Pipes) -> StreamId {
stack.tcp_set_options(now, conn, options);
- self.hold(Stream::established(conn, remote, options, pipes))
+ self.hold(Stream::established(conn, remote, Options::default(), pipes))
}
pub(crate) fn next_deadline(&self) -> Option<Instant> {e22-an-accepted-stream-is-counted-by-the-nodes-address
diff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index b2e2e64ad..bd1df73cc 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -137,7 +137,7 @@ impl Node {
}
let Some((conn, tuple)) = self.stack.tcp_accept(bound) else { return Err(AcceptRefused::Nothing) };
// The peer's address is what `streams` counts a stream its client can see no more by.
- let id = self.streams.accepted(now, &mut self.stack, conn, tuple.remote.addr, options, pipes);
+ let id = self.streams.accepted(now, &mut self.stack, conn, tuple.local.addr, options, pipes);
Ok(Accepted { id, remote: tuple.remote, local: tuple.local.port })
}
e23-an-id-is-used-twice
diff --git a/userland/netstack/node/src/streams.rs b/userland/netstack/node/src/streams.rs
index 091d3fb23..dca817638 100644
--- a/userland/netstack/node/src/streams.rs
+++ b/userland/netstack/node/src/streams.rs
@@ -313,7 +313,6 @@ impl Streams {
/// Holds `stream` under an id of its own.
fn hold(&mut self, stream: Stream) -> StreamId {
let id = StreamId(self.next);
- self.next = self.next.saturating_add(1);
self.live.insert(id, stream);
id
}o1-a-listener-is-one-connection
diff --git a/toyos-net-shard/tcp/src/stack.rs b/toyos-net-shard/tcp/src/stack.rs
index 4f71e2fb4..d7fd6b6b5 100644
--- a/toyos-net-shard/tcp/src/stack.rs
+++ b/toyos-net-shard/tcp/src/stack.rs
@@ -749,7 +749,7 @@ impl Tcp {
return;
}
let Some(listener) = value(&mut self.listeners, index) else { return };
- if listener.pending.len() >= limits::LISTEN_PENDING || listener.ready.len() >= limits::LISTEN_READY {
+ if !listener.pending.is_empty() || !listener.ready.is_empty() {
self.log.count(Counter::ListenOverflow);
return;
}t1-an-orphan-that-ends-stays-counted
diff --git a/toyos-net-shard/tcp/src/stack.rs b/toyos-net-shard/tcp/src/stack.rs
index 4f71e2fb4..996fc0cd7 100644
--- a/toyos-net-shard/tcp/src/stack.rs
+++ b/toyos-net-shard/tcp/src/stack.rs
@@ -449,9 +449,6 @@ impl Tcp {
fn free(&mut self, index: u32) {
let Some(generation) = self.conns.get(usize::try_from(index).unwrap_or(usize::MAX)).map(|s| s.generation) else { return };
let Some(conn) = release(&mut self.conns, &mut self.free_conns, index) else { return };
- if conn.user == User::Orphan {
- self.orphans = self.orphans.saturating_sub(1);
- }
let remote = conn.tuple.remote.addr;
let parked = self.parked.get(&remote).is_some_and(|p| p.conns.contains(&index));
// Its index may name another connection next.t2-a-closed-connection-is-not-counted
diff --git a/toyos-net-shard/tcp/src/stack.rs b/toyos-net-shard/tcp/src/stack.rs
index 4f71e2fb4..9856e5858 100644
--- a/toyos-net-shard/tcp/src/stack.rs
+++ b/toyos-net-shard/tcp/src/stack.rs
@@ -1085,7 +1085,6 @@ impl Tcp {
sync.shutdown_write(now);
sync.orphan(now);
conn.user = User::Orphan;
- self.orphans = self.orphans.saturating_add(1);
self.settle(id.index, now);
}
}t3-a-close-in-syn-received-is-not-counted
diff --git a/toyos-net-shard/tcp/src/stack.rs b/toyos-net-shard/tcp/src/stack.rs
index 4f71e2fb4..65ba099a1 100644
--- a/toyos-net-shard/tcp/src/stack.rs
+++ b/toyos-net-shard/tcp/src/stack.rs
@@ -1072,7 +1072,6 @@ impl Tcp {
Tcb::SynRcvd(rcvd) => {
rcvd.shutdown_write();
conn.user = User::Orphan;
- self.orphans = self.orphans.saturating_add(1);
self.settle(id.index, now);
}
Tcb::Sync(sync) if sync.rx.unread() > 0 => {t4-ready-counts-handshakes-in-progress
diff --git a/toyos-net-shard/tcp/src/stack.rs b/toyos-net-shard/tcp/src/stack.rs
index 4f71e2fb4..178b9d5a8 100644
--- a/toyos-net-shard/tcp/src/stack.rs
+++ b/toyos-net-shard/tcp/src/stack.rs
@@ -585,7 +585,7 @@ impl Tcp {
/// How many children completed their handshake and wait for [`Self::accept`].
pub fn ready(&mut self, id: ListenerId) -> Result<usize, Error> {
- slot(&mut self.listeners, id.index, id.generation).map(|l| l.ready.len()).ok_or(Error::NoSuchSocket)
+ slot(&mut self.listeners, id.index, id.generation).map(|l| l.pending.len()).ok_or(Error::NoSuchSocket)
}
/// The oldest child that completed its handshake.|
Review of Net lines of the stage: 13 files, +1,404, -37. Production +380, -30 ( BLOCKER
NOTE
Asked in the briefWhat a peer can make the node hold. Checked against the code; the body's account stands.
The wake accounting. The listener's option.
The oracle and the controls. The hand-resolved hunks. CI's clippy. No clone left unused ( What the branch must show nextAfter the fix round for BLOCKERs 2 to 4, which is new code and comes back here, #775 merges with
Before the move may be dispatchedFrom this review:
From the track's "does not yet meet" lines, those whose exit names the move or stage 5:
SEND BACK |
Stage D's head carries origin/main at 35d3585 (#772): the node's datagram sockets and its mDNS name. Resolved by hand: - userland/netstack/node/src/lib.rs, the module list: both sides whole, in order: datagram, lease, listeners, name, places, streams. Every other hunk of the file merged by itself: receive and fire still end in bridge after settle, transmit in pass(now, true), and next_deadline is stage D's four terms. - userland/netstack/node/Cargo.toml, description: names what both sides put in the package, and says the bound is on streams and listeners. - the track, the stage 5 paragraph: in the tree are the node, its lease, the datagram sockets, the name, streams, listeners and the bound on streams and listeners; still to build are the resolver and the move. - the track, "What the node does not yet meet": stage E's line on Tcp::ready and Tcp::orphans and stage D's six lines from main, all kept, none reworded. The node's tests at this tree: exit 0 (datagram 5, lease 18, listeners 22, name 13, slirp 3, streams 34). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
…tion is the host's measured rule, a datagram socket holds a place BLOCKER 2. Node::listen takes the address the program bound and hands it to [tcp]. One the interface does not hold usable is refused (ListenRefused::NotLocal) in Shard::listen by [ip]'s is_assigned, the predicate [udp]'s bind refuses by, so both socket kinds have one rule. BLOCKER 3. Measured on macOS, by libc over loopback and by tests/host.rs through socket2: TCP_NODELAY set on a listening socket before a connection began is on the accepted socket; set after the connection was established and before the accept, it is not. The node gave it to both. It now does what the host does by [tcp]'s own inheritance (LS-10): set_listener_nodelay writes [tcp]'s listener, a connection takes the options at its SYN, and the accept seeds the stream with what Tcp::options, a new read, says the connection has. The write at the accept and its parameters are gone. Linux is not read: the host check's first run of tests/host.rs is that reading, and the new issue says so. BLOCKER 4. The loop over wake_each has its test: a listener ended for a refused wake gives its place to another in the same pass, asserted before any transmit opportunity. A datagram socket holds a place from its bind to its close, and a bind with none left is refused ResourceExhausted with nothing made. A connection that waits to be accepted holds none: places.rs and the track say why, and what the shell must price a listener at. NOTEs: the doc comment `orphans` took from `eligible` is back; the places line of the track names its own exit; the outside reading of a TCP segment is declared once, in tests/common, and read by both test files; issues/netstack-datagram-sockets-and-listeners-have-no-bound.md says what is left. socket2 is a new dev-dependency of toyos-net-node, already in the lock through the workspace's patch: it sets and reads a socket option std has no call for on a listener, on every host, with no unsafe. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
The 35 mutations of stage E, in 39 runs, at
|
| mutation | test that turned red | build | test |
|---|---|---|---|
t1-an-orphan-that-ends-stays-counted |
an_orphan_is_counted_until_both_fins |
0 | 101 |
t2-a-closed-connection-is-not-counted |
an_orphan_is_counted_until_both_fins |
0 | 101 |
t3-a-close-in-syn-received-is-not-counted |
a_close_before_the_handshake_ends_leaves_an_orphan_until_it_gives_up |
0 | 101 |
t4-ready-counts-handshakes-in-progress |
ready_counts_the_connections_accept_has_yet_to_return |
0 | 101 |
t5-options-are-the-defaults |
options_are_the_ones_the_connection_has |
0 | 101 |
o1-a-listener-is-one-connection |
a_handshake_nobody_finishes_leaves_the_port_open_and_is_given_up |
0 | 101 |
o1-a-listener-is-one-connection |
a_connect_between_two_accepts_is_queued_not_reset |
0 | 101 |
e01-a-pass-wakes-nobody |
a_listener_answers_a_syn_and_wakes_its_owner_when_the_handshake_ends |
0 | 101 |
e02-an-accept-ends-in-no-pass |
a_listener_answers_a_syn_and_wakes_its_owner_when_the_handshake_ends |
0 | 101 |
e03-an-owner-holds-one-wake-at-most |
a_connect_between_two_accepts_is_queued_not_reset |
0 | 101 |
e04-only-an-accept-that-takes-spends-a-wake |
an_accept_spends_a_wake_whatever_it_answers |
0 | 101 |
e19-a-drawn-port-reads-the-draws-high-half |
a_listen_on_a_taken_port_is_refused_and_a_drawn_port_listens |
0 | 101 |
e05-a-wake-ignores-places |
a_wake_is_owed_only_for_a_connection_there_is_a_place_for |
0 | 101 |
e06-an-accept-ignores-places |
a_wake_is_owed_only_for_a_connection_there_is_a_place_for |
0 | 101 |
e07-a-finishing-connection-holds-no-place |
a_wake_is_owed_only_for_a_connection_there_is_a_place_for |
0 | 101 |
e13-a-connect-ignores-places |
a_connect_past_the_places_is_refused_and_sends_nothing |
0 | 101 |
e18-a-stream-holds-no-place |
a_connect_past_the_places_is_refused_and_sends_nothing |
0 | 101 |
e14-a-listen-ignores-places |
a_listener_holds_a_place_and_a_listen_without_one_makes_nothing |
0 | 101 |
e15-a-listener-holds-no-place |
a_listener_holds_a_place_and_a_listen_without_one_makes_nothing |
0 | 101 |
e09-a-closed-connect-wakes-nobody |
closing_a_connect_gives_its_place_to_a_connection_that_waits |
0 | 101 |
e10-a-stream-reset-for-its-pipe-wakes-nobody |
a_stream_reset_for_its_pipe_gives_its_place_to_a_connection_that_waits |
0 | 101 |
e11-a-closed-listener-wakes-nobody |
closing_a_listener_gives_its_place_to_a_connection_that_waits_at_another |
0 | 101 |
e12-more-places-wake-nobody |
more_places_wake_the_owner_of_a_connection_that_waits |
0 | 101 |
e16-a-closed-listener-stays-in-the-stack |
closing_a_listener_resets_what_waits_and_frees_its_port |
0 | 101 |
e17-a-refused-wake-is-ignored |
a_wake_the_owners_pipe_refuses_ends_the_listener |
0 | 101 |
e20-a-listeners-option-does-not-reach-tcp |
a_stream_starts_with_the_options_its_connection_took_from_its_listener |
0 | 101 |
e21-an-accepted-stream-holds-no-option |
a_stream_starts_with_the_options_its_connection_took_from_its_listener |
0 | 101 |
e22-an-accepted-stream-is-counted-by-the-nodes-address |
an_accepted_stream_is_one_of_its_peers_addresss_sixteen |
0 | 101 |
e23-an-id-is-used-twice |
a_request_for_a_stream_that_was_cut_names_nothing |
0 | 101 |
e05-a-wake-ignores-places |
peers_at_more_addresses_than_there_are_places_hold_no_stream_past_them |
0 | 101 |
e06-an-accept-ignores-places |
peers_at_more_addresses_than_there_are_places_hold_no_stream_past_them |
0 | 101 |
e18-a-stream-holds-no-place |
peers_at_more_addresses_than_there_are_places_hold_no_stream_past_them |
0 | 101 |
e24-a-bind-ignores-places |
a_datagram_socket_holds_a_place_and_a_bind_without_one_makes_nothing |
0 | 101 |
e25-a-datagram-socket-holds-no-place |
a_datagram_socket_holds_a_place_and_a_bind_without_one_makes_nothing |
0 | 101 |
e26-a-closed-socket-wakes-nobody |
a_datagram_socket_holds_a_place_and_a_bind_without_one_makes_nothing |
0 | 101 |
e27-a-closed-socket-keeps-its-place |
a_datagram_socket_holds_a_place_and_a_bind_without_one_makes_nothing |
0 | 101 |
e28-a-listen-is-at-every-address |
a_listener_is_at_the_address_it_named_and_only_one_the_machine_holds |
0 | 101 |
e30-a-listen-takes-an-address-nobody-holds |
a_listener_is_at_the_address_it_named_and_only_one_the_machine_holds |
0 | 101 |
e29-a-listener-ended-in-a-pass-ends-the-pass |
a_listener_ended_for_its_wake_gives_its_place_to_another_in_the_same_pass |
0 | 101 |
The host program
Cargo.toml names libc = "0.2" and an empty [workspace]; cargo run --offline on macOS 27, arm64, exit 0.
src/main.rs
//! What this host's TCP gives an accepted socket of its listener's TCP_NODELAY: set on the
//! listening socket before the connection began, and after it was established but before the
//! accept. Loopback; libc's setsockopt and getsockopt on the raw descriptors.
use std::net::{Ipv4Addr, TcpListener, TcpStream};
use std::os::fd::AsRawFd;
fn set(fd: i32, on: bool) {
let value: libc::c_int = on.into();
let done = unsafe { libc::setsockopt(fd, libc::IPPROTO_TCP, libc::TCP_NODELAY, (&raw const value).cast(), size_of::<libc::c_int>() as libc::socklen_t) };
assert_eq!(done, 0, "setsockopt: {}", std::io::Error::last_os_error());
}
fn get(fd: i32) -> bool {
let mut value: libc::c_int = -1;
let mut len = size_of::<libc::c_int>() as libc::socklen_t;
let done = unsafe { libc::getsockopt(fd, libc::IPPROTO_TCP, libc::TCP_NODELAY, (&raw mut value).cast(), &raw mut len) };
assert_eq!(done, 0, "getsockopt: {}", std::io::Error::last_os_error());
value != 0
}
fn main() {
println!("{} {}", std::env::consts::OS, std::env::consts::ARCH);
for before in [true, false] {
let listener = TcpListener::bind((Ipv4Addr::LOCALHOST, 0)).unwrap();
println!("listener at first: {}", get(listener.as_raw_fd()));
if before {
set(listener.as_raw_fd(), true);
}
// A connect that returned is a handshake that ended: the connection waits in the queue.
let _peer = TcpStream::connect(listener.local_addr().unwrap()).unwrap();
if !before {
set(listener.as_raw_fd(), true);
}
let (accepted, _) = listener.accept().unwrap();
let when = if before { "before the connection began" } else { "after it was established, before the accept" };
println!("set on the listener {when}: listener {}, accepted {}", get(listener.as_raw_fd()), get(accepted.as_raw_fd()));
}
}output
Locking 1 package to latest compatible version
Compiling libc v0.2.190
Compiling listener-nodelay v0.0.0 (the scratch directory)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.51s
Running `target/debug/listener-nodelay`
macos aarch64
listener at first: false
set on the listener before the connection began: listener true, accepted true
listener at first: false
set on the listener after it was established, before the accept: listener true, accepted false
The patches
e01-a-pass-wakes-nobody
diff --git a/userland/netstack/node/src/streams.rs b/userland/netstack/node/src/streams.rs
index 1346df63c..8d9618378 100644
--- a/userland/netstack/node/src/streams.rs
+++ b/userland/netstack/node/src/streams.rs
@@ -353,7 +353,6 @@ impl Node {
*peers = peers.saturating_add(1);
}
live.retain(|id, stream| (connects && !stream.connecting) || stream.pass(*id, now, stack, events, &mut extended));
- self.wake_owners(now);
}
/// The client lets go of the stream: nobody reads it, and what its pipe still holds is sente02-an-accept-ends-in-no-pass
diff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 6d27272ac..cdfd85e39 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -138,7 +138,6 @@ impl Node {
listener.unspent = listener.unspent.saturating_sub(1);
let bound = listener.bound;
let answer = self.take(bound, pipes);
- self.bridge(now);
answer
}
e03-an-owner-holds-one-wake-at-most
diff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 0df6d315e..da391fd88 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -150,7 +150,7 @@ impl Node {
let stack = &mut self.stack;
let refused = self.listeners.live.iter_mut().find_map(|(id, listener)| {
let owed = stack.tcp_ready(listener.bound).min(room);
- while listener.unspent < owed {
+ while listener.unspent < owed.min(1) {
if let Err(refusal) = listener.owner.wake() {
return Some((*id, refusal));
}e04-only-an-accept-that-takes-spends-a-wake
diff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 6d27272ac..f0a53ca2b 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -135,9 +135,11 @@ impl Node {
/// and what it already received moves at once.
pub fn accept(&mut self, now: Instant, id: ListenerId, pipes: Option<Pipes>) -> Result<Accepted, AcceptRefused> {
let Some(listener) = self.listeners.live.get_mut(&id) else { return Err(AcceptRefused::NoListener) };
- listener.unspent = listener.unspent.saturating_sub(1);
let bound = listener.bound;
let answer = self.take(bound, pipes);
+ if let (Ok(_), Some(listener)) = (&answer, self.listeners.live.get_mut(&id)) {
+ listener.unspent = listener.unspent.saturating_sub(1);
+ }
self.bridge(now);
answer
}e05-a-wake-ignores-places
diff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 0df6d315e..efa6d38aa 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -149,7 +149,7 @@ impl Node {
let room = self.room();
let stack = &mut self.stack;
let refused = self.listeners.live.iter_mut().find_map(|(id, listener)| {
- let owed = stack.tcp_ready(listener.bound).min(room);
+ let owed = stack.tcp_ready(listener.bound);
while listener.unspent < owed {
if let Err(refusal) = listener.owner.wake() {
return Some((*id, refusal));e06-an-accept-ignores-places
diff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 6d27272ac..a5f7a1aad 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -144,9 +144,6 @@ impl Node {
fn take(&mut self, bound: toyos_net_tcp::ListenerId, pipes: Option<Pipes>) -> Result<Accepted, AcceptRefused> {
let Some(pipes) = pipes else { return Err(AcceptRefused::NoPipes) };
- if self.room() == 0 {
- return Err(AcceptRefused::Full);
- }
let Some((conn, tuple, options)) = self.stack.tcp_accept(bound) else { return Err(AcceptRefused::Nothing) };
// The peer's address is what `streams` counts a stream its client can see no more by.
let id = self.streams.accepted(conn, tuple.remote.addr, options, pipes);e07-a-finishing-connection-holds-no-place
diff --git a/userland/netstack/node/src/places.rs b/userland/netstack/node/src/places.rs
index 1113a98ea..4cbd0412c 100644
--- a/userland/netstack/node/src/places.rs
+++ b/userland/netstack/node/src/places.rs
@@ -38,7 +38,7 @@ impl Node {
/// The places taken: streams, listeners, datagram sockets, and connections [tcp] is
/// finishing alone.
pub fn held(&self) -> usize {
- self.streams().saturating_add(self.listeners()).saturating_add(self.sockets).saturating_add(self.stack.tcp_orphans())
+ self.streams().saturating_add(self.listeners()).saturating_add(self.sockets)
}
pub(crate) fn room(&self) -> usize {e09-a-closed-connect-wakes-nobody
diff --git a/userland/netstack/node/src/streams.rs b/userland/netstack/node/src/streams.rs
index 1346df63c..d7bd4849d 100644
--- a/userland/netstack/node/src/streams.rs
+++ b/userland/netstack/node/src/streams.rs
@@ -364,7 +364,6 @@ impl Node {
self.stack.tcp_abort(now, stream.conn);
self.streams.live.remove(&id);
self.streams.events.push_back(StreamEvent::Closed { id });
- self.wake_owners(now);
return;
}
stream.to_client = None;e10-a-stream-reset-for-its-pipe-wakes-nobody
diff --git a/userland/netstack/node/src/streams.rs b/userland/netstack/node/src/streams.rs
index 1346df63c..0669d43a0 100644
--- a/userland/netstack/node/src/streams.rs
+++ b/userland/netstack/node/src/streams.rs
@@ -419,7 +419,6 @@ impl Node {
if held {
self.stack.tcp_abort(now, stream.conn);
self.streams.live.remove(&id);
- self.wake_owners(now);
}
}
e11-a-closed-listener-wakes-nobody
diff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 0df6d315e..cf1255229 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -124,9 +124,6 @@ impl Node {
/// says nobody holds the other end of the wake pipe. `false` is an id that names no listener.
pub fn close_listener(&mut self, now: Instant, id: ListenerId) -> bool {
let closed = self.end_listener(now, id);
- if closed {
- self.wake_owners(now);
- }
closed
}
e12-more-places-wake-nobody
diff --git a/userland/netstack/node/src/places.rs b/userland/netstack/node/src/places.rs
index 1113a98ea..91efad0f2 100644
--- a/userland/netstack/node/src/places.rs
+++ b/userland/netstack/node/src/places.rs
@@ -32,7 +32,6 @@ impl Node {
/// How many places the node has from here on. Nothing held is let go for a smaller number.
pub fn set_places(&mut self, now: Instant, places: usize) {
self.places = places;
- self.wake_owners(now);
}
/// The places taken: streams, listeners, datagram sockets, and connections [tcp] ise13-a-connect-ignores-places
diff --git a/userland/netstack/node/src/streams.rs b/userland/netstack/node/src/streams.rs
index 091d3fb23..4aec759a6 100644
--- a/userland/netstack/node/src/streams.rs
+++ b/userland/netstack/node/src/streams.rs
@@ -334,9 +334,6 @@ impl Node {
/// An active open to `remote`, answered by a [`StreamEvent`] once the handshake ends or
/// `timeout` passes. Refused, nothing was sent and the pipe ends are dropped.
pub fn connect(&mut self, now: Instant, remote: Endpoint, timeout: Option<Duration>, pipes: Pipes) -> Result<StreamId, ConnectRefused> {
- if self.room() == 0 {
- return Err(ConnectRefused::Full);
- }
let conn = self.stack.tcp_connect(now, remote).map_err(ConnectRefused::Stack)?;
let deadline = timeout.map(|within| now.after(within));
// An active open has [tcp]'s defaults until `set_nodelay`.e14-a-listen-ignores-places
diff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 6d27272ac..43605e498 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -98,9 +98,6 @@ impl Node {
/// hold, on `port` or on a port chosen from `draw`'s candidates. Answers the listener and
/// its port. Refused, nothing was made and `owner` is dropped.
pub fn listen(&mut self, addr: Ipv4Addr, port: Option<Port>, owner: Box<dyn Wake>, mut draw: impl FnMut() -> u32) -> Result<(ListenerId, Port), ListenRefused> {
- if self.room() == 0 {
- return Err(ListenRefused::Full);
- }
let candidate = || {
let [low, high, ..] = draw().to_le_bytes();
u16::from_le_bytes([low, high])e15-a-listener-holds-no-place
diff --git a/userland/netstack/node/src/places.rs b/userland/netstack/node/src/places.rs
index 1113a98ea..ac49d8fcc 100644
--- a/userland/netstack/node/src/places.rs
+++ b/userland/netstack/node/src/places.rs
@@ -38,7 +38,7 @@ impl Node {
/// The places taken: streams, listeners, datagram sockets, and connections [tcp] is
/// finishing alone.
pub fn held(&self) -> usize {
- self.streams().saturating_add(self.listeners()).saturating_add(self.sockets).saturating_add(self.stack.tcp_orphans())
+ self.streams().saturating_add(self.sockets).saturating_add(self.stack.tcp_orphans())
}
pub(crate) fn room(&self) -> usize {e16-a-closed-listener-stays-in-the-stack
diff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 0df6d315e..341d5ee6a 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -132,7 +132,6 @@ impl Node {
fn end_listener(&mut self, now: Instant, id: ListenerId) -> bool {
let Some(listener) = self.listeners.live.remove(&id) else { return false };
- self.stack.tcp_close_listener(now, listener.bound);
true
}
e17-a-refused-wake-is-ignored
diff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 0df6d315e..43e18cff3 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -151,8 +151,8 @@ impl Node {
let refused = self.listeners.live.iter_mut().find_map(|(id, listener)| {
let owed = stack.tcp_ready(listener.bound).min(room);
while listener.unspent < owed {
- if let Err(refusal) = listener.owner.wake() {
- return Some((*id, refusal));
+ if listener.owner.wake().is_err() {
+ break;
}
listener.unspent = listener.unspent.saturating_add(1);
}e18-a-stream-holds-no-place
diff --git a/userland/netstack/node/src/places.rs b/userland/netstack/node/src/places.rs
index 1113a98ea..b4f3ae458 100644
--- a/userland/netstack/node/src/places.rs
+++ b/userland/netstack/node/src/places.rs
@@ -38,7 +38,7 @@ impl Node {
/// The places taken: streams, listeners, datagram sockets, and connections [tcp] is
/// finishing alone.
pub fn held(&self) -> usize {
- self.streams().saturating_add(self.listeners()).saturating_add(self.sockets).saturating_add(self.stack.tcp_orphans())
+ self.listeners().saturating_add(self.sockets).saturating_add(self.stack.tcp_orphans())
}
pub(crate) fn room(&self) -> usize {e19-a-drawn-port-reads-the-draws-high-half
diff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 6d27272ac..b94db2aeb 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -102,7 +102,7 @@ impl Node {
return Err(ListenRefused::Full);
}
let candidate = || {
- let [low, high, ..] = draw().to_le_bytes();
+ let [.., low, high] = draw().to_le_bytes();
u16::from_le_bytes([low, high])
};
let (bound, port) = match self.stack.tcp_listen(addr, port, candidate) {e20-a-listeners-option-does-not-reach-tcp
diff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 6d27272ac..e53562cad 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -123,7 +123,6 @@ impl Node {
pub fn set_listener_nodelay(&mut self, id: ListenerId, nodelay: bool) -> bool {
let Some(listener) = self.listeners.live.get_mut(&id) else { return false };
listener.options.nodelay = nodelay;
- self.stack.tcp_set_listener_options(listener.bound, listener.options);
true
}
e21-an-accepted-stream-holds-no-option
diff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 6d27272ac..952672c8e 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -149,7 +149,7 @@ impl Node {
}
let Some((conn, tuple, options)) = self.stack.tcp_accept(bound) else { return Err(AcceptRefused::Nothing) };
// The peer's address is what `streams` counts a stream its client can see no more by.
- let id = self.streams.accepted(conn, tuple.remote.addr, options, pipes);
+ let id = self.streams.accepted(conn, tuple.remote.addr, Options::default(), pipes);
Ok(Accepted { id, remote: tuple.remote, local: tuple.local.port })
}
e22-an-accepted-stream-is-counted-by-the-nodes-address
diff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 6d27272ac..1ab57411a 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -149,7 +149,7 @@ impl Node {
}
let Some((conn, tuple, options)) = self.stack.tcp_accept(bound) else { return Err(AcceptRefused::Nothing) };
// The peer's address is what `streams` counts a stream its client can see no more by.
- let id = self.streams.accepted(conn, tuple.remote.addr, options, pipes);
+ let id = self.streams.accepted(conn, tuple.local.addr, options, pipes);
Ok(Accepted { id, remote: tuple.remote, local: tuple.local.port })
}
e23-an-id-is-used-twice
diff --git a/userland/netstack/node/src/streams.rs b/userland/netstack/node/src/streams.rs
index 091d3fb23..dca817638 100644
--- a/userland/netstack/node/src/streams.rs
+++ b/userland/netstack/node/src/streams.rs
@@ -313,7 +313,6 @@ impl Streams {
/// Holds `stream` under an id of its own.
fn hold(&mut self, stream: Stream) -> StreamId {
let id = StreamId(self.next);
- self.next = self.next.saturating_add(1);
self.live.insert(id, stream);
id
}e24-a-bind-ignores-places
diff --git a/userland/netstack/node/src/datagram.rs b/userland/netstack/node/src/datagram.rs
index b24dead14..2b19d06a1 100644
--- a/userland/netstack/node/src/datagram.rs
+++ b/userland/netstack/node/src/datagram.rs
@@ -103,9 +103,6 @@ impl Node {
/// or, with none named, an ephemeral one, which spends the one draw. Returns the socket and
/// the port it holds.
pub fn udp_bind(&mut self, addr: Ipv4Addr, port: Option<Port>, draw: impl FnOnce() -> u32) -> Result<(DatagramId, Port), Refused> {
- if self.room() == 0 {
- return Err(Refused::ResourceExhausted);
- }
let (id, port) = self.stack.bind(addr, port, draw).map_err(refused)?;
self.sockets = self.sockets.saturating_add(1);
Ok((DatagramId(id), port))e25-a-datagram-socket-holds-no-place
diff --git a/userland/netstack/node/src/places.rs b/userland/netstack/node/src/places.rs
index 1113a98ea..a82719c29 100644
--- a/userland/netstack/node/src/places.rs
+++ b/userland/netstack/node/src/places.rs
@@ -38,7 +38,7 @@ impl Node {
/// The places taken: streams, listeners, datagram sockets, and connections [tcp] is
/// finishing alone.
pub fn held(&self) -> usize {
- self.streams().saturating_add(self.listeners()).saturating_add(self.sockets).saturating_add(self.stack.tcp_orphans())
+ self.streams().saturating_add(self.listeners()).saturating_add(self.stack.tcp_orphans())
}
pub(crate) fn room(&self) -> usize {e26-a-closed-socket-wakes-nobody
diff --git a/userland/netstack/node/src/datagram.rs b/userland/netstack/node/src/datagram.rs
index b24dead14..bb81727af 100644
--- a/userland/netstack/node/src/datagram.rs
+++ b/userland/netstack/node/src/datagram.rs
@@ -128,7 +128,6 @@ impl Node {
pub fn udp_close(&mut self, now: Instant, id: DatagramId) -> Result<(), Refused> {
self.stack.close(now, id.0).map_err(refused)?;
self.sockets = self.sockets.saturating_sub(1);
- self.wake_owners(now);
Ok(())
}
}e27-a-closed-socket-keeps-its-place
diff --git a/userland/netstack/node/src/datagram.rs b/userland/netstack/node/src/datagram.rs
index b24dead14..4606c036b 100644
--- a/userland/netstack/node/src/datagram.rs
+++ b/userland/netstack/node/src/datagram.rs
@@ -127,7 +127,6 @@ impl Node {
/// accepted still leaves, to [udp]'s bound. Its place is back.
pub fn udp_close(&mut self, now: Instant, id: DatagramId) -> Result<(), Refused> {
self.stack.close(now, id.0).map_err(refused)?;
- self.sockets = self.sockets.saturating_sub(1);
self.wake_owners(now);
Ok(())
}e28-a-listen-is-at-every-address
diff --git a/userland/netstack/node/src/lease/tcp.rs b/userland/netstack/node/src/lease/tcp.rs
index bd9bcbd73..d6e04ad2b 100644
--- a/userland/netstack/node/src/lease/tcp.rs
+++ b/userland/netstack/node/src/lease/tcp.rs
@@ -72,7 +72,7 @@ impl Stack {
/// A passive open at `addr`, 0.0.0.0 meaning every address the interface holds or comes to
/// hold, and at `port` or at one of `random`'s candidates.
pub(crate) fn tcp_listen(&mut self, addr: Ipv4Addr, port: Option<Port>, random: impl FnMut() -> u16) -> Result<(ListenerId, Port), ListenError> {
- let id = self.shard.listen(addr, port, random)?;
+ let id = self.shard.listen(Ipv4Addr::UNSPECIFIED, port, random)?;
Ok((id, held(self.shard.listener_port(id))))
}
e29-a-listener-ended-in-a-pass-ends-the-pass
diff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 6d27272ac..0149aea23 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -174,7 +174,7 @@ impl Node {
pub(crate) fn wake_owners(&mut self, now: Instant) {
// A listener ended here gives its place back, which another's owner may be owed a wake
// for.
- while self.wake_each(now) {}
+ self.wake_each(now);
}
/// One round over the listeners, up to the first whose pipe refuses a wake; `true` ended it.e30-a-listen-takes-an-address-nobody-holds
diff --git a/toyos-net-shard/src/lib.rs b/toyos-net-shard/src/lib.rs
index d7ee5fb50..fd8e2d469 100644
--- a/toyos-net-shard/src/lib.rs
+++ b/toyos-net-shard/src/lib.rs
@@ -479,9 +479,6 @@ impl Shard {
/// `addr` is the local address to listen on, UNSPECIFIED for any, and otherwise one [ip]
/// holds assigned or announcing, as a datagram socket's is; port 0 takes `random`'s draws.
pub fn listen(&mut self, addr: Ipv4Addr, port: Option<Port>, random: impl FnMut() -> u16) -> Result<ListenerId, ListenError> {
- if !addr.is_unspecified() && !self.ip.is_assigned(addr) {
- return Err(ListenError::NotLocal);
- }
self.tcp.listen(addr, port, random).map_err(ListenError::Tcp)
}
o1-a-listener-is-one-connection
diff --git a/toyos-net-shard/tcp/src/stack.rs b/toyos-net-shard/tcp/src/stack.rs
index 4f71e2fb4..d7fd6b6b5 100644
--- a/toyos-net-shard/tcp/src/stack.rs
+++ b/toyos-net-shard/tcp/src/stack.rs
@@ -749,7 +749,7 @@ impl Tcp {
return;
}
let Some(listener) = value(&mut self.listeners, index) else { return };
- if listener.pending.len() >= limits::LISTEN_PENDING || listener.ready.len() >= limits::LISTEN_READY {
+ if !listener.pending.is_empty() || !listener.ready.is_empty() {
self.log.count(Counter::ListenOverflow);
return;
}t1-an-orphan-that-ends-stays-counted
diff --git a/toyos-net-shard/tcp/src/stack.rs b/toyos-net-shard/tcp/src/stack.rs
index 4f71e2fb4..996fc0cd7 100644
--- a/toyos-net-shard/tcp/src/stack.rs
+++ b/toyos-net-shard/tcp/src/stack.rs
@@ -449,9 +449,6 @@ impl Tcp {
fn free(&mut self, index: u32) {
let Some(generation) = self.conns.get(usize::try_from(index).unwrap_or(usize::MAX)).map(|s| s.generation) else { return };
let Some(conn) = release(&mut self.conns, &mut self.free_conns, index) else { return };
- if conn.user == User::Orphan {
- self.orphans = self.orphans.saturating_sub(1);
- }
let remote = conn.tuple.remote.addr;
let parked = self.parked.get(&remote).is_some_and(|p| p.conns.contains(&index));
// Its index may name another connection next.t2-a-closed-connection-is-not-counted
diff --git a/toyos-net-shard/tcp/src/stack.rs b/toyos-net-shard/tcp/src/stack.rs
index 4f71e2fb4..9856e5858 100644
--- a/toyos-net-shard/tcp/src/stack.rs
+++ b/toyos-net-shard/tcp/src/stack.rs
@@ -1085,7 +1085,6 @@ impl Tcp {
sync.shutdown_write(now);
sync.orphan(now);
conn.user = User::Orphan;
- self.orphans = self.orphans.saturating_add(1);
self.settle(id.index, now);
}
}t3-a-close-in-syn-received-is-not-counted
diff --git a/toyos-net-shard/tcp/src/stack.rs b/toyos-net-shard/tcp/src/stack.rs
index 4f71e2fb4..65ba099a1 100644
--- a/toyos-net-shard/tcp/src/stack.rs
+++ b/toyos-net-shard/tcp/src/stack.rs
@@ -1072,7 +1072,6 @@ impl Tcp {
Tcb::SynRcvd(rcvd) => {
rcvd.shutdown_write();
conn.user = User::Orphan;
- self.orphans = self.orphans.saturating_add(1);
self.settle(id.index, now);
}
Tcb::Sync(sync) if sync.rx.unread() > 0 => {t4-ready-counts-handshakes-in-progress
diff --git a/toyos-net-shard/tcp/src/stack.rs b/toyos-net-shard/tcp/src/stack.rs
index 4f71e2fb4..178b9d5a8 100644
--- a/toyos-net-shard/tcp/src/stack.rs
+++ b/toyos-net-shard/tcp/src/stack.rs
@@ -585,7 +585,7 @@ impl Tcp {
/// How many children completed their handshake and wait for [`Self::accept`].
pub fn ready(&mut self, id: ListenerId) -> Result<usize, Error> {
- slot(&mut self.listeners, id.index, id.generation).map(|l| l.ready.len()).ok_or(Error::NoSuchSocket)
+ slot(&mut self.listeners, id.index, id.generation).map(|l| l.pending.len()).ok_or(Error::NoSuchSocket)
}
/// The oldest child that completed its handshake.t5-options-are-the-defaults
diff --git a/toyos-net-shard/tcp/src/stack.rs b/toyos-net-shard/tcp/src/stack.rs
index b0c7a8391..ec389d86e 100644
--- a/toyos-net-shard/tcp/src/stack.rs
+++ b/toyos-net-shard/tcp/src/stack.rs
@@ -977,7 +977,7 @@ impl Tcp {
/// The options the connection has: the ones its listener had when its SYN arrived, or the
/// defaults of an active open, until [`Self::set_options`] writes others.
pub fn options(&mut self, id: ConnId) -> Result<Options, Error> {
- Ok(self.conn(id)?.options)
+ self.conn(id).map(|_| Options::default())
}
pub fn set_options(&mut self, now: Instant, id: ConnId, options: Options) -> Result<(), Error> {|
Review of Net lines against Round 1's BLOCKERs
Round 1's NOTEs are each closed: BLOCKER
NOTE
Asked in the brief
If Linux answers differently. POSIX is silent, so the rule is what a portable program can depend on. A program cannot know which connections already wait at its listener, so it cannot depend on either answer to the second question; the only use it can rely on is the option set before any connection begins. So: if Linux differs on the second arm, ToyOS keeps [tcp]'s rule at the SYN, which is one mechanism, already specified (LS-10) and what macOS measured; the arm's assertion goes, and the issue records each host's answer with its command. If Linux differs on the first arm, hosts do not agree that a listener's option reaches a connection at all; that is no implementer's and no reviewer's choice, the branch stops and the owner rules. My memory of Linux is that it answers as macOS did on both; nobody has measured it.
A waiting connection holds no place. Sound against the peer: counted, a handshake alone would take a place, and any peer of any listener would refuse every client everything; uncounted, a peer fills the queue of the listener it reaches. What it costs is recorded with an exit at the move, and the move will find the exit hard: one number cannot price a stream at two buffers and a listener at 8.4 MB without being either very small or false as a memory bound. That is the move's to answer in code and its reviewer's to hold; it is a present weakness, known and tracked. A datagram socket's place. Evidence. Not met for landing. For the round it is commands and exits with no log I can read; the patches I checked against the head's blobs and the assertions they must fail, for t5, e20, e21 and e24 to e30. e01, e03, e05, e09 to e11, e13, e16, e17, e23, t1 to t4 and o1 are posted against earlier blobs and apply by offset; their context is in the head unchanged. CI's clippy. Nothing the round adds meets one of the ten: every clone is read after ( What the branch must show nextBLOCKER 5 and the first three NOTEs are code or records and come back here for a round. After it, and after #774 is on
SEND BACK |
…dress went is ended, the host oracle waits on its listener A port is one listener's. [tcp] hands a SYN to the listener that named its address before one at every address (LS-09), so with a server at 0.0.0.0:22 any program that reached netstack listened at the machine's address on 22 and was handed every connection from then on: authority nobody moved into it. A listen carries no word for its program, so the node cannot allow the pair to one owner and refuse it to another. Node::listen now refuses a named port any listener holds, whichever address either named, as [udp] holds a port once; LS-09 stays [tcp]'s and is not reachable from the node. The track has the line and its exit: a listen that carries its program. A listener whose address went is ended. One that named the machine's address kept its place and its port when the lease lost the address, was handed nothing and its owner was told nothing. The pass that writes the wakes now ends it when the lease names another address or none: [tcp] resets what waits, the port and the place are free, the owner reads the end of its wake pipe, and the shell reads Node::drain_ended_listeners, which replaces drain_refused_listeners and says why (ListenerEnd). Ended and not kept, because a listener's owner makes no call a refusal could answer: it only waits for wakes, and none would come. A renewal and a link that returns keep the address and the listener. tests/host.rs waits on the event its second answer depends on. A connect returns at the SYN-ACK and the listener's end is established by the ACK after it, so the option set "after the connection was established" rested on loopback delivering that ACK first. The test now sets it only once the listener is readable (mio, in the lock already, default features off), bounded by a ceiling that fails, and asserts the pair once with the host's name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
…ge E By hand: the node's module list and its header's paragraph on who owns what (both sides whole), the manifest's description (what both sides put in the package), and the track's stage 5 paragraph (in the tree: the resolver and listeners both; still to build: the move). Everything else merged by itself. places.rs says what the merged tree does: the responder's socket and each query's are bound past Node::udp_bind and hold no place, so clients at the bound refuse no lookup. The resolver's test that binds every dynamic port but one now gives its node that many places, since a client's socket holds one, and asserts that with every place a client's socket the lookup still starts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
Shard::listens_at is the one rule for an address a listener may be at: a listen is refused by it, and the pass that writes the wakes ends a listener it no longer holds for. No listener is made that the next pass ends, and none stands where a listen would be refused. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
|
Mutation patches of round 5, all 41, each generated by d21-the-nodes-deadline-leaves-streams-out.patchdiff --git a/userland/netstack/node/src/lib.rs b/userland/netstack/node/src/lib.rs
index f842572af..a405eb61c 100644
--- a/userland/netstack/node/src/lib.rs
+++ b/userland/netstack/node/src/lib.rs
@@ -172,7 +172,7 @@ impl Node {
pub fn next_deadline(&self) -> Option<Instant> {
let name = self.name.as_ref().and_then(name::Name::next_deadline);
- self.stack.next_deadline().into_iter().chain(self.client.next_deadline()).chain(name).chain(self.resolver.next_deadline()).chain(self.streams.next_deadline()).min()
+ self.stack.next_deadline().into_iter().chain(self.client.next_deadline()).chain(name).chain(self.resolver.next_deadline()).min()
}
/// Every deadline at or before `now`; the frames they make due wait for [`Self::transmit`].e01-a-pass-wakes-nobody.patchdiff --git a/userland/netstack/node/src/streams.rs b/userland/netstack/node/src/streams.rs
index 41e86527a..c225ede9b 100644
--- a/userland/netstack/node/src/streams.rs
+++ b/userland/netstack/node/src/streams.rs
@@ -357,7 +357,6 @@ impl Node {
*peers = peers.saturating_add(1);
}
live.retain(|id, stream| (connects && !stream.connecting) || stream.pass(*id, now, stack, events, &mut extended));
- self.wake_owners(now);
}
/// The client lets go of the stream: nobody reads it, and what its pipe still holds is sente02-an-accept-ends-in-no-pass.patchdiff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 327af671f..f05dbe131 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -163,7 +163,6 @@ impl Node {
listener.unspent = listener.unspent.saturating_sub(1);
let bound = listener.bound;
let answer = self.take(bound, pipes);
- self.bridge(now);
answer
}
e03-an-owner-holds-one-wake-at-most.patchdiff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 327af671f..1f2a6a295 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -212,7 +212,7 @@ impl Node {
return Some((*id, ListenerEnd::Address));
}
let owed = stack.tcp_ready(listener.bound).min(room);
- while listener.unspent < owed {
+ while listener.unspent < owed.min(1) {
if let Err(refusal) = listener.owner.wake() {
return Some((*id, ListenerEnd::Wake(refusal)));
}e04-only-an-accept-that-takes-spends-a-wake.patchdiff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 327af671f..bcfd298ae 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -160,9 +160,11 @@ impl Node {
/// and what it already received moves at once.
pub fn accept(&mut self, now: Instant, id: ListenerId, pipes: Option<Pipes>) -> Result<Accepted, AcceptRefused> {
let Some(listener) = self.listeners.live.get_mut(&id) else { return Err(AcceptRefused::NoListener) };
- listener.unspent = listener.unspent.saturating_sub(1);
let bound = listener.bound;
let answer = self.take(bound, pipes);
+ if let (Ok(_), Some(listener)) = (&answer, self.listeners.live.get_mut(&id)) {
+ listener.unspent = listener.unspent.saturating_sub(1);
+ }
self.bridge(now);
answer
}e05-a-wake-ignores-places.patchdiff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 327af671f..d082e5e52 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -211,7 +211,7 @@ impl Node {
if !stack.shard().listens_at(listener.at.addr) {
return Some((*id, ListenerEnd::Address));
}
- let owed = stack.tcp_ready(listener.bound).min(room);
+ let owed = stack.tcp_ready(listener.bound);
while listener.unspent < owed {
if let Err(refusal) = listener.owner.wake() {
return Some((*id, ListenerEnd::Wake(refusal)));e06-an-accept-ignores-places.patchdiff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 327af671f..50b634416 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -169,9 +169,6 @@ impl Node {
fn take(&mut self, bound: toyos_net_tcp::ListenerId, pipes: Option<Pipes>) -> Result<Accepted, AcceptRefused> {
let Some(pipes) = pipes else { return Err(AcceptRefused::NoPipes) };
- if self.room() == 0 {
- return Err(AcceptRefused::Full);
- }
let Some((conn, tuple, options)) = self.stack.tcp_accept(bound) else { return Err(AcceptRefused::Nothing) };
// The peer's address is what `streams` counts a stream its client can see no more by.
let id = self.streams.accepted(conn, tuple.remote.addr, options, pipes);e07-a-finishing-connection-holds-no-place.patchdiff --git a/userland/netstack/node/src/places.rs b/userland/netstack/node/src/places.rs
index ce45b8c18..b49e100c5 100644
--- a/userland/netstack/node/src/places.rs
+++ b/userland/netstack/node/src/places.rs
@@ -45,7 +45,7 @@ impl Node {
/// The places taken: streams, listeners, datagram sockets, and connections [tcp] is
/// finishing alone.
pub fn held(&self) -> usize {
- self.streams().saturating_add(self.listeners()).saturating_add(self.sockets).saturating_add(self.stack.tcp_orphans())
+ self.streams().saturating_add(self.listeners()).saturating_add(self.sockets)
}
pub(crate) fn room(&self) -> usize {e09-a-closed-connect-wakes-nobody.patchdiff --git a/userland/netstack/node/src/streams.rs b/userland/netstack/node/src/streams.rs
index 41e86527a..883e9f346 100644
--- a/userland/netstack/node/src/streams.rs
+++ b/userland/netstack/node/src/streams.rs
@@ -368,7 +368,6 @@ impl Node {
self.stack.tcp_abort(now, stream.conn);
self.streams.live.remove(&id);
self.streams.events.push_back(StreamEvent::Closed { id });
- self.wake_owners(now);
return;
}
stream.to_client = None;e10-a-stream-reset-for-its-pipe-wakes-nobody.patchdiff --git a/userland/netstack/node/src/streams.rs b/userland/netstack/node/src/streams.rs
index 41e86527a..f5931d971 100644
--- a/userland/netstack/node/src/streams.rs
+++ b/userland/netstack/node/src/streams.rs
@@ -419,7 +419,6 @@ impl Node {
if held {
self.stack.tcp_abort(now, stream.conn);
self.streams.live.remove(&id);
- self.wake_owners(now);
}
}
e11-a-closed-listener-wakes-nobody.patchdiff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 327af671f..81014d7de 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -182,9 +182,6 @@ impl Node {
/// says nobody holds the other end of the wake pipe. `false` is an id that names no listener.
pub fn close_listener(&mut self, now: Instant, id: ListenerId) -> bool {
let closed = self.end_listener(now, id);
- if closed {
- self.wake_owners(now);
- }
closed
}
e12-more-places-wake-nobody.patchdiff --git a/userland/netstack/node/src/places.rs b/userland/netstack/node/src/places.rs
index ce45b8c18..3087ec3d2 100644
--- a/userland/netstack/node/src/places.rs
+++ b/userland/netstack/node/src/places.rs
@@ -39,7 +39,6 @@ impl Node {
/// How many places the node has from here on. Nothing held is let go for a smaller number.
pub fn set_places(&mut self, now: Instant, places: usize) {
self.places = places;
- self.wake_owners(now);
}
/// The places taken: streams, listeners, datagram sockets, and connections [tcp] ise13-a-connect-ignores-places.patchdiff --git a/userland/netstack/node/src/streams.rs b/userland/netstack/node/src/streams.rs
index 41e86527a..6cb7ee30b 100644
--- a/userland/netstack/node/src/streams.rs
+++ b/userland/netstack/node/src/streams.rs
@@ -333,9 +333,6 @@ impl Node {
/// An active open to `remote`, answered by a [`StreamEvent`] once the handshake ends or
/// `timeout` passes. Refused, nothing was sent and the pipe ends are dropped.
pub fn connect(&mut self, now: Instant, remote: Endpoint, timeout: Option<Duration>, pipes: Pipes) -> Result<StreamId, ConnectRefused> {
- if self.room() == 0 {
- return Err(ConnectRefused::Full);
- }
let conn = self.stack.tcp_connect(now, remote).map_err(ConnectRefused::Stack)?;
let deadline = timeout.map(|within| now.after(within));
// An active open has [tcp]'s defaults until `set_nodelay`.e14-a-listen-ignores-places.patchdiff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 327af671f..67a0a126c 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -119,9 +119,6 @@ impl Node {
/// hold, on `port` or on a port chosen from `draw`'s candidates. Answers the listener and
/// its port. Refused, nothing was made and `owner` is dropped.
pub fn listen(&mut self, addr: Ipv4Addr, port: Option<Port>, owner: Box<dyn Wake>, mut draw: impl FnMut() -> u32) -> Result<(ListenerId, Port), ListenRefused> {
- if self.room() == 0 {
- return Err(ListenRefused::Full);
- }
// A drawn port is one [tcp] finds no listener on at any address.
if port.is_some_and(|port| self.listeners.live.values().any(|listener| listener.at.port == port)) {
return Err(ListenRefused::InUse);e15-a-listener-holds-no-place.patchdiff --git a/userland/netstack/node/src/places.rs b/userland/netstack/node/src/places.rs
index ce45b8c18..afdf25522 100644
--- a/userland/netstack/node/src/places.rs
+++ b/userland/netstack/node/src/places.rs
@@ -45,7 +45,7 @@ impl Node {
/// The places taken: streams, listeners, datagram sockets, and connections [tcp] is
/// finishing alone.
pub fn held(&self) -> usize {
- self.streams().saturating_add(self.listeners()).saturating_add(self.sockets).saturating_add(self.stack.tcp_orphans())
+ self.streams().saturating_add(self.sockets).saturating_add(self.stack.tcp_orphans())
}
pub(crate) fn room(&self) -> usize {e16-a-closed-listener-stays-in-the-stack.patchdiff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 327af671f..c0881d1bb 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -190,7 +190,6 @@ impl Node {
fn end_listener(&mut self, now: Instant, id: ListenerId) -> bool {
let Some(listener) = self.listeners.live.remove(&id) else { return false };
- self.stack.tcp_close_listener(now, listener.bound);
true
}
e17-a-refused-wake-is-ignored.patchdiff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 327af671f..d556e26f3 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -213,8 +213,8 @@ impl Node {
}
let owed = stack.tcp_ready(listener.bound).min(room);
while listener.unspent < owed {
- if let Err(refusal) = listener.owner.wake() {
- return Some((*id, ListenerEnd::Wake(refusal)));
+ if listener.owner.wake().is_err() {
+ break;
}
listener.unspent = listener.unspent.saturating_add(1);
}e18-a-stream-holds-no-place.patchdiff --git a/userland/netstack/node/src/places.rs b/userland/netstack/node/src/places.rs
index ce45b8c18..ff4b7856d 100644
--- a/userland/netstack/node/src/places.rs
+++ b/userland/netstack/node/src/places.rs
@@ -45,7 +45,7 @@ impl Node {
/// The places taken: streams, listeners, datagram sockets, and connections [tcp] is
/// finishing alone.
pub fn held(&self) -> usize {
- self.streams().saturating_add(self.listeners()).saturating_add(self.sockets).saturating_add(self.stack.tcp_orphans())
+ self.listeners().saturating_add(self.sockets).saturating_add(self.stack.tcp_orphans())
}
pub(crate) fn room(&self) -> usize {e19-a-drawn-port-reads-the-draws-high-half.patchdiff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 327af671f..9fa63551b 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -127,7 +127,7 @@ impl Node {
return Err(ListenRefused::InUse);
}
let candidate = || {
- let [low, high, ..] = draw().to_le_bytes();
+ let [.., low, high] = draw().to_le_bytes();
u16::from_le_bytes([low, high])
};
let (bound, port) = match self.stack.tcp_listen(addr, port, candidate) {e20-a-listeners-option-does-not-reach-tcp.patchdiff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 327af671f..1f0acd342 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -148,7 +148,6 @@ impl Node {
pub fn set_listener_nodelay(&mut self, id: ListenerId, nodelay: bool) -> bool {
let Some(listener) = self.listeners.live.get_mut(&id) else { return false };
listener.options.nodelay = nodelay;
- self.stack.tcp_set_listener_options(listener.bound, listener.options);
true
}
e21-an-accepted-stream-holds-no-option.patchdiff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 327af671f..58d4b8a1b 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -174,7 +174,7 @@ impl Node {
}
let Some((conn, tuple, options)) = self.stack.tcp_accept(bound) else { return Err(AcceptRefused::Nothing) };
// The peer's address is what `streams` counts a stream its client can see no more by.
- let id = self.streams.accepted(conn, tuple.remote.addr, options, pipes);
+ let id = self.streams.accepted(conn, tuple.remote.addr, Options::default(), pipes);
Ok(Accepted { id, remote: tuple.remote, local: tuple.local.port })
}
e22-an-accepted-stream-is-counted-by-the-nodes-address.patchdiff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 327af671f..e7b3b57b0 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -174,7 +174,7 @@ impl Node {
}
let Some((conn, tuple, options)) = self.stack.tcp_accept(bound) else { return Err(AcceptRefused::Nothing) };
// The peer's address is what `streams` counts a stream its client can see no more by.
- let id = self.streams.accepted(conn, tuple.remote.addr, options, pipes);
+ let id = self.streams.accepted(conn, tuple.local.addr, options, pipes);
Ok(Accepted { id, remote: tuple.remote, local: tuple.local.port })
}
e23-an-id-is-used-twice.patchdiff --git a/userland/netstack/node/src/streams.rs b/userland/netstack/node/src/streams.rs
index 41e86527a..958bf203d 100644
--- a/userland/netstack/node/src/streams.rs
+++ b/userland/netstack/node/src/streams.rs
@@ -313,7 +313,6 @@ impl Streams {
/// Holds `stream` under an id of its own.
fn hold(&mut self, stream: Stream) -> StreamId {
let id = StreamId(self.next);
- self.next = self.next.saturating_add(1);
self.live.insert(id, stream);
id
}e24-a-bind-ignores-places.patchdiff --git a/userland/netstack/node/src/datagram.rs b/userland/netstack/node/src/datagram.rs
index b24dead14..2b19d06a1 100644
--- a/userland/netstack/node/src/datagram.rs
+++ b/userland/netstack/node/src/datagram.rs
@@ -103,9 +103,6 @@ impl Node {
/// or, with none named, an ephemeral one, which spends the one draw. Returns the socket and
/// the port it holds.
pub fn udp_bind(&mut self, addr: Ipv4Addr, port: Option<Port>, draw: impl FnOnce() -> u32) -> Result<(DatagramId, Port), Refused> {
- if self.room() == 0 {
- return Err(Refused::ResourceExhausted);
- }
let (id, port) = self.stack.bind(addr, port, draw).map_err(refused)?;
self.sockets = self.sockets.saturating_add(1);
Ok((DatagramId(id), port))e25-a-datagram-socket-holds-no-place.patchdiff --git a/userland/netstack/node/src/places.rs b/userland/netstack/node/src/places.rs
index ce45b8c18..021315755 100644
--- a/userland/netstack/node/src/places.rs
+++ b/userland/netstack/node/src/places.rs
@@ -45,7 +45,7 @@ impl Node {
/// The places taken: streams, listeners, datagram sockets, and connections [tcp] is
/// finishing alone.
pub fn held(&self) -> usize {
- self.streams().saturating_add(self.listeners()).saturating_add(self.sockets).saturating_add(self.stack.tcp_orphans())
+ self.streams().saturating_add(self.listeners()).saturating_add(self.stack.tcp_orphans())
}
pub(crate) fn room(&self) -> usize {e26-a-closed-socket-wakes-nobody.patchdiff --git a/userland/netstack/node/src/datagram.rs b/userland/netstack/node/src/datagram.rs
index b24dead14..bb81727af 100644
--- a/userland/netstack/node/src/datagram.rs
+++ b/userland/netstack/node/src/datagram.rs
@@ -128,7 +128,6 @@ impl Node {
pub fn udp_close(&mut self, now: Instant, id: DatagramId) -> Result<(), Refused> {
self.stack.close(now, id.0).map_err(refused)?;
self.sockets = self.sockets.saturating_sub(1);
- self.wake_owners(now);
Ok(())
}
}e27-a-closed-socket-keeps-its-place.patchdiff --git a/userland/netstack/node/src/datagram.rs b/userland/netstack/node/src/datagram.rs
index b24dead14..4606c036b 100644
--- a/userland/netstack/node/src/datagram.rs
+++ b/userland/netstack/node/src/datagram.rs
@@ -127,7 +127,6 @@ impl Node {
/// accepted still leaves, to [udp]'s bound. Its place is back.
pub fn udp_close(&mut self, now: Instant, id: DatagramId) -> Result<(), Refused> {
self.stack.close(now, id.0).map_err(refused)?;
- self.sockets = self.sockets.saturating_sub(1);
self.wake_owners(now);
Ok(())
}e28-a-listen-is-at-every-address.patchdiff --git a/userland/netstack/node/src/lease/tcp.rs b/userland/netstack/node/src/lease/tcp.rs
index bd9bcbd73..d6e04ad2b 100644
--- a/userland/netstack/node/src/lease/tcp.rs
+++ b/userland/netstack/node/src/lease/tcp.rs
@@ -72,7 +72,7 @@ impl Stack {
/// A passive open at `addr`, 0.0.0.0 meaning every address the interface holds or comes to
/// hold, and at `port` or at one of `random`'s candidates.
pub(crate) fn tcp_listen(&mut self, addr: Ipv4Addr, port: Option<Port>, random: impl FnMut() -> u16) -> Result<(ListenerId, Port), ListenError> {
- let id = self.shard.listen(addr, port, random)?;
+ let id = self.shard.listen(Ipv4Addr::UNSPECIFIED, port, random)?;
Ok((id, held(self.shard.listener_port(id))))
}
e29-a-listener-ended-in-a-pass-ends-the-pass.patchdiff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 327af671f..db8f8e793 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -200,7 +200,7 @@ impl Node {
pub(crate) fn wake_owners(&mut self, now: Instant) {
// A listener ended here gives its place back, which another's owner may be owed a wake
// for.
- while self.wake_each(now) {}
+ self.wake_each(now);
}
/// One round over the listeners, up to the first to end; `true` ended one.e30-a-listen-takes-an-address-nobody-holds.patchdiff --git a/toyos-net-shard/src/lib.rs b/toyos-net-shard/src/lib.rs
index ed06f07bc..809b4df82 100644
--- a/toyos-net-shard/src/lib.rs
+++ b/toyos-net-shard/src/lib.rs
@@ -485,9 +485,6 @@ impl Shard {
/// `addr` is the local address to listen on, one [`Self::listens_at`]; port 0 takes
/// `random`'s draws.
pub fn listen(&mut self, addr: Ipv4Addr, port: Option<Port>, random: impl FnMut() -> u16) -> Result<ListenerId, ListenError> {
- if !self.listens_at(addr) {
- return Err(ListenError::NotLocal);
- }
self.tcp.listen(addr, port, random).map_err(ListenError::Tcp)
}
e31-a-port-is-held-at-one-address-only.patchdiff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 327af671f..1e6e5a38c 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -123,9 +123,6 @@ impl Node {
return Err(ListenRefused::Full);
}
// A drawn port is one [tcp] finds no listener on at any address.
- if port.is_some_and(|port| self.listeners.live.values().any(|listener| listener.at.port == port)) {
- return Err(ListenRefused::InUse);
- }
let candidate = || {
let [low, high, ..] = draw().to_le_bytes();
u16::from_le_bytes([low, high])e32-a-listener-outlives-its-address.patchdiff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 327af671f..b3bcb9586 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -208,9 +208,6 @@ impl Node {
let room = self.room();
let stack = &mut self.stack;
let ended = self.listeners.live.iter_mut().find_map(|(id, listener)| {
- if !stack.shard().listens_at(listener.at.addr) {
- return Some((*id, ListenerEnd::Address));
- }
let owed = stack.tcp_ready(listener.bound).min(room);
while listener.unspent < owed {
if let Err(refusal) = listener.owner.wake() {e33-an-address-in-use-is-any-address.patchdiff --git a/toyos-net-shard/src/lib.rs b/toyos-net-shard/src/lib.rs
index ed06f07bc..514da5ebe 100644
--- a/toyos-net-shard/src/lib.rs
+++ b/toyos-net-shard/src/lib.rs
@@ -479,7 +479,7 @@ impl Shard {
/// Whether `addr` is one a listener may be at: UNSPECIFIED for any, and otherwise one [ip]
/// holds assigned or announcing, as a datagram socket's is.
pub fn listens_at(&self, addr: Ipv4Addr) -> bool {
- addr.is_unspecified() || self.ip.is_assigned(addr)
+ addr.is_unspecified() || self.ip.is_assigned(addr) || true
}
/// `addr` is the local address to listen on, one [`Self::listens_at`]; port 0 takesn2-deadline-without-the-name.patchdiff --git a/userland/netstack/node/src/lib.rs b/userland/netstack/node/src/lib.rs
index f842572af..40ec34d60 100644
--- a/userland/netstack/node/src/lib.rs
+++ b/userland/netstack/node/src/lib.rs
@@ -172,7 +172,7 @@ impl Node {
pub fn next_deadline(&self) -> Option<Instant> {
let name = self.name.as_ref().and_then(name::Name::next_deadline);
- self.stack.next_deadline().into_iter().chain(self.client.next_deadline()).chain(name).chain(self.resolver.next_deadline()).chain(self.streams.next_deadline()).min()
+ self.stack.next_deadline().into_iter().chain(self.client.next_deadline()).chain(self.resolver.next_deadline()).chain(self.streams.next_deadline()).min()
}
/// Every deadline at or before `now`; the frames they make due wait for [`Self::transmit`].o1-a-listener-is-one-connection.patchdiff --git a/toyos-net-shard/tcp/src/stack.rs b/toyos-net-shard/tcp/src/stack.rs
index b0c7a8391..702a7df89 100644
--- a/toyos-net-shard/tcp/src/stack.rs
+++ b/toyos-net-shard/tcp/src/stack.rs
@@ -749,7 +749,7 @@ impl Tcp {
return;
}
let Some(listener) = value(&mut self.listeners, index) else { return };
- if listener.pending.len() >= limits::LISTEN_PENDING || listener.ready.len() >= limits::LISTEN_READY {
+ if !listener.pending.is_empty() || !listener.ready.is_empty() {
self.log.count(Counter::ListenOverflow);
return;
}r16-deadline-without-the-lookups.patchdiff --git a/userland/netstack/node/src/lib.rs b/userland/netstack/node/src/lib.rs
index f842572af..64bcede45 100644
--- a/userland/netstack/node/src/lib.rs
+++ b/userland/netstack/node/src/lib.rs
@@ -172,7 +172,7 @@ impl Node {
pub fn next_deadline(&self) -> Option<Instant> {
let name = self.name.as_ref().and_then(name::Name::next_deadline);
- self.stack.next_deadline().into_iter().chain(self.client.next_deadline()).chain(name).chain(self.resolver.next_deadline()).chain(self.streams.next_deadline()).min()
+ self.stack.next_deadline().into_iter().chain(self.client.next_deadline()).chain(name).chain(self.streams.next_deadline()).min()
}
/// Every deadline at or before `now`; the frames they make due wait for [`Self::transmit`].t1-an-orphan-that-ends-stays-counted.patchdiff --git a/toyos-net-shard/tcp/src/stack.rs b/toyos-net-shard/tcp/src/stack.rs
index b0c7a8391..b173f3aeb 100644
--- a/toyos-net-shard/tcp/src/stack.rs
+++ b/toyos-net-shard/tcp/src/stack.rs
@@ -449,9 +449,6 @@ impl Tcp {
fn free(&mut self, index: u32) {
let Some(generation) = self.conns.get(usize::try_from(index).unwrap_or(usize::MAX)).map(|s| s.generation) else { return };
let Some(conn) = release(&mut self.conns, &mut self.free_conns, index) else { return };
- if conn.user == User::Orphan {
- self.orphans = self.orphans.saturating_sub(1);
- }
let remote = conn.tuple.remote.addr;
let parked = self.parked.get(&remote).is_some_and(|p| p.conns.contains(&index));
// Its index may name another connection next.t2-a-closed-connection-is-not-counted.patchdiff --git a/toyos-net-shard/tcp/src/stack.rs b/toyos-net-shard/tcp/src/stack.rs
index b0c7a8391..82552c793 100644
--- a/toyos-net-shard/tcp/src/stack.rs
+++ b/toyos-net-shard/tcp/src/stack.rs
@@ -1091,7 +1091,6 @@ impl Tcp {
sync.shutdown_write(now);
sync.orphan(now);
conn.user = User::Orphan;
- self.orphans = self.orphans.saturating_add(1);
self.settle(id.index, now);
}
}t3-a-close-in-syn-received-is-not-counted.patchdiff --git a/toyos-net-shard/tcp/src/stack.rs b/toyos-net-shard/tcp/src/stack.rs
index b0c7a8391..b2e18f953 100644
--- a/toyos-net-shard/tcp/src/stack.rs
+++ b/toyos-net-shard/tcp/src/stack.rs
@@ -1078,7 +1078,6 @@ impl Tcp {
Tcb::SynRcvd(rcvd) => {
rcvd.shutdown_write();
conn.user = User::Orphan;
- self.orphans = self.orphans.saturating_add(1);
self.settle(id.index, now);
}
Tcb::Sync(sync) if sync.rx.unread() > 0 => {t4-ready-counts-handshakes-in-progress.patchdiff --git a/toyos-net-shard/tcp/src/stack.rs b/toyos-net-shard/tcp/src/stack.rs
index b0c7a8391..823abd405 100644
--- a/toyos-net-shard/tcp/src/stack.rs
+++ b/toyos-net-shard/tcp/src/stack.rs
@@ -585,7 +585,7 @@ impl Tcp {
/// How many children completed their handshake and wait for [`Self::accept`].
pub fn ready(&mut self, id: ListenerId) -> Result<usize, Error> {
- slot(&mut self.listeners, id.index, id.generation).map(|l| l.ready.len()).ok_or(Error::NoSuchSocket)
+ slot(&mut self.listeners, id.index, id.generation).map(|l| l.pending.len()).ok_or(Error::NoSuchSocket)
}
/// The oldest child that completed its handshake.t5-options-are-the-defaults.patchdiff --git a/toyos-net-shard/tcp/src/stack.rs b/toyos-net-shard/tcp/src/stack.rs
index b0c7a8391..ec389d86e 100644
--- a/toyos-net-shard/tcp/src/stack.rs
+++ b/toyos-net-shard/tcp/src/stack.rs
@@ -977,7 +977,7 @@ impl Tcp {
/// The options the connection has: the ones its listener had when its SYN arrived, or the
/// defaults of an active open, until [`Self::set_options`] writes others.
pub fn options(&mut self, id: ConnId) -> Result<Options, Error> {
- Ok(self.conn(id)?.options)
+ self.conn(id).map(|_| Options::default())
}
pub fn set_options(&mut self, now: Instant, id: ConnId, options: Options) -> Result<(), Error> { |
|
Review of Net lines against Earlier BLOCKERs
Round 2's NOTEs: The merge
|
Round 2 ended a listener in the first pass after the machine lost the address its listen named, and nothing brought it back. What takes the address is the wire's doing and is not authenticated: a NAK, a server away for longer than the lease has left, two ARP frames inside the defend interval. The lease returns seconds later, as a rule at the same address, and no unchanged program listens again because its address was away. So a loss the wire could already cause for seconds became the loss of a service for good. Removed whole: the check in wake_each, ListenerEnd (the drain carries the refusal of the wake again), Shard::listens_at back into Shard::listen, and Listener::at down to the port the one-listener-a-port rule reads. Nothing replaces it: [ip] takes no segment for an address it does not hold, and [tcp] still has the listener when the address is back. A listener at a lost address stands as a datagram socket bound to it does. a_listener_stands_while_its_address_is_lost_and_answers_when_it_is_back, beside the lease's tests, over a NAK, expiry and a conflict, and through a renewal and a link that returns before them: both listeners stand with two places, nothing is drained and neither owner's end is dropped; a SYN to the lost address is answered by nothing and wakes nobody; a listen there on another port is NotLocal; and once the lease is held again a handshake completes and wakes the named listener's owner once. Red against the round 2 source. It replaces round 2's two tests. common::outside reads a TCP segment for it. tests/host.rs: an interrupted poll is one more turn of the loop. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
|
Mutation patches of round 6, all 39, each generated by d21-the-nodes-deadline-leaves-streams-out.patchdiff --git a/userland/netstack/node/src/lib.rs b/userland/netstack/node/src/lib.rs
index 60b4e21ce..c96f197dc 100644
--- a/userland/netstack/node/src/lib.rs
+++ b/userland/netstack/node/src/lib.rs
@@ -172,7 +172,7 @@ impl Node {
pub fn next_deadline(&self) -> Option<Instant> {
let name = self.name.as_ref().and_then(name::Name::next_deadline);
- self.stack.next_deadline().into_iter().chain(self.client.next_deadline()).chain(name).chain(self.resolver.next_deadline()).chain(self.streams.next_deadline()).min()
+ self.stack.next_deadline().into_iter().chain(self.client.next_deadline()).chain(name).chain(self.resolver.next_deadline()).min()
}
/// Every deadline at or before `now`; the frames they make due wait for [`Self::transmit`].e01-a-pass-wakes-nobody.patchdiff --git a/userland/netstack/node/src/streams.rs b/userland/netstack/node/src/streams.rs
index 41e86527a..c225ede9b 100644
--- a/userland/netstack/node/src/streams.rs
+++ b/userland/netstack/node/src/streams.rs
@@ -357,7 +357,6 @@ impl Node {
*peers = peers.saturating_add(1);
}
live.retain(|id, stream| (connects && !stream.connecting) || stream.pass(*id, now, stack, events, &mut extended));
- self.wake_owners(now);
}
/// The client lets go of the stream: nobody reads it, and what its pipe still holds is sente02-an-accept-ends-in-no-pass.patchdiff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 552c962a6..e61ad23ea 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -150,7 +150,6 @@ impl Node {
listener.unspent = listener.unspent.saturating_sub(1);
let bound = listener.bound;
let answer = self.take(bound, pipes);
- self.bridge(now);
answer
}
e03-an-owner-holds-one-wake-at-most.patchdiff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 552c962a6..1614970b5 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -195,7 +195,7 @@ impl Node {
let stack = &mut self.stack;
let ended = self.listeners.live.iter_mut().find_map(|(id, listener)| {
let owed = stack.tcp_ready(listener.bound).min(room);
- while listener.unspent < owed {
+ while listener.unspent < owed.min(1) {
if let Err(refusal) = listener.owner.wake() {
return Some((*id, refusal));
}e04-only-an-accept-that-takes-spends-a-wake.patchdiff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 552c962a6..6b83d9bf2 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -147,9 +147,11 @@ impl Node {
/// and what it already received moves at once.
pub fn accept(&mut self, now: Instant, id: ListenerId, pipes: Option<Pipes>) -> Result<Accepted, AcceptRefused> {
let Some(listener) = self.listeners.live.get_mut(&id) else { return Err(AcceptRefused::NoListener) };
- listener.unspent = listener.unspent.saturating_sub(1);
let bound = listener.bound;
let answer = self.take(bound, pipes);
+ if let (Ok(_), Some(listener)) = (&answer, self.listeners.live.get_mut(&id)) {
+ listener.unspent = listener.unspent.saturating_sub(1);
+ }
self.bridge(now);
answer
}e05-a-wake-ignores-places.patchdiff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 552c962a6..9b44fabc3 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -194,7 +194,7 @@ impl Node {
let room = self.room();
let stack = &mut self.stack;
let ended = self.listeners.live.iter_mut().find_map(|(id, listener)| {
- let owed = stack.tcp_ready(listener.bound).min(room);
+ let owed = stack.tcp_ready(listener.bound);
while listener.unspent < owed {
if let Err(refusal) = listener.owner.wake() {
return Some((*id, refusal));e06-an-accept-ignores-places.patchdiff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 552c962a6..c9860dfb3 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -156,9 +156,6 @@ impl Node {
fn take(&mut self, bound: toyos_net_tcp::ListenerId, pipes: Option<Pipes>) -> Result<Accepted, AcceptRefused> {
let Some(pipes) = pipes else { return Err(AcceptRefused::NoPipes) };
- if self.room() == 0 {
- return Err(AcceptRefused::Full);
- }
let Some((conn, tuple, options)) = self.stack.tcp_accept(bound) else { return Err(AcceptRefused::Nothing) };
// The peer's address is what `streams` counts a stream its client can see no more by.
let id = self.streams.accepted(conn, tuple.remote.addr, options, pipes);e07-a-finishing-connection-holds-no-place.patchdiff --git a/userland/netstack/node/src/places.rs b/userland/netstack/node/src/places.rs
index ce45b8c18..b49e100c5 100644
--- a/userland/netstack/node/src/places.rs
+++ b/userland/netstack/node/src/places.rs
@@ -45,7 +45,7 @@ impl Node {
/// The places taken: streams, listeners, datagram sockets, and connections [tcp] is
/// finishing alone.
pub fn held(&self) -> usize {
- self.streams().saturating_add(self.listeners()).saturating_add(self.sockets).saturating_add(self.stack.tcp_orphans())
+ self.streams().saturating_add(self.listeners()).saturating_add(self.sockets)
}
pub(crate) fn room(&self) -> usize {e09-a-closed-connect-wakes-nobody.patchdiff --git a/userland/netstack/node/src/streams.rs b/userland/netstack/node/src/streams.rs
index 41e86527a..883e9f346 100644
--- a/userland/netstack/node/src/streams.rs
+++ b/userland/netstack/node/src/streams.rs
@@ -368,7 +368,6 @@ impl Node {
self.stack.tcp_abort(now, stream.conn);
self.streams.live.remove(&id);
self.streams.events.push_back(StreamEvent::Closed { id });
- self.wake_owners(now);
return;
}
stream.to_client = None;e10-a-stream-reset-for-its-pipe-wakes-nobody.patchdiff --git a/userland/netstack/node/src/streams.rs b/userland/netstack/node/src/streams.rs
index 41e86527a..f5931d971 100644
--- a/userland/netstack/node/src/streams.rs
+++ b/userland/netstack/node/src/streams.rs
@@ -419,7 +419,6 @@ impl Node {
if held {
self.stack.tcp_abort(now, stream.conn);
self.streams.live.remove(&id);
- self.wake_owners(now);
}
}
e11-a-closed-listener-wakes-nobody.patchdiff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 552c962a6..95f854377 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -169,9 +169,6 @@ impl Node {
/// says nobody holds the other end of the wake pipe. `false` is an id that names no listener.
pub fn close_listener(&mut self, now: Instant, id: ListenerId) -> bool {
let closed = self.end_listener(now, id);
- if closed {
- self.wake_owners(now);
- }
closed
}
e12-more-places-wake-nobody.patchdiff --git a/userland/netstack/node/src/places.rs b/userland/netstack/node/src/places.rs
index ce45b8c18..3087ec3d2 100644
--- a/userland/netstack/node/src/places.rs
+++ b/userland/netstack/node/src/places.rs
@@ -39,7 +39,6 @@ impl Node {
/// How many places the node has from here on. Nothing held is let go for a smaller number.
pub fn set_places(&mut self, now: Instant, places: usize) {
self.places = places;
- self.wake_owners(now);
}
/// The places taken: streams, listeners, datagram sockets, and connections [tcp] ise13-a-connect-ignores-places.patchdiff --git a/userland/netstack/node/src/streams.rs b/userland/netstack/node/src/streams.rs
index 41e86527a..6cb7ee30b 100644
--- a/userland/netstack/node/src/streams.rs
+++ b/userland/netstack/node/src/streams.rs
@@ -333,9 +333,6 @@ impl Node {
/// An active open to `remote`, answered by a [`StreamEvent`] once the handshake ends or
/// `timeout` passes. Refused, nothing was sent and the pipe ends are dropped.
pub fn connect(&mut self, now: Instant, remote: Endpoint, timeout: Option<Duration>, pipes: Pipes) -> Result<StreamId, ConnectRefused> {
- if self.room() == 0 {
- return Err(ConnectRefused::Full);
- }
let conn = self.stack.tcp_connect(now, remote).map_err(ConnectRefused::Stack)?;
let deadline = timeout.map(|within| now.after(within));
// An active open has [tcp]'s defaults until `set_nodelay`.e14-a-listen-ignores-places.patchdiff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 552c962a6..ec95e7606 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -106,9 +106,6 @@ impl Node {
/// hold, on `port` or on a port chosen from `draw`'s candidates. Answers the listener and
/// its port. Refused, nothing was made and `owner` is dropped.
pub fn listen(&mut self, addr: Ipv4Addr, port: Option<Port>, owner: Box<dyn Wake>, mut draw: impl FnMut() -> u32) -> Result<(ListenerId, Port), ListenRefused> {
- if self.room() == 0 {
- return Err(ListenRefused::Full);
- }
// A drawn port is one [tcp] finds no listener on at any address.
if port.is_some_and(|port| self.listeners.live.values().any(|listener| listener.port == port)) {
return Err(ListenRefused::InUse);e15-a-listener-holds-no-place.patchdiff --git a/userland/netstack/node/src/places.rs b/userland/netstack/node/src/places.rs
index ce45b8c18..afdf25522 100644
--- a/userland/netstack/node/src/places.rs
+++ b/userland/netstack/node/src/places.rs
@@ -45,7 +45,7 @@ impl Node {
/// The places taken: streams, listeners, datagram sockets, and connections [tcp] is
/// finishing alone.
pub fn held(&self) -> usize {
- self.streams().saturating_add(self.listeners()).saturating_add(self.sockets).saturating_add(self.stack.tcp_orphans())
+ self.streams().saturating_add(self.sockets).saturating_add(self.stack.tcp_orphans())
}
pub(crate) fn room(&self) -> usize {e16-a-closed-listener-stays-in-the-stack.patchdiff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 552c962a6..b383536f4 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -177,7 +177,6 @@ impl Node {
fn end_listener(&mut self, now: Instant, id: ListenerId) -> bool {
let Some(listener) = self.listeners.live.remove(&id) else { return false };
- self.stack.tcp_close_listener(now, listener.bound);
true
}
e17-a-refused-wake-is-ignored.patchdiff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 552c962a6..2dcfb4372 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -196,8 +196,8 @@ impl Node {
let ended = self.listeners.live.iter_mut().find_map(|(id, listener)| {
let owed = stack.tcp_ready(listener.bound).min(room);
while listener.unspent < owed {
- if let Err(refusal) = listener.owner.wake() {
- return Some((*id, refusal));
+ if listener.owner.wake().is_err() {
+ break;
}
listener.unspent = listener.unspent.saturating_add(1);
}e18-a-stream-holds-no-place.patchdiff --git a/userland/netstack/node/src/places.rs b/userland/netstack/node/src/places.rs
index ce45b8c18..ff4b7856d 100644
--- a/userland/netstack/node/src/places.rs
+++ b/userland/netstack/node/src/places.rs
@@ -45,7 +45,7 @@ impl Node {
/// The places taken: streams, listeners, datagram sockets, and connections [tcp] is
/// finishing alone.
pub fn held(&self) -> usize {
- self.streams().saturating_add(self.listeners()).saturating_add(self.sockets).saturating_add(self.stack.tcp_orphans())
+ self.listeners().saturating_add(self.sockets).saturating_add(self.stack.tcp_orphans())
}
pub(crate) fn room(&self) -> usize {e19-a-drawn-port-reads-the-draws-high-half.patchdiff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 552c962a6..4410eb12a 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -114,7 +114,7 @@ impl Node {
return Err(ListenRefused::InUse);
}
let candidate = || {
- let [low, high, ..] = draw().to_le_bytes();
+ let [.., low, high] = draw().to_le_bytes();
u16::from_le_bytes([low, high])
};
let (bound, port) = match self.stack.tcp_listen(addr, port, candidate) {e20-a-listeners-option-does-not-reach-tcp.patchdiff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 552c962a6..a1dc0a2f8 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -135,7 +135,6 @@ impl Node {
pub fn set_listener_nodelay(&mut self, id: ListenerId, nodelay: bool) -> bool {
let Some(listener) = self.listeners.live.get_mut(&id) else { return false };
listener.options.nodelay = nodelay;
- self.stack.tcp_set_listener_options(listener.bound, listener.options);
true
}
e21-an-accepted-stream-holds-no-option.patchdiff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 552c962a6..ae6f41f5b 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -161,7 +161,7 @@ impl Node {
}
let Some((conn, tuple, options)) = self.stack.tcp_accept(bound) else { return Err(AcceptRefused::Nothing) };
// The peer's address is what `streams` counts a stream its client can see no more by.
- let id = self.streams.accepted(conn, tuple.remote.addr, options, pipes);
+ let id = self.streams.accepted(conn, tuple.remote.addr, Options::default(), pipes);
Ok(Accepted { id, remote: tuple.remote, local: tuple.local.port })
}
e22-an-accepted-stream-is-counted-by-the-nodes-address.patchdiff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 552c962a6..587b48a64 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -161,7 +161,7 @@ impl Node {
}
let Some((conn, tuple, options)) = self.stack.tcp_accept(bound) else { return Err(AcceptRefused::Nothing) };
// The peer's address is what `streams` counts a stream its client can see no more by.
- let id = self.streams.accepted(conn, tuple.remote.addr, options, pipes);
+ let id = self.streams.accepted(conn, tuple.local.addr, options, pipes);
Ok(Accepted { id, remote: tuple.remote, local: tuple.local.port })
}
e23-an-id-is-used-twice.patchdiff --git a/userland/netstack/node/src/streams.rs b/userland/netstack/node/src/streams.rs
index 41e86527a..958bf203d 100644
--- a/userland/netstack/node/src/streams.rs
+++ b/userland/netstack/node/src/streams.rs
@@ -313,7 +313,6 @@ impl Streams {
/// Holds `stream` under an id of its own.
fn hold(&mut self, stream: Stream) -> StreamId {
let id = StreamId(self.next);
- self.next = self.next.saturating_add(1);
self.live.insert(id, stream);
id
}e24-a-bind-ignores-places.patchdiff --git a/userland/netstack/node/src/datagram.rs b/userland/netstack/node/src/datagram.rs
index b24dead14..2b19d06a1 100644
--- a/userland/netstack/node/src/datagram.rs
+++ b/userland/netstack/node/src/datagram.rs
@@ -103,9 +103,6 @@ impl Node {
/// or, with none named, an ephemeral one, which spends the one draw. Returns the socket and
/// the port it holds.
pub fn udp_bind(&mut self, addr: Ipv4Addr, port: Option<Port>, draw: impl FnOnce() -> u32) -> Result<(DatagramId, Port), Refused> {
- if self.room() == 0 {
- return Err(Refused::ResourceExhausted);
- }
let (id, port) = self.stack.bind(addr, port, draw).map_err(refused)?;
self.sockets = self.sockets.saturating_add(1);
Ok((DatagramId(id), port))e25-a-datagram-socket-holds-no-place.patchdiff --git a/userland/netstack/node/src/places.rs b/userland/netstack/node/src/places.rs
index ce45b8c18..021315755 100644
--- a/userland/netstack/node/src/places.rs
+++ b/userland/netstack/node/src/places.rs
@@ -45,7 +45,7 @@ impl Node {
/// The places taken: streams, listeners, datagram sockets, and connections [tcp] is
/// finishing alone.
pub fn held(&self) -> usize {
- self.streams().saturating_add(self.listeners()).saturating_add(self.sockets).saturating_add(self.stack.tcp_orphans())
+ self.streams().saturating_add(self.listeners()).saturating_add(self.stack.tcp_orphans())
}
pub(crate) fn room(&self) -> usize {e26-a-closed-socket-wakes-nobody.patchdiff --git a/userland/netstack/node/src/datagram.rs b/userland/netstack/node/src/datagram.rs
index b24dead14..bb81727af 100644
--- a/userland/netstack/node/src/datagram.rs
+++ b/userland/netstack/node/src/datagram.rs
@@ -128,7 +128,6 @@ impl Node {
pub fn udp_close(&mut self, now: Instant, id: DatagramId) -> Result<(), Refused> {
self.stack.close(now, id.0).map_err(refused)?;
self.sockets = self.sockets.saturating_sub(1);
- self.wake_owners(now);
Ok(())
}
}e27-a-closed-socket-keeps-its-place.patchdiff --git a/userland/netstack/node/src/datagram.rs b/userland/netstack/node/src/datagram.rs
index b24dead14..4606c036b 100644
--- a/userland/netstack/node/src/datagram.rs
+++ b/userland/netstack/node/src/datagram.rs
@@ -127,7 +127,6 @@ impl Node {
/// accepted still leaves, to [udp]'s bound. Its place is back.
pub fn udp_close(&mut self, now: Instant, id: DatagramId) -> Result<(), Refused> {
self.stack.close(now, id.0).map_err(refused)?;
- self.sockets = self.sockets.saturating_sub(1);
self.wake_owners(now);
Ok(())
}e28-a-listen-is-at-every-address.patchdiff --git a/userland/netstack/node/src/lease/tcp.rs b/userland/netstack/node/src/lease/tcp.rs
index bd9bcbd73..d6e04ad2b 100644
--- a/userland/netstack/node/src/lease/tcp.rs
+++ b/userland/netstack/node/src/lease/tcp.rs
@@ -72,7 +72,7 @@ impl Stack {
/// A passive open at `addr`, 0.0.0.0 meaning every address the interface holds or comes to
/// hold, and at `port` or at one of `random`'s candidates.
pub(crate) fn tcp_listen(&mut self, addr: Ipv4Addr, port: Option<Port>, random: impl FnMut() -> u16) -> Result<(ListenerId, Port), ListenError> {
- let id = self.shard.listen(addr, port, random)?;
+ let id = self.shard.listen(Ipv4Addr::UNSPECIFIED, port, random)?;
Ok((id, held(self.shard.listener_port(id))))
}
e29-a-listener-ended-in-a-pass-ends-the-pass.patchdiff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 552c962a6..d9462f536 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -186,7 +186,7 @@ impl Node {
pub(crate) fn wake_owners(&mut self, now: Instant) {
// A listener ended here gives its place back, which another's owner may be owed a wake
// for.
- while self.wake_each(now) {}
+ self.wake_each(now);
}
/// One round over the listeners, up to the first whose pipe refuses a wake; `true` ended it.e30-a-listen-takes-an-address-nobody-holds.patchdiff --git a/toyos-net-shard/src/lib.rs b/toyos-net-shard/src/lib.rs
index d7ee5fb50..fd8e2d469 100644
--- a/toyos-net-shard/src/lib.rs
+++ b/toyos-net-shard/src/lib.rs
@@ -479,9 +479,6 @@ impl Shard {
/// `addr` is the local address to listen on, UNSPECIFIED for any, and otherwise one [ip]
/// holds assigned or announcing, as a datagram socket's is; port 0 takes `random`'s draws.
pub fn listen(&mut self, addr: Ipv4Addr, port: Option<Port>, random: impl FnMut() -> u16) -> Result<ListenerId, ListenError> {
- if !addr.is_unspecified() && !self.ip.is_assigned(addr) {
- return Err(ListenError::NotLocal);
- }
self.tcp.listen(addr, port, random).map_err(ListenError::Tcp)
}
e31-a-port-is-held-at-one-address-only.patchdiff --git a/userland/netstack/node/src/listeners.rs b/userland/netstack/node/src/listeners.rs
index 552c962a6..e2373720f 100644
--- a/userland/netstack/node/src/listeners.rs
+++ b/userland/netstack/node/src/listeners.rs
@@ -110,9 +110,6 @@ impl Node {
return Err(ListenRefused::Full);
}
// A drawn port is one [tcp] finds no listener on at any address.
- if port.is_some_and(|port| self.listeners.live.values().any(|listener| listener.port == port)) {
- return Err(ListenRefused::InUse);
- }
let candidate = || {
let [low, high, ..] = draw().to_le_bytes();
u16::from_le_bytes([low, high])n2-deadline-without-the-name.patchdiff --git a/userland/netstack/node/src/lib.rs b/userland/netstack/node/src/lib.rs
index 60b4e21ce..75820dbbd 100644
--- a/userland/netstack/node/src/lib.rs
+++ b/userland/netstack/node/src/lib.rs
@@ -172,7 +172,7 @@ impl Node {
pub fn next_deadline(&self) -> Option<Instant> {
let name = self.name.as_ref().and_then(name::Name::next_deadline);
- self.stack.next_deadline().into_iter().chain(self.client.next_deadline()).chain(name).chain(self.resolver.next_deadline()).chain(self.streams.next_deadline()).min()
+ self.stack.next_deadline().into_iter().chain(self.client.next_deadline()).chain(self.resolver.next_deadline()).chain(self.streams.next_deadline()).min()
}
/// Every deadline at or before `now`; the frames they make due wait for [`Self::transmit`].o1-a-listener-is-one-connection.patchdiff --git a/toyos-net-shard/tcp/src/stack.rs b/toyos-net-shard/tcp/src/stack.rs
index b0c7a8391..702a7df89 100644
--- a/toyos-net-shard/tcp/src/stack.rs
+++ b/toyos-net-shard/tcp/src/stack.rs
@@ -749,7 +749,7 @@ impl Tcp {
return;
}
let Some(listener) = value(&mut self.listeners, index) else { return };
- if listener.pending.len() >= limits::LISTEN_PENDING || listener.ready.len() >= limits::LISTEN_READY {
+ if !listener.pending.is_empty() || !listener.ready.is_empty() {
self.log.count(Counter::ListenOverflow);
return;
}r16-deadline-without-the-lookups.patchdiff --git a/userland/netstack/node/src/lib.rs b/userland/netstack/node/src/lib.rs
index 60b4e21ce..ff58dcf6b 100644
--- a/userland/netstack/node/src/lib.rs
+++ b/userland/netstack/node/src/lib.rs
@@ -172,7 +172,7 @@ impl Node {
pub fn next_deadline(&self) -> Option<Instant> {
let name = self.name.as_ref().and_then(name::Name::next_deadline);
- self.stack.next_deadline().into_iter().chain(self.client.next_deadline()).chain(name).chain(self.resolver.next_deadline()).chain(self.streams.next_deadline()).min()
+ self.stack.next_deadline().into_iter().chain(self.client.next_deadline()).chain(name).chain(self.streams.next_deadline()).min()
}
/// Every deadline at or before `now`; the frames they make due wait for [`Self::transmit`].t1-an-orphan-that-ends-stays-counted.patchdiff --git a/toyos-net-shard/tcp/src/stack.rs b/toyos-net-shard/tcp/src/stack.rs
index b0c7a8391..b173f3aeb 100644
--- a/toyos-net-shard/tcp/src/stack.rs
+++ b/toyos-net-shard/tcp/src/stack.rs
@@ -449,9 +449,6 @@ impl Tcp {
fn free(&mut self, index: u32) {
let Some(generation) = self.conns.get(usize::try_from(index).unwrap_or(usize::MAX)).map(|s| s.generation) else { return };
let Some(conn) = release(&mut self.conns, &mut self.free_conns, index) else { return };
- if conn.user == User::Orphan {
- self.orphans = self.orphans.saturating_sub(1);
- }
let remote = conn.tuple.remote.addr;
let parked = self.parked.get(&remote).is_some_and(|p| p.conns.contains(&index));
// Its index may name another connection next.t2-a-closed-connection-is-not-counted.patchdiff --git a/toyos-net-shard/tcp/src/stack.rs b/toyos-net-shard/tcp/src/stack.rs
index b0c7a8391..82552c793 100644
--- a/toyos-net-shard/tcp/src/stack.rs
+++ b/toyos-net-shard/tcp/src/stack.rs
@@ -1091,7 +1091,6 @@ impl Tcp {
sync.shutdown_write(now);
sync.orphan(now);
conn.user = User::Orphan;
- self.orphans = self.orphans.saturating_add(1);
self.settle(id.index, now);
}
}t3-a-close-in-syn-received-is-not-counted.patchdiff --git a/toyos-net-shard/tcp/src/stack.rs b/toyos-net-shard/tcp/src/stack.rs
index b0c7a8391..b2e18f953 100644
--- a/toyos-net-shard/tcp/src/stack.rs
+++ b/toyos-net-shard/tcp/src/stack.rs
@@ -1078,7 +1078,6 @@ impl Tcp {
Tcb::SynRcvd(rcvd) => {
rcvd.shutdown_write();
conn.user = User::Orphan;
- self.orphans = self.orphans.saturating_add(1);
self.settle(id.index, now);
}
Tcb::Sync(sync) if sync.rx.unread() > 0 => {t4-ready-counts-handshakes-in-progress.patchdiff --git a/toyos-net-shard/tcp/src/stack.rs b/toyos-net-shard/tcp/src/stack.rs
index b0c7a8391..823abd405 100644
--- a/toyos-net-shard/tcp/src/stack.rs
+++ b/toyos-net-shard/tcp/src/stack.rs
@@ -585,7 +585,7 @@ impl Tcp {
/// How many children completed their handshake and wait for [`Self::accept`].
pub fn ready(&mut self, id: ListenerId) -> Result<usize, Error> {
- slot(&mut self.listeners, id.index, id.generation).map(|l| l.ready.len()).ok_or(Error::NoSuchSocket)
+ slot(&mut self.listeners, id.index, id.generation).map(|l| l.pending.len()).ok_or(Error::NoSuchSocket)
}
/// The oldest child that completed its handshake.t5-options-are-the-defaults.patchdiff --git a/toyos-net-shard/tcp/src/stack.rs b/toyos-net-shard/tcp/src/stack.rs
index b0c7a8391..ec389d86e 100644
--- a/toyos-net-shard/tcp/src/stack.rs
+++ b/toyos-net-shard/tcp/src/stack.rs
@@ -977,7 +977,7 @@ impl Tcp {
/// The options the connection has: the ones its listener had when its SYN arrived, or the
/// defaults of an active open, until [`Self::set_options`] writes others.
pub fn options(&mut self, id: ConnId) -> Result<Options, Error> {
- Ok(self.conn(id)?.options)
+ self.conn(id).map(|_| Options::default())
}
pub fn set_options(&mut self, now: Instant, id: ConnId, options: Options) -> Result<(), Error> { |
|
Review of Net lines against Earlier BLOCKERs
The test, against what round 3 namedEvery assertion is there, and inside the loop over the three losses, so each loss gets all of them:
BLOCKER
NOTENone. Asked in the brief"Has no mutation at this head." Right. The test guards an absence, and its control is the removed rule put back whole: the same test text against The name
The body. "It is the brief's ruling" is gone; difference 9 ends "stands, and answers again when the address is back". Its numbers for this head (20 files, +1,943, -102; +476, -39; +1,385, -59; +82, -4) are Anything dead or new. Nothing dead: no caller of a removed name is left, and What closes BLOCKER 1, and who may landUnchanged from round 3 but for the head and the counts. After #775 is on
SEND BACK |
…d only when no query is still read Round 1 of the review of #781. A report from the wire is anyone's word: an off-path sender who guesses a query's source port can quote its addresses and ports, and needs no id. toyos-net-udp's SocketError::Unreachable was both an ICMP prohibition and [ip]'s own report that a next hop failed; it is two words now, Prohibited and NextHopFailed. The node's resolver lets a query go only on what it knows: [udp]'s refusal in the call, and NextHopFailed. An ICMP error (Refused, Prohibited) goes to the new toyos_dns::Lookup::on_report, which asks the next server at once where one is left to ask, lets no query go and ends nothing: the query's socket stays open and its answer is read. Lookup counts the queries sent in a row with no wait between them, and sends at once only while that run is shorter than the servers, so no server is asked twice inside WAIT_MS whoever reports what. When nobody is left to ask at once it ends Unreachable only if no query is still read, and otherwise waits the newest query's wait out: before, an unreached newest query ended the lookup under older queries whose answers were a millisecond away, and an unreached last query ended it TimedOut early. an_alias_answered_late_restarts_the_lookup_and_lets_the_old_names_queries_go is its base's again, two resolvers and 10.5 s: on 20b2143 that body was red (TimedOut at the silent resolver's last query, reported unreached while three queries to the answering resolver were read), which was the defect above and not a property of the test. The track's two residuals name their owner, the move's node stage after #777, the design it takes for [ip]'s queue, and what it deletes with it; the departure line names NUD-04's limit and why NUD-05 still passes. Readers of the old word: toyos-net-udp's US-049 and US-027, the shard's ICD-012 test, and the node's resolver. The node's datagram.rs matches Error::Failed(_) and is untouched. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
…, the drivers' room and wake (#782) and the SMI_CMD call (#780), into the listeners stage No file stopped the merge. One file is both sides': the track, where main's two removed bullets and two added ones (#779) sit in the node's and stage 3's lists and this stage's lines in stage 5's, merged by git and read against both parents. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
|
Merge round: The diff proof. Not empty, and both differences are
The node's #779 and #782 under this stage. Nothing it rests on moved: #779's rules read 169.254/16 alone and no source or test of the stage names that prefix; its silent-next-hop rule is a test of [ip]'s with no change of [ip]'s source; #782 is the shipped netstack's drivers, which nothing here depends on. Exits at
Mutations not run again: the merge touches none of the eight files the 39 patches name, and all 39 pass The title still says "stacks on #775"; the brief fenced this round to the body. |
|
The orchestrator's call on the merge at |
|
CI at |
… pipe ABI's broadcast permission One conflict, in issues/toyos-has-its-own-network-stack.md. The stage line takes main's list of what is in the tree, listeners and the places, and keeps this branch's broadcast permission as what is still to build before the move. Among the node's open lines main's five new ones and its rewritten 100 s line stand, and the line on a reset stream's options keeps this branch's wording, since libc's getsockopt of TCP_NODELAY answers from its socket's entry here. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
…ers (#777), the drivers' room and wake (#782), the SMI_CMD call (#780) and the guest waits (#786), into the resolver rules One conflict, in issues/toyos-has-its-own-network-stack.md, resolved by hand with every line of both sides accounted for: - The stage 5 paragraph: main's "In the tree", which now names streams, listeners and the one bound, and this branch's "Still to build on it", less the streams and listeners that landed: datagram senders that wait on the hop, then the move. - "What the node does not yet meet": this branch's two lines stand in place of the two lines they rewrote, which main had left as they were; the line on an answer to a 169.254/16 asker, which #779 deleted with the defect, is gone, as is the line on a silent next hop, which #779 deleted with its test (no conflict). - "What stage 3 departs from its specifications": both sides added a line at the list's end; both stand, this branch's on NUD-04 and #779's on the scenarios the readers' specification lacks. Every other file merged without a conflict. toyos-dns/src/lib.rs and userland/netstack/node/src/resolve.rs are byte-identical to b2d9037. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
One conflict, the track's stage-5 paragraph: this branch named datagram senders that wait on the hop as still to build, #783 named the datagram sockets' broadcast permission. Both stand, in that order, before the move. The clause that ordered the first after #777 is met and is restated as where the work lies. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
…kes them and its accept answers its stream's The round-1 review of the listener's-option stage found three defects and this answers them. A bind's request ends in the TcpOptions an accept's answer ends in, the shell's Listening is made with them and opens its first socket with them before the socket is in the set, and libc's bind names its socket's TCP_NODELAY there: the second request libc sent after the bind's answer, and its close-and-fail unwind, are gone, and with them the frame in which a SYN could begin a connection without an option set before bind. toyos::net::tcp_bind keeps its signature, which the socket2 and mio forks call, and stands on tcp_bind_with; the renamed issue's exit asks for one call once the forks pass their own. The node's listen takes the listener's nodelay, written to [tcp] in the call that made the passive open, and Accepted carries the option its stream was handed over with, which the node had in hand in take and dropped. Three tests of the node: a listener holds what its listen named from its first connection; a handshake reset before it ends leaves the next connection the option its listener holds when its own SYN arrives, both ways round; and an accept whose own pass lets its stream go still answers the stream's option, where Node::nodelay of its id answers None. The track's node line is closed by them. The three tests added on a smoltcp socket are deleted: no new test is built on the stack being replaced. The shell's rule differs from the host's for the connection after a handshake its peer reset, since smoltcp puts a socket reset in SynReceived straight back to Listen with the Nagle switch it had; that is filed, read from the two sources and not run, with the move as its exit, and the shell's module header says it. run_test_hooked lost its last caller with a hook and is deleted; nodelay_accepted's arm for other hosts, which nothing in the tree built, is deleted; libc's accept writing its caller's address before alloc_socket can fail is filed; the renamed issue's Linux paragraph is struck, the host check's log of #777 on ubuntu-24.04 being that reading. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
Stage E of the cut of
issues/toyos-has-its-own-network-stack.md's stage 5: netstack's listeners on ToyOS's own TCP, and the one bound on the streams, listeners and datagram sockets clients make the node hold, intoyos-net-node, host-tested and shipped in nothing. Base:mainat5f2657703, which carries stage D (#775, landed asd5de6fabe), #779, #782 and #780, merged here atf0c483ea2. Head:f0c483ea2; the reviewed head ise737052a5. Stage E isgit diff --shortstat origin/main...f0c483ea2: 20 files changed, 1943 insertions(+), 102 deletions(-). Production +476, -39 (listeners.rs222,places.rs54,streams.rs+56 -24,lease/tcp.rs+40 -4,lib.rs+19 -4,datagram.rs+15 -3, the shard +46 -4, [tcp] 24); tests +1,385, -59 (tests/listeners.rs1,031,tests/lease.rs+133 -2,tcp/tests/held.rs83,tests/common/mod.rs+58 -5,tests/host.rs54,tests/streams.rs+22 -51,tests/resolve.rs+4 -1); the track, two issues, the manifest and two lockfile lines +82, -4. Againstmainthe diff is those 20 files and no others. No image, no shipped module and no guest test changes.The merge of
mainat5f2657703(f0c483ea2)No file stopped the merge and none was resolved by hand. One file is both sides', the track, merged by git and read against both parents: #779 removes two bullets from the node's list and adds two to stage 3's lists, and every line of this stage stands.
git diff e737052a5 f0c483ea2 --statoveruserland/netstack/node,toyos-net-shard,toyos-dns,userland/netstack/src/resolve.rs,Cargo.lockand the track is two files, bothmain's and both #779's (47d8a6baf):issues/toyos-has-its-own-network-stack.md, +2 -2: the four bullets above.userland/netstack/node/tests/name.rs, +17 -10: toyos-net-ip: 169.254/16 is on the link whatever address is held, and a silent next hop holds up no other #779 turnsan_answer_to_a_link_local_asker_leaves_by_the_routerintoan_answer_to_a_link_local_asker_goes_to_its_own_link_address. It is not one of this stage's 20 files, and it ismain's bytes:git diff origin/main f0c483ea2of it is empty. It importsarpandBROADCASTfromtests/common/mod.rs, which this stage edits and toyos-net-ip: 169.254/16 is on the link whatever address is held, and a silent next hop holds up no other #779 does not;nameis 13 tests and green.Everything else there is
e737052a5's byte for byte: the node'ssrc, its other tests, the shard, [tcp],toyos-dns,resolve.rsand both lockfile lines.What #779 and #782 change under this stage: nothing it rests on. #779's rules read 169.254/16 alone (a route to it on the link, its two edges no source, an ARP sender in it a neighbour); no source or test of this stage names that prefix, and its peers are 192.0.2.0/24's. The silent-next-hop rule is a test of [ip]'s that #779 adds, with no change of [ip]'s behaviour. #782 is
userland/netstack/srcandtoyos-i219, the shipped netstack and its drivers, which nothing here depends on.Mutations not run again: the merge touches none of the eight files the 39 patches name, and each patch still applies to
f0c483ea2(git apply --check, step40-mutations-apply-check, 39 of 39 exit 0).Round 3 of the review, finding by finding (
e737052a5)BLOCKER 1, evidence. Still open and not mine to close: a draft, and no
hostjob has run. What I ran is in "Gates": round 6, one log a step, in the orchestrator's scratchpad.BLOCKER 6, the wire ended a program's listener for good. Removed whole, and nothing replaces it in the node. Gone: the check in
wake_each;ListenerEnd(the drain yields(ListenerId, WriteRefusal)again; it keeps the namedrain_ended_listeners, so the test below is the same text against both sources);Shard::listens_at, back insideShard::listen, its one caller;Listener::at, nowport, which the one-listener-a-port rule reads; the header's paragraph; the track's sentence.listeners.rsagainst643584d4bdiffers by the port rule, its field, its header paragraph, one sentence on the lost address and the drain's name, and by nothing else; the shard'slib.rsis643584d4b's byte for byte. A listener whose address the machine loses stands as a datagram socket bound to it does: [ip] takes no segment for an address it does not hold, and [tcp] still has the listener when the address is back.a_listener_stands_while_its_address_is_lost_and_answers_when_it_is_back, over a NAK, expiry and a second conflict. Before the loss, a renewal and a link down and up (round 2's second test, folded in): both listeners stand. After it: the named listener and the one at every address stand, two listeners and two places, nothing drained, neither owner's end dropped, no wake; a SYN to the lost address makes the node send nothing and wakes nobody; a listen there on another port isNotLocal. Then the server offers and acknowledges the same address, conflict detection ends, and a peer's handshake at port 22 completes: the SYN-ACK is read byetherparse(<SEQ=ISS><ACK=5001><CTL=SYN,ACK>from port 22) and the named owner holds one wake, the other none.c27cc36aa(round 6, stepsred-first-build, exit 0, andred-first--a_listener_stands_while_its_address_is_lost_and_answers_when_it_is_back, exit 101):userland/netstack/node/tests/lease.rs:282:13: assertion left == right failed: a NAK, the address lost; left: (1, 1, 1), right: (2, 2, 0). It has no mutation at this head: the rule it guards against is the code that was removed.leaseis 19 tests.common::outsidereads a TCP segment (Seen::Tcp), so the lease's harness can carry the handshake; no other harness changed.NOTE,
tests/host.rs. An interruptedpollis one more turn of the loop; any other error still panics, and so does the ceiling.NOTE, the body. "It is the brief's ruling" is gone with the code, and difference 9 loses its last clause.
The patches. e32 and e33 are gone with what they mutated. The other 39 are regenerated against
e737052a5's blobs, each applying withgit apply --checkexactly, and posted once in the newest patch comment.Round 2 of the review, finding by finding (
c27cc36aa)BLOCKER 1, evidence. Open then as now; that round's steps are round 5's logs.
BLOCKER 5, a port is one listener's.
Node::listenrefuses a named port any listener of the node holds, whichever address either named,ListenRefused::InUse, before [tcp] is asked: the rule [udp] has for its ports. [tcp]'s preference for the listener that named a SYN's address (LS-09) stays [tcp]'s and is not reachable from the node, since every listener [tcp] holds is one the node made. A drawn port needs no check: [tcp] draws only a port no listener holds at any address (port_listened).a_listen_at_the_machines_address_takes_no_port_held_at_every_address(0.0.0.0:22 then 192.0.2.1:22),a_listen_at_every_address_takes_no_port_held_at_the_machines_address(the other order) anda_second_listen_at_the_machines_address_takes_no_port_held_there. Each: the second listen isInUse, its owner's pipe end is dropped, one listener and one place are held, and the next handshake wakes the first owner and is accepted there.643584d4bbefore the rule (stepsred-first-build, exit 0, andred-first--<test>): the two cross-address orders exit 101,userland/netstack/node/tests/listeners.rs:766:23: called Result::unwrap_err() on an Ok value: (ListenerId(1), Port(22)); the same-address one exits 0, which [tcp] already refused.a_listener_is_at_the_address_it_named_and_only_one_the_machine_holdsno longer puts a named listener beside one at every address; it keeps the refused listen at 192.0.2.7, the reset, and a listener at 192.0.2.1 that is woken.NOTE,
tests/host.rs, all three. Oneassert_eq!over the pair, its message the host's name (std::env::consts::OS), so a red log is the reading. The second arm now waits on the event it depends on. A connect returns at the SYN-ACK and the listener's end is established by the ACK after it; the arm rested on loopback delivering that ACK first. What a portable program can observe is that the listener is readable, which is the host saying a connection waits to be accepted: the test registers the listener withmiobefore the connect and sets the option only after the poll reports it, under a 60 s ceiling that panics. Both arms take the same path, so the accept never waits either. The module header says what the arm rests on, and the issue says it too.miois new as a direct dev-dependency oftoyos-net-node: general and widely used, in the lock already through the workspace's patch (one line added toCargo.lock, no new package; default features off, so its own dependency list in the lock is unchanged), on Linux, macOS and Windows alike. std cannot learn that a connection waits except by accepting it, andsocket2has no poll; the alternative wasunsafelibcpoll, Unix only.NOTE, a listener whose address the machine loses. Round 2 ended it; round 3 found that wrong (BLOCKER 6, above) and it is removed.
NOTE, the body's logs. Every step of this round has its log, named below.
The merge of
49e38ca4a(fdfcf6576), hunk by hunkThree files stopped the merge;
Cargo.lockdid not, and cargo, given stage D's lockfile and this manifest, writes the same bytes git merged (cargo metadata --locked, stepgate-lock, exit 0).userland/netstack/node/src/lib.rs, the header's paragraph on who owns what: one sentence withdatagram,nameandresolve, thenplacessaid to count what it counts: streams, listeners and clients' datagram sockets, the responder's socket and a lookup's outside it.lib.rs, the module list: both sides whole:datagram,lease,listeners,name,places,resolve,streams.userland/netstack/node/Cargo.toml,description: the three kinds and their one bound, the name and the resolver. The dependencies merged by themselves:toyos-dns,mio,socket2.Merged by git alone and read at the merge: the three
pub uselines (listeners, the widenedstreams,resolve);NodeandNode::newwithname,resolver,streams,listeners,sockets,placesonce each;next_deadlineis stage D's five terms and gains none (a listener keeps no deadline);settleends inserve_namethen the oneresolver.pass;receiveandfireend inbridgeaftersettle,transmitinpass(now, true);lease.rskeepsStack::connectandStack::close[udp]'s and unprefixed, every forward of this stage istcp_-prefixed.lib.rsagainst49e38ca4adiffers only in this stage's lines. d21, n2 and r16, the chain without each joined term, are red after the merge (table).A lookup's sockets hold no place: decided, in words and by a test.
resolve.rsbinds and closes throughStack::bindandStack::close, pastNode::udp_bind, the one call in which a datagram socket takes a place. They are the node's own, as the responder's is, and stand outside the places, bounded bytoyos_dns::MAX_LOOKUPSand each lookup's rounds: so clients at the bound refuse no lookup.places.rs's header opens "the one bound on the streams, listeners and datagram sockets clients make the node hold" and has the paragraph;issues/netstack-datagram-sockets-and-listeners-have-no-bound.mdand the track say the same. One test of the resolver's changed, and had to:a_query_with_no_port_to_leave_from_ends_its_lookup_by_namebinds every dynamic port but one as a client, 16,383 sockets, which 32 places refuse; it now gives its node exactly 16,383 places and asserts that the next client bind isResourceExhaustedand the lookup still starts and sends. e25 (a socket holds no place) is red there too. The other 22, the burst ofMAX_LOOKUPSamong them, are green underPLACES = 32unchanged.The earlier merges (
b230f88a5,fa6f350ff) were reviewed in rounds 1 and 2.What differs from what ships today
Nothing ships this yet. These are the behaviours that will differ from netstack today once it moves onto the node:
SYNACK_GIVE_UP(60 s) and holds nothing shut meanwhile. Today it holds the port shut for the rest of the boot.Node::close_listener).max_piped_connectionscounts established and connecting streams, and nothing bounds listeners (issues/netstack-datagram-sockets-and-listeners-have-no-bound.md). On the node a stream, a listener and a connection [tcp] is finishing alone each hold a place, and a connect, a listen and an accept past the number are refused with nothing made. The number stays the shell's (memory and poller slots are its to know); a new node has none untilNode::set_places.Fullwhatever waits; today an accept with nothing waiting is answered "nothing" first. The mapping onto the pipe ABI's codes is the move's.nodelay, and a connection that begins afterwards has it, as on the host measured above. Today the request is refused for a listener, and every accepted stream starts with Nagle's algorithm on.toyos_dns::MAX_LOOKUPSbounds them.Unchanged: a listen before the lease listens; an accept without pipes is refused; closing a listener resets what waits at it.
What a peer can make the node hold
Without any client asking, at each listener: 256 handshakes in progress, each a control block with no buffer, each for at most 60 s; and 128 finished connections waiting to be accepted, each with at most one receive buffer of text (65,535 bytes as netstack configures it). Past either, a SYN or a final ACK is dropped and counted (
tcp.listen-overflow,tcp.accept-queue-full). Listeners are at mostplaces. The product: 128 waiting connections of 65,535 bytes are 8.4 MB a listener, which no place counts and the listener's one place stands for (decided above, with the reason).With a client's help: a datagram socket is one place and [udp]'s two queues of 16 datagrams; an accepted or connected stream is one place, two of the client's pipes and [tcp]'s two buffers; a connection the client let go keeps its place until [tcp] has finished it, which the peer can stretch (60 s idle, restarted by every acknowledgment). So a peer holds at most
placesplaces, and holding them all denies every connect, listen and accept: recorded in the track with the per-program share that would end it.How this bound meets stage D's. Stage D lets one peer address keep at most 16 streams their clients can see no more alive by taking their bytes (
OWNERLESS_PER_PEER), and counts no addresses; one past the 16 has 100 s from the pass that found it so, until one of the 16 is done. Such a connection holds a place through both of its lives: it is a stream while its pipe holds bytes, and [tcp]'s to finish after. So stage D's rule bounds how long a peer holds a departed client's place while bytes are left, places bound how many a peer holds in all, and neither replaces the other:a_departed_clients_connection_holds_its_place_until_it_is_cutholds one at the bound, sees a connect refusedFull, and sees the place go to a connection that waited when stage D's rule cuts it, andpeers_at_more_addresses_than_there_are_places_hold_no_stream_past_themholds the places against peers at more addresses than there are places. Still open, in the track: a connection [tcp] is finishing is reset after 60 s idle and every acknowledgment restarts that, so its place is the peer's for as long as it has bytes to acknowledge.Not bounded by this stage, and recorded in the track: a SYN flood of 256 a minute from addresses that never answer shuts a port to other peers ([tcp] has no SYN cookie); TIME-WAIT is [tcp]'s 16,384 entries.
What changed, per decision
listeners.rs:Node::listen,set_listener_nodelay,listener_nodelay,accept,close_listener,listeners,drain_ended_listeners; the traitWakeis the owner's pipe, as stage D's pipe traits are a client's. The queues stay [tcp]'s: the node keeps no second queue of accepted-but-unclaimed connections, which would be a sibling of [tcp]'s and would turn its reset-before-accept rule off.Node::pass, which a frame, a deadline, a transmit opportunity and each call end in), and costs one lookup in [tcp] a listener:Tcp::readymoves nothing, re-files no deadline and offers no flow, which is what toyos-net-node: streams, a connection of the own stack's bridged to its client's two pipes (stage D) #775's review asks of this stage.places.rs:Node::set_places,held, and the oneroomevery admission reads. A setter and not a parameter ofNode::new: the node is born with its link down, no address and no place, and the shell tells it each; it also leavesNode::newas stages B and C call it.streams.rsgainedConnectRefused(soNode::connectnow answersResult<StreamId, ConnectRefused>, the one signature of an earlier stage that changed),Stream::establishedandStreams::hold/accepted(one constructor for a connect's stream and an accept's), and threewake_ownerslines where a place comes back (pass, a closed connect, a stream reset for its pipe).tests/streams.rs: one assertion and one import follow the new refusal type.tests/common/mod.rs: the test node gets 32 places, above the 17 streams stage D's per-peer test holds.lease/tcp.rs: five forwards beside stage D's,tcp_-prefixed as toyos-net-node: streams, a connection of the own stack's bridged to its client's two pipes (stage D) #775's review asks of that file.shardstays private tolease.toyos-net-tcp(named, as the brief asks):Tcp::ready(ListenerId), how many connections wait at a listener, andTcp::orphans(), the connectionscloseleft it to finish, a count kept incloseandfree. Tests intcp/tests/held.rs. This round addsTcp::options(ConnId), the read of a connection's options.toyos-net-shard: four forwards,listener_port,ready,close_listener,orphans. None settles: none makes a flow eligible, and a closed listener's resets leave outside the round. This round addsset_listener_optionsandoptions, andShard::listenanswersResult<ListenerId, ListenError>with itsNotLocal.a-handshake-nobody-finishes…'s exit is a test this stage has, anda-connect-between-two-accepts…'s first half too, but both defects are still true of what ships until the move.The checks (a trust boundary)
Wire input. No received byte is read in the node.
no_cut_of_a_syn_is_a_syndelivers every prefix of a peer's SYN andno_flipped_bit_of_a_syn_wakes_an_owner_or_ends_a_listenerevery single-bit flip of it to a listening node: no wake, no stream, the listener stands, and a whole handshake after it completes. The newunreachable!s (lease/tcp.rs) are on [tcp]'s answers about a listener or a connection the node holds; none is reachable from the wire that I can construct.Time is an argument of every call; the tests' clock moves only to
next_deadline. No wait anywhere.Negative control. 39 named mutations in 45 runs at
e737052a5, each applied as a checked patch, built (stepmutation-<name>--build), run against one test by exact name (stepmutation-<name>--<test>), and reversed with the tree clean after. Red first for BLOCKER 5 (round 5's steps) and for BLOCKER 6 (above). No whole-change revert: the tests call signatures the base lacks. The patches are in the newest patch comment.running 1 test)an_orphan_is_counted_until_both_finstoyos-net-shard/tcp/tests/held.rs:17:5left == rightfailed: TIME-WAIT is no connectionan_orphan_is_counted_until_both_finstoyos-net-shard/tcp/tests/held.rs:15:5left == rightfaileda_close_before_the_handshake_ends_leaves_an_orphan_until_it_gives_uptoyos-net-shard/tcp/tests/held.rs:40:5left == rightfailedready_counts_the_connections_accept_has_yet_to_returntoyos-net-shard/tcp/tests/held.rs:58:5left == rightfailed: a handshake in progress is not readyoptions_are_the_ones_the_connection_hastoyos-net-shard/tcp/tests/held.rs:77:5left == rightfailed: its listener's, as its SYN found thema_handshake_nobody_finishes_leaves_the_port_open_and_is_given_upuserland/netstack/node/tests/listeners.rs:471:41a_connect_between_two_accepts_is_queued_not_resetuserland/netstack/node/tests/listeners.rs:306:64a_listener_answers_a_syn_and_wakes_its_owner_when_the_handshake_endsuserland/netstack/node/tests/listeners.rs:442:5left == rightfaileda_listener_answers_a_syn_and_wakes_its_owner_when_the_handshake_endsuserland/netstack/node/tests/listeners.rs:449:5left == rightfailed: what arrived before the accept moves in ita_connect_between_two_accepts_is_queued_not_resetuserland/netstack/node/tests/listeners.rs:502:5left == rightfailed: one wake a connectionan_accept_spends_a_wake_whatever_it_answersuserland/netstack/node/tests/listeners.rs:527:5left == rightfailed: the wake that accept spent is not counted against the next connectiona_listen_on_a_taken_port_is_refused_and_a_drawn_port_listensuserland/netstack/node/tests/listeners.rs:729:5left == rightfaileda_wake_is_owed_only_for_a_connection_there_is_a_place_foruserland/netstack/node/tests/listeners.rs:556:5left == rightfailed: two wait, and there is a place for onea_wake_is_owed_only_for_a_connection_there_is_a_place_foruserland/netstack/node/tests/listeners.rs:558:31Result::unwrap_err()on anOkvalue: (Accepted { id: StreamId(1), remote: Endpoint { addr: 192.0...a_wake_is_owed_only_for_a_connection_there_is_a_place_foruserland/netstack/node/tests/listeners.rs:564:5left == rightfailed: a connection [tcp] is finishing holds its placea_connect_past_the_places_is_refused_and_sends_nothinguserland/netstack/node/tests/listeners.rs:640:5left == rightfailed: a connect not yet answered holds a placea_connect_past_the_places_is_refused_and_sends_nothinguserland/netstack/node/tests/listeners.rs:640:5left == rightfailed: a connect not yet answered holds a placea_listener_holds_a_place_and_a_listen_without_one_makes_nothinguserland/netstack/node/tests/listeners.rs:659:23Result::unwrap_err()on anOkvalue: (ListenerId(1), Port(23))a_listener_holds_a_place_and_a_listen_without_one_makes_nothinguserland/netstack/node/tests/listeners.rs:659:23Result::unwrap_err()on anOkvalue: (ListenerId(1), Port(23))closing_a_connect_gives_its_place_to_a_connection_that_waitsuserland/netstack/node/tests/listeners.rs:597:5left == rightfaileda_stream_reset_for_its_pipe_gives_its_place_to_a_connection_that_waitsuserland/netstack/node/tests/listeners.rs:609:5left == rightfailedclosing_a_listener_gives_its_place_to_a_connection_that_waits_at_anotheruserland/netstack/node/tests/listeners.rs:620:5left == rightfailedmore_places_wake_the_owner_of_a_connection_that_waitsuserland/netstack/node/tests/listeners.rs:630:5left == rightfailedclosing_a_listener_resets_what_waits_and_frees_its_portuserland/netstack/node/tests/listeners.rs:687:9a_wake_the_owners_pipe_refuses_ends_the_listeneruserland/netstack/node/tests/listeners.rs:707:9left == rightfaileda_stream_starts_with_the_options_its_connection_took_from_its_listeneruserland/netstack/node/tests/listeners.rs:856:5left == rightfaileda_stream_starts_with_the_options_its_connection_took_from_its_listeneruserland/netstack/node/tests/listeners.rs:856:5left == rightfailedan_accepted_stream_is_one_of_its_peers_addresss_sixteenuserland/netstack/node/tests/listeners.rs:908:5left == rightfaileda_request_for_a_stream_that_was_cut_names_nothinguserland/netstack/node/tests/listeners.rs:935:5left != rightfailedpeers_at_more_addresses_than_there_are_places_hold_no_stream_past_themuserland/netstack/node/tests/listeners.rs:964:5left == rightfailed: sixty-four wait, and there are places for twopeers_at_more_addresses_than_there_are_places_hold_no_stream_past_themuserland/netstack/node/tests/listeners.rs:974:39Result::unwrap_err()on anOkvalue: (Accepted { id: StreamId(2), remote: Endpoint { addr: 192.0...peers_at_more_addresses_than_there_are_places_hold_no_stream_past_themuserland/netstack/node/tests/listeners.rs:974:39Result::unwrap_err()on anOkvalue: (Accepted { id: StreamId(2), remote: Endpoint { addr: 192.0...a_datagram_socket_holds_a_place_and_a_bind_without_one_makes_nothinguserland/netstack/node/tests/listeners.rs:824:5left == rightfaileda_datagram_socket_holds_a_place_and_a_bind_without_one_makes_nothinguserland/netstack/node/tests/listeners.rs:823:5left == rightfaileda_datagram_socket_holds_a_place_and_a_bind_without_one_makes_nothinguserland/netstack/node/tests/listeners.rs:831:5left == rightfailed: its place is back, and the connection that waited is announceda_datagram_socket_holds_a_place_and_a_bind_without_one_makes_nothinguserland/netstack/node/tests/listeners.rs:831:5left == rightfailed: its place is back, and the connection that waited is announceda_listener_is_at_the_address_it_named_and_only_one_the_machine_holdsuserland/netstack/node/tests/listeners.rs:744:23Result::unwrap_err()on anOkvalue: (ListenerId(0), Port(22))a_listener_is_at_the_address_it_named_and_only_one_the_machine_holdsuserland/netstack/node/tests/listeners.rs:744:23Result::unwrap_err()on anOkvalue: (ListenerId(0), Port(22))a_listener_ended_for_its_wake_gives_its_place_to_another_in_the_same_passuserland/netstack/node/tests/listeners.rs:810:5left == rightfaileda_listen_at_the_machines_address_takes_no_port_held_at_every_addressuserland/netstack/node/tests/listeners.rs:766:23Result::unwrap_err()on anOkvalue: (ListenerId(1), Port(22))a_listen_at_every_address_takes_no_port_held_at_the_machines_addressuserland/netstack/node/tests/listeners.rs:766:23Result::unwrap_err()on anOkvalue: (ListenerId(1), Port(22))a_query_with_no_port_to_leave_from_ends_its_lookup_by_nameuserland/netstack/node/tests/resolve.rs:575:5left == rightfailed: every place is a client's socketa_connect_past_its_deadline_is_timed_out_at_the_deadlineuserland/netstack/node/tests/streams.rs:566:5left == rightfaileda_held_lease_is_announced_at_once_and_a_second_lateruserland/netstack/node/tests/name.rs:170:5a_resolver_that_never_answers_is_asked_at_each_waits_enduserland/netstack/node/tests/resolve.rs:615:5left == rightfailedGates
At the merged head
f0c483ea2, by me, in this worktree, tree clean before and after, on macOS under load (1-minute load average 35 to 107 as sampled over the steps); step names are the log names.f0c483ea210-build-onlycargo run -- --build-only20-gate-lockcargo metadata --locked --format-version 121-gate-tcp-testscargo test --locked -p toyos-net-tcp(21 targets with tests, 5 to 42 each;held6,take5)22-gate-shard-testscargo test --locked -p toyos-net-shard(acquisition5,drr6,egress19,icmp4,log3,net5,udp3, 2 unit tests)23-gate-node-testscargo test --locked --manifest-path userland/netstack/node/Cargo.toml(datagram5,host1,lease19,listeners28,name13,resolve23,slirp3,streams34)24-gate-dns-testscargo test --locked -p toyos-dns(44)25-gate-ip-testscargo test --locked -p toyos-net-ip(acd18,addr37,clk10,icmp53,igmp35,io52,nbr23,nud35, 8 doc tests)26-gate-tcp-clippy,27-gate-shard-clippy,28-gate-node-clippy,29-gate-dns-clippycargo clippy --locked ... --all-targets -- <the adopted lints> -D warnings, this machine's toolchain30-ci-hostcargo run -- --ci host(Host: 78 step(s), all green;clippy, warnings denied: clean; the node's eight targets with the counts above)40-mutations-apply-checkgit apply --checkof each of the 39 patchestests/host.rson this machine:test a_host_gives_a_connection_the_nodelay_its_listener_had_when_it_began ... ok, green, so its oneassert_eq!held(true, false)onmacos, in23-gate-node-testsand again in30-ci-host.Round 6, at the reviewed head; round 5's steps (at
d1d940dfdandc27cc36aa) are in the earlier body and their logs are kept.c27cc36aa+ the new testred-first-build,red-first--<test>e737052a5gate-lockcargo metadata --locked --format-version 1gate-tcp-testscargo test --locked -p toyos-net-tcp(21 targets with tests, 5 to 42 each;held6,take5)gate-shard-testscargo test --locked -p toyos-net-shard(acquisition5,drr6,egress19,icmp4,log3,net5,udp3, 2 unit tests)gate-node-testscargo test --locked --manifest-path userland/netstack/node/Cargo.toml(datagram5,host1,lease19,listeners28,name13,resolve23,slirp3,streams34)gate-root-libcargo test --locked --lib -- hostws:: userlandhost:: sourcegate:: licence::(45 tests)gate-tcp-clippy,gate-shard-clippy,gate-node-clippycargo clippy --locked ... --all-targets -- <the adopted lints> -D warnings, this machine's toolchain (1.98.1)mutations-summaryrunning 1 testeachafter-mutations-tcp-held,after-mutations-node-testsheldand the node's tests again, unmutatedNot run, and not claimed: the
hostjob onubuntu-24.04andguest / suite, which a draft skips; clippy on CI's toolchain;tests/host.rson Linux or Windows; the 39 mutations atf0c483ea2(above). No guest test reaches the change: no shipped package depends ontoyos-net-node,toyos-net-shardortoyos-net-tcp.What I am unsure of
tests/host.rs. Unread; macOS answers(true, false). The arm no longer rests on delivery order, so a red on Linux is Linux's answer, printed with its name.mioon Windows is read from its API (AsSocketforSockRef), not run.InUseis checked beforeNotLocalinNode::listen, where [udp]'s bind checks the address first: a listen at an address the machine does not hold on a held port answersInUse.ListenRefused::NotLocalcounts nothing; in the track with the other unspecified additions.MAX_LOOKUPStimes what a lookup keeps out over its rounds; I did not derive the product.a_wake_left_by_a_connection_its_peer_reset_stands_for_the_nexthas no mutation of its own: no line writes a wake for an arrival.🤖 Generated with Claude Code
https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A