Repository navigation
Conversation
Review r1 of #1077 at
|
Review r1 answered at 4849b7b, now rebased onto #1074's fixed head 46848c4Blockers
Suggestions
Checks at 4849b7b
T14 images are staged at 4849b7b. |
48c92e3 to
094323f
Compare
Review r2 of #1077 at
|
Review r2 answered at 10c835a
|
… load A connect made its whole 8 MiB region memory, every frame zeroed before the SYN left. It now commits the header and 64 KiB of each ring, and a ring's producer doubles it, up to its 4 MiB place, as it needs more. Kernel: a memory object's pages are committed at an explicit call, never at a fault. SYS_SHM_CREATE with SHM_PART commits only a first part; SYS_SHM_COMMIT (any holder with MAP) promises and zeroes the rest at the call, charged to the object's own job as its creation is. A fault on a page not committed is refused like an access outside every range, and a copy reads it as zeros. fstat's new `committed` reports what is. Transport: cursors are stream positions and a ring's capacity a power of two, so a byte's offset is its position below the capacity on every lap. A producer grows its ring without waiting for it to empty: the size word names the new capacity and a switch one old ring past the head it last read, past every byte it may have placed; bytes before the switch keep the old mask, bytes after use the new, and the room stays counted in the old capacity until the consumer passes the switch, so a new byte can only land on an unread one a whole old ring behind it, which the old room forbids. A consumer reads the size after the tail and judges it before believing it. The client grows its send ring when a write finds less room than it brings; netstack grows the receive ring when [tcp] asks for more buffer than it holds, each after committing the memory. Each consumer commits what a peer's size claims before it touches it, so a lying peer spends its own region's memory and cannot fault the reader. [tcp] addresses a lent ring by stream position and the node lays it out by the judged size. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t test Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ss reads cleanly Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ng's first size Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s window or netstack B1: SharedMemObject::read answers zeros for a page not committed, as the module promised; a program image spawned or opened from a region with pages not committed is refused by its loader rather than stopping the machine. A kernel write still panics there: every writer commits its object whole. B3: a syscall's window over an object's page not committed is BadAddress (Space::allows asks the object), where the copy used to drop the bytes and answer success; the fault path already refused the same access. B2, S2: netstack commits the header and both rings' first sizes where it judges a region, charged to the client, and refuses it there if it cannot; the node's deferred `broken` state is gone. B4: a span the pass's own size cannot lay out is unreachable, said with its arguments, not a silent no-op. S1: the ring's oracle runs across the u32 wrap with growth. S3: fstat's `committed` is gone: readers commit rather than ask. S4: a write that fills the send ring grows it too. S5, B6: shm_commit reads the job's account around each commit and the close, covers B1 and B3, and runs on QEMU as shm_commit_alone. A client's region with nothing committed is driven at real netstack in netcase's boot (stream_region_refused). B5: a ring that never shrinks is recorded with its owner and exit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The rebase onto main brings #1123's paged objects, which free a frame with `pmm::free_drawn` as the shared regions now do, and #1104's clock page, which reads its object's one page: both take the commit's types. SYS_SHM_COMMIT is 160, past #1121's SYS_MESSAGE_RECV. The guest tests map with a protection, as main's `shm_map` asks, and a connect's answer carries main's three bells. The kernel's `Stat` size assertion, left from the removed `committed` field, goes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n the guest The ring's growth was tested on the host, through models of the region's memory, and on no guest: nothing ran the client's commit before it reads or writes past a ring's first size, or netstack's before it grows the receive ring, against the kernel that refuses a touch past a commit. `stream_grows` sends 4 MiB each way over loopback, in writes of a byte to more than a ring, and checks every byte's place. It runs in the netcase boot that already runs `stream_region_refused`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… loopback netstack refuses 127/8 as a destination, so `stream_grows` listens on the port its boot forwards and the harness connects through it, as for `socket_export`: both ends send 4 MiB and check every byte's place. `shm_commit` names main's `SpawnArgs.pager` and closes in `unsafe`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…host No test drove the receive ring's growth end to end: the node harness configured [tcp] with 64 KiB buffers, not netstack's 4 MiB, and its peer offered no window scale, so [tcp] never wanted more than the first ring. The harness now configures netstack's buffers, and its peer can scale windows (RFC 7323 §2.2) and read the node's scaled windows. The new test downloads with a reader that keeps up and a millisecond round trip: [tcp] wants more buffer, and netstack commits and doubles the receive ring twice, every byte read in place. Its client commits nothing, so every page either end touches is one netstack committed: with netstack's commit before the growth removed, the node's put past what was committed reds it. `stream_grows` on the guest claims the send ring's growth alone: QEMU's user network's round trips seldom make [tcp] want more. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…host Both ends commit what a grown ring reaches, so in an honest exchange either end's commit hides the other's absence: the guest showed it, with the client's commits removed and `stream_grows` still green. The node harness's region now says whose commits make memory. The upload test counts the client's alone and the download test netstack's alone: with the client's commit before its growth removed, the node's read past what was committed reds the first, and with netstack's, its put reds the second. `stream_grows` claims what only the guest has: netstack's own mapping read past the ring's first size, over pages the kernel committed after both ends mapped it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nd a joined writer commits what it resumed B1: no test failed with netstack's commit of a client-grown send ring removed, which is all that keeps a client from faulting netstack. A client that claims its send ring grown to 4 MiB without committing a page, and publishes 100,000 bytes, is now read only through netstack's own commit, and the peer hears a fresh commit's zeros. With that commit removed, the harness's take past what was committed reds it. S1: a holder that joined copied by the size the last holder stored without committing it, so a co-holder's bogus size ended the process. It now commits a resumed capacity it has not reached before it copies, and one it cannot commit is Violation::Size, as a read's already was. S2: the client's own commit before it reads by a size netstack stored is pinned the same way, netstack's commits switched off. S3: a size netstack's commit is refused for resets that stream alone, before [tcp] reads by it. S4: a shape's `holds` says what each ring uses it as. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… judges it covered The A/B at 342ecc1 read every bench's syscall-heavy rows slower at the head, resolved on both: fs.mem.fstat +7% on the T14 and +15% on the AMD bench, pipe.throughput.64 -7%, fs.disk.pread_4k +4 to +6%. `allows`, which every user window asks first, had gained a second walk of the inventory, `meeting`'s two descents, to judge an object's pages committed, even for a window in private memory. `covered_where` now hands its predicate each entry's start, and `allows` asks an object entry for its pages in the descent it already made; a private entry costs nothing more. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The A/B at 342ecc1, read against the rule set before it ran; the fix at 549f1c4
Evidence: https://github.com/ToyOSOrg/evidence/tree/b2a6aae4e9e9639acca8a5075616767b82147476/r5 |
Part 2 of the approved "remove the memory a TCP connect wastes" work (#1074, part 1, landed). The design is option B, which the orchestrator approved under the owner's "properly" direction. The growth mechanism is the implementers' design, not the owner's. Rebased onto main
058bea2d3;SYS_SHM_COMMITis 164, after main's 160 to 163.Problem
A connect made its whole 8 MiB stream region memory, and every one of its 2048 frames was zeroed before the SYN left. On the T14, a 2 MiB region moved the connect p50 from about 3.0 to about 2.05 ms (#1069's evidence,
issues/a-tcp-connect-zeroes-twelve-mebibytes.md).What changes
Kernel: a memory object is committed only by an explicit call, never by a fault.
SYS_SHM_CREATEwithSHM_PARTcommits only the firstcommittedbytes (toyos_abi::syscall::shm_reserve).SYS_SHM_COMMIT(handle, offset, len)(164) commits more. Any holder withMAPmay call it. It promises and zeroes the pages at the call, charged to the object's own job (mm: every charge is its job's, the controls keep a floor, and a restart refused for memory waits for it #1057's accounting), and is refused whole where they cannot all be promised. It is lock-free: a racing commit gives back its frame and its unused promises.BadAddress:Space::allowsasks an object entry for its pages in the one inventory descent it already makes. The kernel's own read of one, such as a program loader's, answers zeros. A kernel write to one panics: every kernel writer (inbox, clock page, TLS) commits its object whole.kernel/src/object/shm.rs.The region. A 4 KiB header plus a 4 MiB receive ring and a 4 MiB send ring (
REGION_BYTESis 8 MiB + 4 KiB). A connect commits the header and 64 KiB of each ring: 132 KiB, where it used to commit 8 MiB.Each ring doubles under its producer, without ever needing to be empty (
toyos::transport::stream).Violation::Size.Who grows which ring, and who commits.
Region::judged), charged to the client, and refuses the region there if that fails.Violation::Size. A holder with onlyMAPmay commit, up to the size the owner made the region.[tcp] addresses a lent ring by stream position, through
Lent::mostandLent::want; the node lays each ring out by the size its pass judged last.Records.
issues/a-stream-commits-eight-mebibytes-and-a-process-holds-128.mdis split: its region half is this change, and what is left isa-process-holds-at-most-128-streams.md. A ring never shrinks:a-stream-ring-never-shrinks.mdrecords it, with an owner and an exit.a-tcp-connect-zeroes-twelve-mebibytes.mdnow says the region commits 132 KiB, its exit still owed the T14 reading.Since review r1 (all blockers answered at
4849b7b88, comment above)pmm::free_drawnover an iterator,page()answeringOption).Region::judgedtakes IPC phase 2: a connection carries whole messages #1121'sOwnedHandle, and the client's commit names the region through IPC phase 2: a connection carries whole messages #1121'sMappedend. The staleStatsize assertion left from the removedfstat.committedis gone.a_download_a_reader_keeps_up_with_grows_the_receive_ring_and_keeps_every_byte: with a millisecond round trip, [tcp] wants more buffer, netstack commits and doubles the receive ring twice, and every byte reads back in place.stream_grows(guest): 4 MiB each way between the guest and the harness, in writes from a byte to more than a ring, every byte checked in place. ToyOS has no loopback, so the harness is the peer through the boot's forwarded port, as forsocket_export. It runs innetstack_holders_exported_to_children's existing netcase boot, x86-64 q35 under QEMU, on virtio-net and e1000e, and adds about 3 s to each. Its claim is what only a guest has: netstack's own region adapter read past the ring's first size, over pages the kernel committed after both ends mapped them. QEMU's user network does not drive [tcp] to want a larger receive buffer, so that growth is the host test's.Since review r2 (at
10c835a42, evidence r4)a_send_ring_a_client_grew_without_committing_is_read_through_netstacks_own_commit. The client claims a 4 MiB send ring without committing a page and publishes 100,000 bytes. netstack's reads stay inside its own commits, and the peer hears a fresh commit's zeros. With netstack's send-ring commit removed, the test reds:a take of 1460 bytes at 4262640 past what was committed.a_joined_writer_commits_the_size_it_resumed_before_it_copies; with the check removed it reds.a_receive_ring_grown_without_netstacks_commit_is_read_through_the_clients_own(netstack's commits switched off). With the read's commit removed it reds.a_send_ring_size_netstack_cannot_commit_resets_that_stream(the harness refuses netstack's commits):Violation::Size, that stream alone. The B1 mutation reds it too.Shape::holdssays what each ring uses it as.stream_holdersandnetstack_holders_exported_to_childrenexit 0.342ecc142stand. S1 adds one comparison to a write. A connecting or accepting holder'sreachedalways covers its capacity, so no measured stream makes a new syscall. The netstack binary changes only by a comment.Metal A/B at
342ecc142(evidence r5)no netstack link holds 10.0.2.15. std'sTcpStream::local_addranswers QEMU's literal address (issues/getsockname-answers-an-address-nobody-asked-netstack-for.md, fixed by net: a stream's local address is netstack's, not QEMU's 10.0.2.15 #1147). No connect, upload or download row was measured, so the rule decides nothing for TCP.pipe.throughput.{64,256}on the T14, and 18 fs.* rows and 3 pipe rows on the AMD bench. Examples:fs.mem.fstat+7.0% on the T14 and +15.4% on the AMD bench,fs.disk.pread_4k+6.1% on the T14.Space::allows, which every user window asks first, had gained a second inventory walk (meeting, two tree descents) even for private memory.549f1c44djudges commitment inside the existingcovered_wheredescent, which now passes each entry's start.058bea2d3against head549f1c44don both benches. A fs, pipe, ipc or mem row resolving more than 2% worse blocks.pmm_accountingon both benches.Checks
At
342ecc142unless noted (round r4's are above). Logs: evidence r3.cargo run -- --ci hostcargo run -- --build-onlycargo test --test toyos-build -- netstack_holders_exported_to_children shm_commit_alone… stream_holders(atd6e62d053)… udp_receive_times_out kernel_objects_refused_alone memory_floor_holds_the_controls session_lifecycle libc_children(at929e73d6c)The host suite includes the ring's oracle across the u32 wrap with random growth, hostile size words, and the loom model
a_ring_grown_while_it_holds_bytes_reads_each_as_writtenwith its two red controls inbuild/ci.rs(transport-grow-room-now,transport-size-before-tail).shm_commit_alone(QEMU q35, a boot of its own because a child faults) shows the kernel contract:BadAddress, and nothing is lost;stream_region_refusedsends a region with nothing committed to the real netstack, which goes on serving.Negative controls (patch applied, built, run, reverted; patches and logs in the evidence):
a put of 1444 bytes at 69472 past what was committed.a write … past what was committed.stream_growsreds, its process ending mid-transfer.stream_growsstays green, which is why the host tests carry each end's.BadAddresscheck removed,shm_commit_alonereds at its assertion (evidence r2).Metal, the first A/B (run and read above). Base main
190f3b164against head342ecc142, four boots each, interleaved. The decision rule was stated in the request before any run:osbench_lan:lan.wired.tcp.connectcounts as improved only if it resolves lower. Upload, download andrtt.64count as regressed if they resolve worse by more than 5%, which blocks the PR until explained.osbench: ToyOS has no loopback, so itsnet.*rows measure nothing here.ipc.*,procs.*andmem.*rows that resolve more than 5% worse get four more boots each.pmm_accounting: the testcases boot, head only.Gaps and risks
peer_received_by_*msramp fields are where this shows.issues/a-stream-ring-never-shrinks.md). netstack still prices a place at the worst case, both rings grown whole.logs/qemu1.log). It did not recur, and its cause is not established.🤖 Generated with Claude Code