Repository navigation
Conversation
Japabu
force-pushed
the
tcp-pace
branch
2 times, most recently
from
October 31, 2026 17:49
dc4f576 to
b3aee3b
Compare
Japabu
added a commit
that referenced
this pull request
Oct 31, 2026
…goes On the T14's LAN path, #1114's A/B (evidence branch pr-1114, r4/t14 at 783d377ae1ee56592aae53465a74bf60c3beb839) read the paced sender with L = infinity (P) against #1112's head (C) and Linux in the same session: p50 MB/s by 100 ms by 300 ms Linux 112, 112 5.7 26.1-26.2 MB C 112 x3 2.0-3.1 23.8-25.4 P 114 x3 6.3-6.6 29.3-29.9 P beat C at 300 ms in every pair, resent nothing, and is at or above Linux at every mark. That meets the record's exit, so the record goes. The comparison record's pointer to it now names the PRs and the evidence. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Japabu
added a commit
that referenced
this pull request
Oct 31, 2026
…goes On the T14's LAN path, #1114's A/B (evidence branch pr-1114, r4/t14 at 783d377ae1ee56592aae53465a74bf60c3beb839) read the paced sender with L = infinity (P) against #1112's head (C) and Linux in the same session: p50 MB/s by 100 ms by 300 ms Linux 112, 112 5.7 26.1-26.2 MB C 112 x3 2.0-3.1 23.8-25.4 P 114 x3 6.3-6.6 29.3-29.9 P beat C at 300 ms in every pair, resent nothing, and is at or above Linux at every mark. That meets the record's exit, so the record goes. The comparison record's pointer to it now names the PRs and the evidence. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RFC 9406 §4.3 caps a non-paced sender's growth at L = 8 segments an ACK and gives L = infinity to a paced one. On the T14's LAN path the cap is what held a new connection's ramp: with L = infinity and no pacing (a diagnostic arm in #1112's session) ToyOS reached Linux's marks at 100 and 300 ms, and nothing was resent. That arm had no pacing, so it did not land. [tcp] now paces each connection (RFC 9002 §7.7). A token bucket fills at N * cwnd / srtt. It holds the larger of the initial window, the burst §7.7 bounds a sender to, and 1 ms at the rate. Every data segment spends from it. New data that it cannot cover is held until half the bucket has come back, so a wake up to half a millisecond late loses no rate. The release instant is a connection deadline, so netstack wakes through the existing Tcp::next_deadline. No thread or timer is added. - N is 1.25 in avoidance, the value §7.7 gives. In slow start it is 2: cwnd doubles across the round trip, so the round's data is twice the window it began with, and 1.25 would hold the doubling back. - A pacing hold leaves `limited` as the last send set it. Clearing it would stop growth and CUBIC's clock while the pacer is spreading a window that is in use. - Retransmissions spend but are not held. Recovery's pipe and a timeout's one-segment window already bound them. - ACK-only segments are not paced (§7.7). - Without a smoothed RTT nothing is paced, and cwnd is at most the initial window then. - The time a pacing hold keeps data waiting is `tcp.send-limited.pacing.ns`. osbench reads it. Two net.rs downloads on a 15 ms gigabit path asserted that the receive window reaches the whole 4 MiB buffer. The window grows to twice what is read per round trip, 3.75 MB on that path. The unpaced sender carried it to the whole buffer; paced, it stops 3 KB short. They now assert the rule's bound. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Evidence branch pr-1112 was rewritten to f2909c1f without the commit that published an unmasked temp path; c990b1f2 no longer exists. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With any send joining a run shorter than SMSS, a window of body sent after a short header joined the header's run. The run then spanned two instants, so only its first partway ACK was timed: HTTP's shape lost most of its samples. A send now joins a short run only if it is short too, as before #1112's review r2. The record's bound returns to two runs per SMSS in flight plus two, about 16 KB at a 250-segment flight. s_rt_025: a 200 B header, then four segments of body 1 ms later. Four ACKs give four samples. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ue stays open The bucket held max(IW, 1 ms at the rate). At srtt up to about 2 ms in slow start, or 1.25 ms in avoidance, a millisecond at the rate is at least cwnd, so the pacer never held there. HyStart++ still took L = infinity there, which is the unpaced L = infinity RFC 9406 §4.3 rules out. The bucket is now max(IW, min(1 ms at the rate, cwnd / 2)). A window past the initial one always leaves in more than one burst, at any srtt. The cost is more wakes on short round trips: a half bucket every srtt / 8 in slow start. The module header now gives the bucket's two bounds and their floor, and says where the millisecond departs from RFC 9002 §7.7's initial-window burst. It also names the data that spends but is never held, each with its reason: retransmissions, new data in SACK recovery, and a loss probe of new data. `hold`'s doc says when the held data is offered again. The ramp issue is no longer deleted. The A/B ran on trees from before #1106 and with the old bucket, so it is recorded as relative evidence (P over C, N = 2 over 1.25) at its full evidence hash. Parity waits on a boot of this head. - a_bucket_holds_at_most_half_the_window: at srtt 1 ms the bucket is 50 of a 100-segment window. - a_fast_bucket_holds_a_millisecond now uses srtt 8 ms, where the millisecond is under half the window. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ver holds The header said a fast sender wakes at most every half millisecond, and that a wake up to half a millisecond late loses no rate. Both hold only while the millisecond sets the bucket. Where half the window sets it (srtt under 4 ms in slow start, 2.5 ms in avoidance), the wake comes every half refill time and the slack is that much. At cwnd 146,000 and srtt 1 ms that is 125 µs. Both are now stated as half the bucket's refill time. The list of data spent but never held now has persist probes of new data. New data in SACK recovery is bounded by RFC 6675's pipe and the ACK clock, not "a segment at a time": next_segment repeats while cwnd - pipe >= SMSS. The issue's A/B bullet names 3a0744d as the head whose pacer holds at most half the window. Comments only; no code changed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e bucket Where the initial-window floor sets the bucket, it holds more than a millisecond at the rate, so the half-refill wake interval is longer than half a GRANULARITY, not shorter. Comment only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… record stays open One T14 boot of 3a0744d on current main, with Linux before and after it (evidence branch pr-1114, r7/t14 at 8f08a8e9399817fc669e395dc717ee236a18ed35): p50 MB/s by 100 ms by 300 ms Linux 113, 113 6.10, 6.38 MB 28.63, 29.56 MB ToyOS 114 6.07 MB 29.19 MB The rule set before the run asked for both marks at or above the lower Linux run's. The 300 ms mark, the p50 and loss (none) passed. The 100 ms mark missed by 30 KB, 0.5 %, so the record stays open with this reading. That gap is under the runs' own spread (280 KB between the two Linux runs, 260 KB across the A/B's three P runs), which one boot cannot separate it from. The record's opening now gives the paced sender's numbers and keeps the pre-#1114 ones beside them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Reviewed in two rounds; head abae9a3 on main 24a538c. The parity boot ran: the 300 ms mark, p50 and loss pass; the 100 ms mark misses the pre-set rule by 30 KB, so the ramp issue stays open with the reading.
Problem
On the T14's LAN path a new ToyOS connection delivers 2–3 MB in its first 100 ms and about 24 MB by 300 ms, against Linux's 6 MB and 28–29 MB (
issues/tcp-opens-a-connections-window-slower-than-linux-in-its-first-300-ms.md). In #1112's session a diagnostic arm, L = ∞ without pacing, reached Linux's marks (5.2–6.4 MB and 28.3–29.7 MB) and resent nothing. So RFC 9406 §4.3's L = 8 is what holds the ramp back. That section gives L = ∞ only to a paced sender, so the unpaced arm could not land.Change
src/pace.rs, RFC 9002 §7.7). A token bucket fills at N · cwnd / srtt, and every data segment sent spends from it.Tcp::next_deadline, so no thread or timer is added.limited: a pacing hold leavesCc::limitedas the last send set it. Clearing it would stop slow start's growth and CUBIC's clock while the pacer spreads a window that is in use.tcp.send-limited.pacing.ns, and osbench reads it.Tests
pace.rs(unit tests):tests/pacing.rs, through the connection:cargo test --no-fail-fast442 passed, exit 0; clippy on the crate clean. f7c9eb2 and ff0dc5a change only comments (pace.rs's header, one issue bullet), so these checks and the parity boot of 3a0744d still apply; abae9a3 changes only the issue; the pacer's unit tests and clippy were rerun on it, clean.limitedcargo run -- --ci hostat 3a0744d: 107 steps, all green, exit 0.Metal
Parity boot, 2026-11-01 (3a0744d on current main, between two Linux runs; image and binary hashes checked first; every run rc 0, the judge exit 0). The Linux binaries were rebuilt at this head, and the peer's are byte-identical to the A/B's.
A/B, 2026-10-31 (relative evidence; every boot rc 0, every judge exit 0). Peer: the AMD bench's Linux over its Wi-Fi. Order: Linux L1, c1 p1 q1 c2 p2 q2 c3 p3 q3, Linux L2.
Marks are each row's mean over five connections, received by the peer, in MB:
This needs metal because the claim is the ramp on a real gigabit path against Linux on the same machine. The host harness establishes the pacer's arithmetic and the connection's behaviour, but not a real path's queue.
Risks
🤖 Generated with Claude Code