Repository navigation
Logging: records from every producer, and a kernel that waits on nobody - #527
Conversation
… a kernel that waits on nobody The userland log path, rebuilt around records instead of a blocking byte pipe. - A program's stdout and stderr are a 2 MiB shared-memory log ring init makes for it (`toyos::log::region`, `ring`): a lock-free shared ring whose writer retries only past another writer's reservation, and four single-writer lanes a thread that may not retry claims — soundd's mix thread. A write stamps time, severity, pid and thread, never waits, allocates or makes a syscall, and a full ring is a count its reader says. std's and libc's stdout and stderr assemble lines into it (`toyos::log::stdio`); a launched program's ring replaces any ring its caller passed. - The monotonic clock is a read-only page at `toyos_abi::clock::CLOCK_PAGE` in every address space; `SYS_CLOCK` is retired. A thread's id is in its TCB at `toyos_abi::TCB_TID`. - logd reads rings on a cadence and merges them with the kernel's records by write time under a round watermark; its Own/Theirs/bell/Echo machinery and soundd's `say.rs` are gone. Storage: fsync at an Alert, on a 2 s interval and at init's flush; parts preallocated and cut when finished; a retention floor keeps every boot's first part; a per-program allowance. - init sequences the stop: `power` is its port, and it has logd flush before it asks the kernel. The kernel's `wait_for_durable`, `wait_for_log_file`, `LOG_HOLDERS`, the `holds_the_log` carve-outs and `LogCursor::durable` are gone; a panic halts the other CPUs first and the black box carries the record tail at every seal. - klogd is the console's one writer: console holders' lines are queued to it, it holds the wire with interrupts on and the registers per FIFO burst. - `Severity` is an ordered ladder; `log_limited!` bounds per-thread `exit:`. Work in progress on the branch: guest tests are being moved onto it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
…achine themselves ask it A raw SYS_REBOOT waits on nobody now, so a program line still in a ring at that moment is lost; every stop the test estate makes goes through init's power port, which has logd make the log whole first. The raw right stays nameable only for quiesce_twice's second caller and for endowment_denied to narrow away. - test-runner's deadline and give-up, quiesce_last, quiesce_writers and the metal image derivation ask init; configs drop the raw right. - quiesce_twice: the second caller is refused inside the window quiesce-last-park holds the first open, before anything is stopped; the kernel says where the first call waits. - quiesce-fsync-refuse stages one named file's fsync (quiesce_fsync) instead of logd's, and the judge places the flush's close inside the stop by the stop record's own clock. - find_gap no longer hands out address space below the floor: the clock page sits under it and bounded a gap there (va_exhaustion). - A process spawned with an empty stdout slot is no longer ended at its exit: flushing a stream nothing was written to asks nothing. - logd joins a writer's unended records into one line; the console queue carries a long line's pieces as one line; a process's exit ends a line a flush opened, with a record that only closes it. - logd writes the file before it serves a reader, and a flush reads the rings only up to the moment init asked. - /log ends at init's word that the machine stops: the metal verdicts read that line, and the stop's record, census and last word off the black-box tail the next loader pass prints. - The shipped image's logd holds no netd connector, and a gate says so. - console_line_atomicity writes through its log ring and is refused the console. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
…t fill its parent's ring - A writer other than the process init started leaves the ring's last slots free (region::CHILD_KEEP), so a job flooding test-runner's ring does not take test-runner's own next lines with it; logd names the owner when it registers the ring. - log_program_flood: every line of the flood is in /log in order, or counted by logd as refused a full ring or past the allowance, and the three add up exactly; the flood's lines are wide, so the stalled reader test still has megabytes to stall on, and it waits for the kernel's record of the flood's end rather than the flood's last line. - log_program_line_after_its_records: no hold actuator; logd reads the program's ring before the kernel's records each round, and only the stamps order them. tests/logholdcase is gone. - log_program_forgery: every forged line is on the console under the runner's head, and none opens a line as the kernel's; the forger also writes a line in the console's kernel shape. - log_program_line: the line is on the console under the runner's name. - soundd_log_stall: the premise is logd finding every slot of soundd's ring waiting at the release, and the accounting is logd's counts; lane refusals are said apart from the shared ring's. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
main's one Watch per waitable object replaced the inbox sources, so the console's room is a watch klogd posts (log::console::SPACE) rather than an inbox source; the one-stage stop is a Gate as main's two-stage one became; soundd's reporting window is a Display its caller says, so the mix thread formats it on its stack as before rather than allocating the String main's flush returned; ci.rs keeps both new loom control rows. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
…g and the clock page toyos-abi retires SYS_CLOCK and gains the clock page, the thread id in the control block, FileType::SharedMemory and the severity ladder; toyos gains the log ring, the stdio sinks and the power client; toyos-window moves its pins with them. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
…d a lock the boot takes before the scheduler - A SleepLock released before sched::init posts to nobody: no task can have queued yet, and main's one-watch post needs the scheduler. The boot's console takes the wire that early. - log_program_line_after_its_records also finds the line before the kernel's record of its writer's exit. - Closed: a program printing a kernel record onto the console, a dead logd panicking every daemon, a line stamped when logd read it, two programs splicing one pipe's line, nothing bounding the log's writer below the last word, and the stop's in-flight count deciding holders at an open (no holder is left to decide). The log's three staged pieces are the track's now, with a machine that names logd's end and the control that stages a kernel writing a file. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
…ither Run under the never-waits bound, so a lane that waits for room is a red in ten seconds rather than a test that never ends. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
…om build compiles it kernel-loom compiles sleeplock.rs against its own scheduler shim, which has started from the first step of every model. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
klogd submitted each burst and then spun on the used ring with the registers lock held, so every burst held interrupts off for as long as the host took to drain the chardev. Measured on a loaded host (load average 38 on 14 cores), audio_tone and audio_tone_load under the instrumented tree saw the longest console IF-off window reach 83152 us, and audio dropped out at smp=1. The burst is now submitted under the lock, and its completion looked for under it once per spin, with interrupts on between; the panic path waits out a burst it finds in flight. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
A part created at its whole length writes a mebibyte of zeros to the stick, and written back while a tone plays it starved the client in audio_tone_load at eight CPUs: 181, 183, 185 and 189 underruns of 32 allowed across four runs at host load 19 to 35, against 0 of 32 at load 25 with the part created empty (the same tree, one line apart). The parts go back to growing as they are written; the zeros-reading helpers and the judge of a part cut to its length go with them, the deferral is item 5 of the logging track, and the stimulus is filed on its own: any program writing that much beside a tone is the same one. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
The wire is a sleep lock, and waiting for it registers klogd on the lock's watch; registered once for the thread's life on its own, a contended wire was a second registration and the kernel panicked (a task waits on at most one watch), in eight boots of the fast tier. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
The stop syncs no file still open, so a line logd wrote to /log after answering the flush was served on the network log and never reached the device: the served log carried an `exit: sshd` record /log did not (lan_swap, swap_netd, swap_crash_rolls_back, swap_not_inherited). From the answer on, the file and the served log end where the flush made the file durable, and the console still says the rest. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
…rors queue() counted a line after letting the queue go, so klogd could take the line and uncount it first: the count wrapped below zero, read as a queue full for good, logd's writes were refused for room until some other line was queued, and klogd spun on a count it could never drain. Seen in both fast tiers as 30_hanoi timing out: its burst of lines put logd and klogd on the queue at once, the runner's TEST_END reached /log at 0.902 s and the console only when the next command was typed. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
…s a request before it expires a client - The kernel took a line the console queue had no room for and held it for the holder's next write. The last line of a burst - the runner's TEST_END after 30_hanoi's 71 lines, or 75_array_in_struct_init's 72 - then waited for a write that never came: logd had nothing more to say. Now the bytes of this write's part of the line go back, and logd writes them again when the queue has room; only a whole piece of an over-long line is held, and its holder always has the rest to send. - init expired a client that had said nothing in its bound before it served the ones whose request had arrived, so a loop that came round late dropped a request already in hand: quiesce_writers' stop, asked through the power port at 2.196 s beside six fsyncing writers and a stalled stick, was dropped at 4.753 s. Requests that arrived are served first. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
…yscall Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
log_stream_stalled_reader hung on its flood: the flood ran and exited at 1.29 s and /log has the runner's ===TEST_END===, but the console never showed it. logd holds up to 1 MiB of program lines the console has not taken, and a line arriving past that was dropped. A flood fills the bound in one round, and the line after it - the runner's end marker, the one thing a watcher waits on - was the one dropped: 1677 lines unshown, the harness waiting 1415 s. Two runs hung, one of them with the ConsoleLine give-back of 5a68db7 reverted, so that change is not the cause. Now the oldest held lines go, as a console behind Linux's printk ring skips what the ring overwrote, down to half the bound at once so a flood moves the held bytes once per half rather than once per line; the first held line stays, because the console may have taken its head. Every line gone is still in /log and still counted and said. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
The test read logd's "this boot's kernel log is" only from the console before ===READY===. Both lines are program lines now, reaching the console through logd in stamp order, and logd names its file once the stick is up: with the stick mounted at 0.645 s and 0.655 s in two wide nightly runs, the runner's marker came first and the test said "logd never opened a file" of a boot that had opened one. Alone it was green (exit 0). It now waits for the line, bounded at ten seconds widened by the host. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
QEMU's virtconsole drops what a full non-blocking stdout refuses, and completes the transmit buffer as if it had been sent. log_stream_stalled_reader put a mebibyte of program lines through the console after its flood, and hung in 2 of 8 runs at a host load of 40-50: /log held the runner's TEST_END, and the console had a 555-byte fragment of a flood line where the drop began. A passing run showed a 460-byte fragment too. Filed as tooling, with the QEMU source lines and the fragments as evidence. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
lan_mdns_answer reds wide and alone in the fast tier after merging #529: the tap socket path under toyos-tmp-<pid>-0/tests-0/lane-8 is 104 bytes, which macOS sun_path cannot hold. Not this branch's diff; filed for the harness. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
|
Review of #527 at 512da5e, against Readiness: PR CI green ( Net lines ( BLOCKER
NOTE
REMOVE
SEND BACK |
`user_ptr::window` and `copy_out` translated through `AddressSpace::translate`, which answers for any present leaf, and then wrote through the direct map, where the leaf's WRITE bit does not apply. `read(fd, CLOCK_PAGE, n)` rewrote the one clock frame every address space shares; the same held on main for an `mmap(PROT_READ)` region, a program's own .text and a shared library's .text, which one cached image backs in every process that loads it. A copy into user memory now translates through `translate_writable`, which requires USER and WRITE at every level of the walk, as the MMU does for a ring 3 store under CR0.WP, and checks it at every 4 KiB page of the window, since a split window grants rights per page. Gate: `abuse_readonly_copyout`: read and fstat into a read-only mmap, into its own .text and into the clock page, each compared byte for byte before its return value is read, and a second process still reads the clock's magic. Negative control, the whole fix reverted as one patch and the tree rebuilt: "read wrote into a read-only mmap", exit 1. At the fix: exit 0. With the arms in their first order the reverted tree stalled the guest instead, because a rewritten clock page asserts in every stamp, the verdict's own included. Filed issues/isolation/a-syscall-writes-through-a-read-only-user-mapping.md. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
A record's at_ns is the writer's word. One stamped u64::MAX sorted after every round's cut, so it sat in `waiting`, re-sorted every round, until the stop's flush wrote everything held; a program stamping every line that way kept all of them out of /log and grew logd's heap by its whole allowance. `origin::clamp_ahead` reads every stamp past the moment of reading plus STAMP_SLACK_NS (1 ms, the counters' cross-CPU skew with room to spare) as that moment, in `read` and in `sweep`, and logd says how many it did per program. A program line can no longer be held past the round after it was read. Gate: `log_program_forgery`, whose forger now pushes a record stamped u64::MAX straight into its ring: the line must be in /log before init's STOPPING line, and logd must say it read a stamp ahead of the clock. Host: `a_stamp_ahead_of_the_clock_is_read_as_now`. Negative control, the logd change reverted whole and rebuilt: "is after "init: power: the machine stops..." in /log: logd held it until the machine stopped", exit 1. At the fix: exit 0; logd host suite exit 0. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
logd set `stopping` on init's FLUSH and never cleared it, so a stop the kernel refused - which init survives and reports - left /log unwritten for the rest of the boot, silently. init now sends logd RESUME (toyos-logstream, frame 7) when the kernel refuses the stop. logd holds the file's text back from the flush on instead of dropping it, and on RESUME writes what it held, says the stop was refused, and writes the file again. The refusal is staged by a new actuator, `power-refused-once`: the first SYS_SHUTDOWN or SYS_REBOOT answers NotSupported before anything is torn down. Gate: `log_after_a_refused_stop` - a job asks init for a reboot on the armed kernel, is refused NotSupported, says a line; /log must carry it after the stop line, with logd's word. Negative control, the init/logd/logstream change reverted whole and rebuilt (actuator and test kept): "/log carries no "log refused stop: said after the refusal"", exit 1. At the fix: exit 0. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
`TCB_TID` was 16 on both architectures. Under the AArch64 psABI (TLS variant I) the TCB is the two words at TP - the DTV pointer and a word left to the implementation - and the first TLS block starts at TP + 16, so the aarch64 `current_tid` read the program's own TLS data as its tid, and a kernel writing it there would have written into that data. `toyos_abi::tcb` now states each architecture's layout: x86-64 (variant II) keeps the tid at TP + 16 inside the 64-byte TCB the kernel reserves; AArch64 (variant I) at TP + 8, the implementation's word. `TCB_TID` is the target's. PR #524's worktree has no variant I loader yet (its loader/tls.rs is still variant II); the track issues/kernel/toyos-runs-on-arm64.md names variant I with TLSDESC, which this layout is. Gate: toyos-abi `the_tid_word_is_inside_each_tcb_and_on_no_named_word`, checked on every host whatever its architecture. Mutation AARCH64_TID = 16: exit 101. At head: toyos-abi host suite exit 0. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
… device `Virtqueue::submit_and_wait`, virtio_console's `tx_slot` and `write_burst`'s poll were three copies of poll-until-5-s-then-panic. They are now one: `virtio::wait_used(queue, look)`, which spins on a caller's look - the burst wait takes the backend lock inside it, one look at a time - and panics past ANSWERS. `Answers` is deleted. Past the bound the wait looks once more before it panics. klogd's burst wait is preemptible, so a waiter off its CPU for five seconds used to find the bound spent and panic the kernel over a device that had finished long before. Gate: `virtio_used_ring` now also requires "virtio: wait selftest 2/2" from `wait_selftest`, which drives the wait on a clock already past its bound: a completion the next look finds is taken, and a device that never answers is not. Mutation, the look after the bound replaced by `return None`: "wait selftest FAILED on a completion found after the bound", exit 1. At head: exit 0. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
… times wider - logd: with every round synced, the stop's flush synced each of its rounds too, and a flush of a full ring outran init's 5 s bound, so the reboot cut /log short: `log_ring_keeps_the_owners_slots` found 1022 flood lines and no TEST_END. A flush's rounds are written and made durable together by `flushed`, once, before init is told; every other round is still synced before the next. `log_ring_keeps_the_owners_slots` green after, exit 0. - log_stream_stalled_reader: its readers are owed only what logd took of the flood, and with a sync per round logd takes less of it before the ring refuses the rest; 2 of 3 runs let no reader go (0 of 8 in 32 s), because what they were owed fit in the buffers between logd and them. The flood job now takes an optional line count and this test asks for four times the ordinary one; `log_program_flood` still floods 16 384. Five runs after: 5 green, exit 0 each, 2748-3237 flood lines to the reader after. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
main landed #528 (iced, `toyos::wake`) and #530 (self-update), which took toyos-abi 0.15.0, toyos 0.17.0 and toyos-window 0.18.0. - SDK versions: past main's, to toyos-abi 0.16.0, toyos 0.18.0 and toyos-window 0.19.0; every tracked lockfile re-locked in its own workspace with `cargo update -w`, tests/iced-counter's among them. - toyos/src/lib.rs: both `power` (this branch) and `wake` (main). - userland/logd/src/main.rs: main's hunks move logd's `Own`/`Theirs` bell onto `toyos::wake`; this branch deleted that machinery (logd's own lines are records in its ring), so they have nothing to land on and this side is kept. `toyos::wake` stays for its other users. - userland/soundd/src/say.rs: deleted here; main's hunk moves its wake pipe onto `toyos::wake`, and soundd's mix thread writes a lane here instead. - rust: main's pin is still 80ea645f83b, which this branch's pin already merges. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
This branch filed it as a-segment-socket-path-outgrows-sun-len-in-the-per-run-scratch; main carries it as a-lane-s-tap-socket-path-is-past-sun-len-on-the-dev-host and a-lane-s-tap-socket-path-outgrows-sun-len-on-the-dev-host, so this copy goes. Nothing cites it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
|
Hand-back for the review at 512da5e. Head 348b497; the body is current. Every exit below is the command's own. BLOCKERs
NOTEs: Gates
Audio A/B, every bootTables are in the body; here every boot. run 1 — per round 1..40: gaps in the first capture / soundd underruns (atl only) / the 1-min load before the run run 2 — per round 1..40: gaps in the first capture / soundd underruns (atl only) / the 1-min load before the run |
|
Review of #527, round 2, at 348b497 against Readiness: CI at 348b497 has Net lines (
Round-1 BLOCKERs
BLOCKER
NOTE
REMOVE
SEND BACK |
… flood that ends at the let-go - user_ptr: `copy_in` and `copy_out` translated under the address-space lock, dropped it, then touched the frame through the direct map unpinned, so a sibling's munmap could free and reissue it in between. Both now go through `window`, which pins under the lock; `translate` is gone and `object` is the `is_user_object` bound over a pinned window. `window`'s contiguity and rights walk is `toyos_userbound::contiguous`, pure, with a host test that puts a read-only page between two writable ones (the per-4 KiB write step). `copy-meets-a-remap` holds a marked typed copy between its translation and its store until its process maps again; `user_copy_races_munmap` stages a sibling's munmap and mmap there and asserts the fresh mapping is untouched. - logd reads init's frames up to a flush and runs the flush before reading on, so a RESUME queued behind a FLUSH it has not run answers that flush instead of panicking. `--hold-flush` holds the first flush until init speaks again; `tests/logflushcase` and `log_resume_meets_its_flush` stage the together-arrival. init's `resume` survives a gone logd as `flush` does, and counts the flushes it waited out so a late FLUSHED is not read as the next stop's answer. logd's post-flush hold-back is bounded (1 MiB), the rest counted and said at a resume. - log_stream_stalled_reader floods until every stalled reader is let go, bounded by the flood's ceiling, instead of one fixed flood; log_flood's line-count argument goes with the 4x flood. - log_ring_keeps_the_owners_slots fails with its own verdict when the stop's flush was waited out. - virtio-console: `write_burst` rings the TX doorbell after dropping `BackendGuard` (`Virtqueue::publish` + `Doorbell`); the panic path rings again before it waits a burst out. - logd's header loses the unestablished sentence about batched syncs. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
… unbuffered console goes - log_ring_keeps_the_owners_slots fails with its own verdict when the stop's flush was waited out: init's warning on the console, or the kernel's `Syncing filesystems...` record FLUSH_BOUND or more after init's stop line in /log. init's warning is in its ring when quiesce stops logd, so the console alone rarely carries it; the two records share one clock. - `console-unbuffered` and `serial::write_console` are deleted: no test arms the actuator since the console's only writer became logd. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
Conflicts, each resolved onto main's layout with this branch's change kept: - rust: pinned at 64a2050c484, this branch's 23783dc3524 merged with main's c34ecdf0ab6 in the fork (`wt-toyos-logtrack`), and merged into `toyos-inbox` (645d1c70732). `git merge-base --is-ancestor` gives 0 for both pins against it and for it against `origin/toyos-inbox`. Its `compiler/` is c34ecdf0ab6's byte for byte; the inbox head 26f662d303a was not taken because it also carries x86-64's switch to rust-lld, another branch's compiler change. - panic.rs: main moved `halt_all_cpus` here from `apic.rs`; this branch's version stops the other CPUs first and has no wait for `/log`, so `wait_for_log_file`, `LOG_FILE_DRAIN` and `LOG_DRAIN_EXPIRED` go from panic.rs as they went from apic.rs, and `tests/common/power.rs`'s check for that string, which the kernel no longer prints, goes with them. - serial.rs: this branch's two locks and burst writers over main's `arch::console_uart`, `serial_lock::BackendLock` and `IrqGuard`. The burst is `console_uart::TX_BURST`: 16 on the 16550, whose THRE means an empty FIFO, and 1 on the PL011, whose TXFF clear promises one byte. - clock.rs: the clock page is published from main's `set_counter`. - paging.rs: `translate_writable` on main's x86-64 `AddressSpace`, and an uninhabited stub on AArch64's. - virtio.rs: `publish` and `Doorbell` on main's barriers: the doorbell is an `Mmio` write, which orders the idx before it. - loader/tls.rs: main lays out variant I (TP+0 the DTV pointer, TP+8 zeroed); `toyos_abi::TCB_TID` is TP+8 there, so the tid write does not touch the DTV pointer. - toyos-window goes to 0.20.0, past main's 0.19.0; toyos-abi 0.16.0 and toyos 0.18.0 are already past main's. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
`log_ring_keeps_the_owners_slots`'s flush verdict needed init's stop line in /log to time the wait, and a flush that was never answered can leave that line unwritten: `logd` held at the flush reads no ring. An answered flush always wrote it, since the line is stamped before init asks for the flush, so its absence is the flush verdict too. Measured with `logd --hold-flush` in `tests/logkeepcase`: the previous head answered "/log carries no stop line with a time". Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
With the flush verdict of the owner's-slots test in place, three runs at 7544e7a went red on it wide (the kernel synced 6118, 6088 and 6185 ms after init's stop line, against init's 5000 ms bound) and green alone at 3184 and 2995 ms: the stop's flush of a full ring of 960-byte lines, about 1.8 MB, sits at init's bound on this host. A ring fills by its slots, not its bytes, and the width was there to give the stalled reader megabytes to stall on, which it no longer needs now that it floods until every reader is let go. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
…ate rules
`main`'s `every_architecture_rule_holds` (the ARM port's `ARCH_RULES`) puts
every `asm!`, `core::arch` intrinsic and `target_arch` in an architecture's own
module. This branch's clock-page counter read (`clock.rs`) and TCB tid read
(`lib.rs`) were outside one, so `--ci host` went red on them after the merge.
They move, unchanged, to `toyos-abi/src/arch/{x86_64,aarch64}.rs` behind one
selector, which the two rules name as a place. The three guest probes this
branch added with a raw `syscall` or `hlt` (`abuse_readonly_copyout`,
`copy_out_races_munmap`, `panic_halts_first`) join the declared exceptions
that cite `issues/build/assembly-outside-an-arch-module-in-userland-and-guest-probes.md`,
whose count goes to twenty.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
…e last word Seen once in a fast tier run used as load beside the stalled-reader loop at 618e68e (quiesce_stops_the_machine, three quiesce-writer lines after `Rebooting.`, green alone). Filed with the mechanism read off `log/console.rs`: `klogd` drains records before queued lines whatever their age, and nothing drains the queue between the stop of every holder and the last word. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
|
Hand-back for the round-2 review at 348b497. Head a8c37cf, which differs from 618e68e only under BLOCKERs
B11 found a real flake: with the flush verdict in place, 3 of 3 runs at 7544e7a were red wide on it (6118, 6088, 6185 ms from init's stop line to the kernel's sync, against init's 5000 ms bound) and green alone (3184, 2995 ms). A full ring of 960-byte flood lines was about 1.8 MB to flush. The width existed for the stalled reader, which no longer needs it; at 64 bytes the same test syncs 185–203 ms after the stop line. NOTEs
The merge and the pin
Gates
The interrupts-off sampler, as rundiff --git a/kernel/src/drivers/serial.rs b/kernel/src/drivers/serial.rs
index d2489db3..45f9b87e 100644
--- a/kernel/src/drivers/serial.rs
+++ b/kernel/src/drivers/serial.rs
@@ -98,7 +98,7 @@ pub struct BackendGuard {
/// This CPU's own `RFLAGS`, captured by `pushfq`; the only value `popfq` may be given.
/// Not `Copy`/`Clone`: one CPU's state at one instant, not to be duplicated.
-pub struct SavedFlags(u64);
+pub struct SavedFlags(u64, u64, &'static core::panic::Location<'static>);
impl SavedFlags {
/// Restores the flags; `&self` because `Drop` cannot move a field out, and restoring twice is idempotent.
@@ -114,10 +114,12 @@ impl SavedFlags {
options(nomem),
);
}
+ crate::irqoff::closed(self.1, self.2, self.0);
}
}
impl BackendGuard {
+ #[track_caller]
pub fn lock() -> Self {
let rflags = save_and_cli();
while BACKEND_LOCKED
@@ -132,6 +134,7 @@ impl BackendGuard {
}
/// Non-blocking acquire: `None` if another CPU already holds the backend.
+ #[track_caller]
pub fn try_lock() -> Option<Self> {
let rflags = save_and_cli();
if BACKEND_LOCKED
@@ -183,6 +186,7 @@ impl Drop for BackendGuard {
/// This CPU's `RFLAGS`, captured with interrupts off in one instruction sequence:
/// the value is stale if anything runs between the read and `cli`.
#[inline]
+#[track_caller]
fn save_and_cli() -> SavedFlags {
let rflags: u64;
// SAFETY: irreducible — `pushfq`/`cli` have no safe spelling; the asm reads
@@ -196,7 +200,7 @@ fn save_and_cli() -> SavedFlags {
options(nomem),
);
}
- SavedFlags(rflags)
+ SavedFlags(rflags, crate::irqoff::now(), core::panic::Location::caller())
}
pub fn has_data() -> bool {
diff --git a/kernel/src/hw.rs b/kernel/src/hw.rs
index 21fd5c78..4f03eef5 100644
--- a/kernel/src/hw.rs
+++ b/kernel/src/hw.rs
@@ -30,9 +30,12 @@ pub fn now_ns() -> u64 {
#[must_use = "the interrupt gate closes when the guard drops"]
pub struct IrqGuard {
rflags: u64,
+ at: u64,
+ site: &'static core::panic::Location<'static>,
}
impl IrqGuard {
+ #[track_caller]
pub fn close() -> Self {
let rflags: u64;
// SAFETY: touches only RFLAGS and one pushed-and-popped stack slot; `cli` cannot fail in
@@ -41,7 +44,7 @@ impl IrqGuard {
unsafe {
asm!("pushfq", "pop {}", "cli", out(reg) rflags, options(nomem));
}
- Self { rflags }
+ Self { rflags, at: crate::irqoff::now(), site: core::panic::Location::caller() }
}
}
@@ -52,6 +55,7 @@ impl Drop for IrqGuard {
unsafe {
asm!("push {}", "popfq", in(reg) self.rflags, options(nomem));
}
+ crate::irqoff::closed(self.at, self.site, self.rflags);
}
}
@@ -79,6 +83,7 @@ impl Machine for KernelHw {
apic::stop_timer();
}
+ #[track_caller]
fn irq_guard(&self) -> IrqGuard {
IrqGuard::close()
}
diff --git a/kernel/src/irqoff.rs b/kernel/src/irqoff.rs
new file mode 100644
index 00000000..5584093d
--- /dev/null
+++ b/kernel/src/irqoff.rs
@@ -0,0 +1,48 @@
+//! MEASUREMENT ONLY, never committed: the longest window an `IrqGuard` or a
+//! `BackendGuard` held interrupts off, from an outermost close to its restore,
+//! said with the site that opened it each time a longer one is seen.
+
+use core::panic::Location;
+use core::sync::atomic::{AtomicBool, AtomicU64, Ordering};
+
+static MAX_NS: AtomicU64 = AtomicU64::new(0);
+static REPORTING: AtomicBool = AtomicBool::new(false);
+const FLOOR_NS: u64 = 100_000;
+
+pub fn now() -> u64 {
+ crate::clock::nanos_since_boot()
+}
+
+pub fn closed(at: u64, site: &'static Location<'static>, rflags: u64) {
+ // After the boot's console is virtio's: the UART phase before it is the
+ // boot's, not what a running machine's audio meets.
+ if rflags & 0x200 == 0
+ || at == 0
+ || !crate::log::PERCPU_READY.load(Ordering::Relaxed)
+ || !crate::drivers::virtio_console::is_ready()
+ {
+ return;
+ }
+ let took = now().saturating_sub(at);
+ let mut max = MAX_NS.load(Ordering::Relaxed);
+ loop {
+ if took <= max || took < FLOOR_NS {
+ return;
+ }
+ match MAX_NS.compare_exchange_weak(max, took, Ordering::Relaxed, Ordering::Relaxed) {
+ Ok(_) => break,
+ Err(seen) => max = seen,
+ }
+ }
+ if REPORTING.swap(true, Ordering::Acquire) {
+ return;
+ }
+ crate::log!(
+ "irqoff: {} us at {}:{} on cpu{}",
+ took / 1000,
+ site.file(),
+ site.line(),
+ crate::arch::percpu::cpu_id()
+ );
+ REPORTING.store(false, Ordering::Release);
+}
diff --git a/kernel/src/main.rs b/kernel/src/main.rs
index 360ba473..988c31ca 100644
--- a/kernel/src/main.rs
+++ b/kernel/src/main.rs
@@ -81,6 +81,7 @@ mod irq_census;
mod irq_ring;
mod trace;
mod time;
+mod irqoff;
mod clock;
mod rtc;
|
|
Round-2 BLOCKERs closed and judged at a8c37cf: the user-copy race closed by routing copy_in/copy_out through window (user_copy_races_munmap red on the unpinned path), logd survives FLUSH and RESUME together (log_resume_meets_its_flush), the stalled-reader test floods until let-go (20/20 beside the fast tier at loads to 96), and the ring-slot verdict is separated from a waited-out flush (both arms at one head). T14 run 150 (orchestrator) at this head: metal_device_probe PASS, no panic, log partition passes toyos-fat32-check, kernel to Boot: complete 1225 ms. The console-after-last-word defect this branch introduces is filed with its mechanism. Landing. |
…-lld switch The rust submodule moves to fbf6ad143d8, a fork merge of this branch's 209f10ded88 (every ToyOS target links with rust-lld and -Bsymbolic) and main's 64a2050c484 (#527's stream sinks and clock page); it merged without conflict and is on toyos-inbox through 56f00d51770. Every other overlapping file merged hunk by hunk with no textual conflict: the two sides touch disjoint regions of src/build.rs, src/release.rs, src/sourcegate.rs, kernel/Cargo.toml, kernel/src/loader/tls.rs, fpu_isolation.rs and the two issue files. No published SDK crate changes on this branch, so no version moves; every tracked lockfile resolves --locked. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
…he last word Since #527, a console holder's line (every program's, through logd) waits in `log::console`'s queue for `klogd`. `klogd` takes a chunk of records and then a chunk of the queue per hold of the wire. The stop never drained that queue. A line queued just before the stop was written either after `Rebooting.` by the power-off's `flush_final`, or not at all if the reset came first. This is issues/kernel/a-holders-queued-line-can-reach-the-console-after-the-last-word.md. It is also the shape of the nightly's `metal_job_reboot` and `quiesce_wakes_on_the_last_exit` reds: `===READY===` never reached, or a drain after `===READY===` with no kernel line in it, because the job's own kernel lines had gone out ahead of the queued marker. `quiesce()` now drains every record and queued line under the wire right after `quiesce::stop()` has stopped every holder, before `Syncing filesystems...`. `console-queue-at-the-stop` is the deterministic stimulus. It queues one line once every holder is stopped, and keeps `klogd` off the queue from the stop's claim on. `quiesce_stops_the_machine` arms it and judges that line above the last word. Measured: - negative control, the drain disabled as a checked patch that builds (`if false { ... }`): `cargo test --test toyos-build -- --nightly quiesce_stops_the_machine` EXIT=1, `1 line(s) reached the console after the boot's last word: console: a holder's line, queued once the stop had stopped every holder`, wide and alone. - with the drain: EXIT=0. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
Both were red on main's nightly at 1ce7183 (run 36290616312), wide and alone. Each judge's premise was the log before #527. - `usb_reset_hands_devices_back`: `nothing_after_the_last_word` took the text's last `Rebooting.`. Since #527 the next loader pass prints the boot's newest records under `log-tail:`, newest first. The last `Rebooting.` in the text is that copy, and the older records under it read as spawns after the last word ("3 of 4 reset path(s) unmet: a process started after "Rebooting."... "| log-tail: ... spawn: ..."). The judge now takes the last `Rebooting.` that is not a `log-tail:` line, and reads to the next loader pass as before. - `usb_flush_optional`: it wanted `Shutting down.` in `/log`. Since #527 `/log` ends at init's stop line, because the stop stops logd with every other thread, so the kernel's last word is on the console alone. The judge now wants init's stop line (`bootlog::stopping_line`). Measured, `cargo test --test toyos-build -- --nightly <name>`: - `usb_reset_hands_devices_back`: the old judge restored as a checked patch EXIT=1 (`3 of 4 reset path(s) unmet`), the new one EXIT=0. - `usb_flush_optional`: before EXIT=1 (`the shutdown's last line never reached the file`, wide and alone), after EXIT=0. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
Four update tests stalled on main's nightly at 1ce7183 (run 36290616312), wide and alone: `update_boots_the_new_kernel`, `update_falls_back_from_a_dying_kernel`, `update_floor_is_the_images_own` and `update_refusals_boot_the_other_slot`. #527 moved every stop onto init's `power` port, so the `reboot` applet (`toyos::power::stop`) asks init, which has logd make the log whole and then stops the machine. `tests/updatecase/system.toml` still gave toybox `syscap = ["power"]` and no `power` connector. The host's `reboot` over ssh was "accepted" and the machine never went down. toybox now receives `power`, as it does in `system.toml`. The `SysCap` power right goes with the change, because no applet in this image uses it any more. Measured: `cargo test --test toyos-build -- --nightly update_boots_the_new_kernel` before, EXIT=1 (STALLED after the reboot, wide and alone). After, `--nightly update_` EXIT=0 (7 of 7). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
… not fix Run 36290616312, read after #527: - gate A's `audio_tone_load.smp1` median against the dev host's TCG sample: a new sighting added to issues/audio/gate-a-has-no-runner-baseline.md. It is the instrument. Harm was null, and the same lane passed on this branch's nightly and on the one before #527. - `wake_storm_cost`: a third sighting added to its issue, green alone. - `swap_crash_rolls_back` and `i8042_health_cadence`: each red once wide and green alone twice, filed as findings. - `log_ring_keeps_the_owners_slots`, seen on the dev host: a ring's owner is named only when logd reads init's registration, so a child that floods first takes the owner's slots. Filed with the mechanism and the rates. The closing fix is in `toyos/src`. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
…, #531, #534) into the Netstack3 netd Two conflicts. tests/common/origin.rs: main added `volumes` to the imports this branch had split to name `segment`'s NEIGHBOUR and NEIGHBOUR_MAC; both kept. userland/Cargo.lock: main's lock taken whole and re-locked against this branch's manifests (`cargo metadata`), which adds the mirrored crates and their dependencies and drops smoltcp and its defmt. The rust gitlink fast-forwarded to main's fbf6ad143d8; this branch's pin (7a809b7591f) is its ancestor, and the branch made no fork commit of its own. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
hold_source computed one Result per arm and called withhold from four sites, so the only tested arm was Ok(None); the other three could drop their withhold silently and nothing would notice. Fold the found partition and its view into one Result<(found, view), &'static str> keyed on why it failed, and call withhold once from the single Err arm the existing test already exercises. The log text is unchanged. Also: drop the false and the branch-scoped lines from the console queue issue (main has had the queue since #527, and the branch name rots at the merge), and align the `put` row in toyos_ssh's usage table with its neighbours. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
Userland logging, rebuilt: every program writes into a log ring of its own, and the kernel waits on no userland program to log, panic or stop. This covers changes 1–4 of the logging roast and its small defects. Change 5, the kernel's own ring rebuilt printk-style, is item 1 of the track
issues/kernel/logging-records-from-every-producer-and-a-kernel-that-waits-on-nobody.md.What changed, per decision
1. A log ring per program, written without a syscall (
toyos/src/log/)logdin an IPC frame. The frame carries the program's name, so the identity on a line is the one init stamped, not one the program claims.logdsays the count. The time, severity, pid and tid are stamped when the record is written. Astdout/stderrwrite takes the stream's partial-line spinlock (Held); that is filed, below.stdoutandstderrgo into the ring asFLAG_UNENDED/FLAG_CLOSESrecords.logdjoins them into lines per (pid, tid), up to 64 KiB and 16 joins.CHILD_KEEP= 64 shared slots stay free for the ring's owner (Ring::own, set bylogd), so a flooding child does not take its parent's next lines. The owner word and a record's pid are memory the child can write, so this holds against a child that floods, not one that forges.say.rs, its log thread, logd's queues and thesay!workarounds are deleted. soundd's mix thread writes its own lane.logdreads one more than 1 ms past the moment it read it as that moment, and says how many it did (origin::clamp_ahead). A stamp in the future no longer holds its line out of every round.2. The clock is a page (
toyos-abi/src/clock.rs): a record is stamped without a syscall, the counter read behindlfence(x86-64) orisb(AArch64). The ABI changed, so the SDK versions go pastmain's: toyos-abi 0.16.0, toyos 0.18.0, toyos-window 0.20.0.clock::set_counter, which each architecture's boot calls once it has the counter's period.toyos_abi::tcb): x86-64 (TLS variant II) at TP+16, AArch64 (variant I) at TP+8, the word the psABI leaves to the implementation. The loadermainnow carries lays variant I out as TP+0 the DTV pointer and TP+8 zeroed (loader/tls.rs), soprocess::spawn_thread's tid store atTP + TCB_TIDlands on that zeroed word and not on the DTV pointer. TP+16 on AArch64 is the first TLS block, so the layout is a compile-time assertion.std's side is the fork, pinned at64a2050c484: this branch's23783dc3524merged withmain'sc34ecdf0ab6, on the fork'swt-toyos-logtrackand merged intotoyos-inbox(645d1c70732). Itscompiler/isc34ecdf0ab6's byte for byte.3. A kernel that waits on nobody
panic::halt_all_cpus, then seals its tail in the black box; the next loader pass copies that tail onto its page.powerport (toyos::power::stop): init haslogdflush, then stops the machine.logdwrites a flush's rounds and makes them durable once, then holds lines back from the file, up to 1 MiB (STOPPING_BYTES), counting and saying what is past it;/logends at init's STOPPING line.RESUMEframe (toyos-logstream):logdwrites what it held and says the stop was refused.logdreads init's frames up to a flush and runs the flush before it reads on, so aRESUMEqueued behind aFLUSHit has not run (init waited the flush out, then the stop was refused) answers that flush instead of finding no stop to resume. init'sresumesurvives a gonelogdas itsflushdoes, and init counts the flushes it waited out, so a lateFLUSHEDis not taken for the next stop's answer.wait_for_durable,wait_for_log_fileand its budget,LOG_HOLDERS,holds_the_log, and thedurablefield. Quiesce is one stage behind aGate.4. One console writer, with interrupts on
klogdwrites the console. A console holder's write goes to a 64-line queue and never touches the device; the queue's length is read under its lock.Virtqueue::publish,Doorbell), because QEMU runs the device's output into its chardev inside that MMIO write. The panic path rings again before it waits a burst out, since a CPU stopped between publishing and ringing never rings. Every wait on a used ring is one function,virtio::wait_used; past its 5 s bound it looks once more before it panics.console_uart::TX_BURST): 16 on the 16550, 1 on the PL011.logd, under the program's name. A child given a console is refused a write (PermissionDenied). Theconsole-unbufferedactuator andserial::write_console, which no test armed any more, are deleted.logdholds up to 1 MiB of lines the console has not taken; past that the oldest are dropped, counted, and still in/log.5. logd's duties
main'slogddid. A sync the kernel declines to start is owed and asked again each round.no_shipped_image_serves_the_log_on_the_network, over every non-test configALL_CONFIGSwalks).6. Defects fixed along the way, on
maintooAddressSpace::translate_writable, USER and WRITE at every level of the walk. Filed asissues/isolation/a-syscall-writes-through-a-read-only-user-mapping.md.copy_inandcopy_outtranslated under the address-space lock, let it go, then read or wrote the frame through the direct map unpinned, so a sibling thread'smunmapcould free it and the PMM reissue it in between. Both now go throughuser_ptr::window, which pins the frames under the lock for the copy's life; the unpinnedtranslateis deleted andobjectisis_user_object's bound over a pinned window.window's walk istoyos_userbound::contiguous, pure and host-tested: a write is asked at every 4 KiB page, a read at every 2 MiB page.find_gapis bounded by its floor; aSleepLocktaken before the scheduler starts does not post; init serves the requests that arrived before it expires a silent client; a/logpart is not preallocated.7. Tests
user_copy_races_munmap(new, fast):copy-meets-a-remapholds a typed copy whose destination carries a mark between its translation and its store, until its own process has mapped memory again; the program unmaps the destination on the kernel's cue and maps a fresh region, and asserts the region is still all zeroes.log_resume_meets_its_flush(new, fast):tests/logflushcasehaslogdhold its first flush until init speaks again (--hold-flush), so init waits the flush out, the kernel refuses the stop, and the resume meets the flush unrun.logdmust live and the job's line after the refusal must be in/log.log_stream_stalled_readerfloods until every stalled reader is let go, bounded by the flood's ceiling, rather than by one fixed flood;log_flood's line-count argument goes.log_ring_keeps_the_owners_slotsfails with its own verdict when the stop's flush was not answered: init's warning on the console, init's stop line missing from/log, or the kernel'sSyncing filesystems...recordFLUSH_BOUNDor more after that line.Filed: the track;
issues/design-debt/a-rust-program-holds-two-copies-of-its-stream-state.md;issues/design-debt/a-programs-two-streams-map-its-one-ring-twice.md;issues/design-debt/a-stream-write-spins-on-a-lock-its-own-panic-can-hold.md;issues/design-debt/a-console-holders-line-state-guards-writers-that-no-longer-exist.md;issues/audio/a-megabyte-written-to-the-stick-starves-a-tone-beside-it.md;issues/build/qemu-drops-console-output-the-harness-is-slow-to-read.md.Filed this round:
issues/kernel/a-holders-queued-line-can-reach-the-console-after-the-last-word.md, a red seen once under load and not fixed here.Gates
On the dev host, which other worktrees load; every exit is the command's own. The head, a8c37cf, differs from 618e68e only under
issues/.cargo run -- --ci hostcargo run -- --ci abi-splitlog_ring(--features loom --test log_ring --release)cargo test --test toyos-build(fast tier)lan_mdns_answer: a tap socket path pastSUN_LEN;main5e446e5 in the same session, exit 1 on the same message (issues/build/a-lane-s-tap-socket-path-is-past-sun-len-on-the-dev-host.md). Run beside the stalled-reader loop below.lan_mdns_answeras above;quiesce_stops_the_machine,launcher_refusalsandport_poll_churnred wide and green alone, at 1-minute loads up to 96. The first is filed with its mechanism (above); the other two were not investigated.--nightly log_stream_stalled_reader, 20 runs beside the fast tier--nightly log_--nightly console_line_atomicityuser_copy_races_munmap,log_resume_meets_its_flush,log_after_a_refused_stop,abuse_readonly_copyout,munmap_reissues_read_window,panic_halts_the_others_first,virtio_used_ringlog_ring_keeps_the_owners_slots, 5 runsNegative controls
Each a checked patch: applied, the tree shown to build, run, restored, at the head its green arm ran on.
objectput back on the unpinnedtranslate_user, the copy's frame not pinned (the code before this round)user_copy_races_munmap: "a copy held across a sibling's munmap stored into the region mapped after it: byte 0 is 0x1", wide and alonefrom_initand init back to 348b497 (actuator, config and test kept)log_resume_meets_its_flush: "logd ended before the machine did: exit: logd pid=4 code=101", wide and alonelet keep = { let _ = owner; 0 };inRing::pushlog_ring_keeps_the_owners_slots: "/log carries no "===TEST_END test_rs_log_flood exit=0===": the child's flood took the slots its parent's line needed (1917 flood lines in /log)", wide and alonelogd --hold-flushintests/logkeepcase: a flush init waits outlog_ring_keeps_the_owners_slots: "the stop went ahead without the flush's answer (init's stop line never reached /log)", wide and aloneAccess::Write => PAGE_2Mintoyos_userbound::contiguousa_write_across_a_read_only_page_between_writable_ones_is_refused, 18 of 19translate_writableand theAccessthreading reverted whole (the treemainhad)abuse_readonly_copyout: "read wrote into a read-only mmap"log_program_forgery: theu64::MAXline "is after init's stop line in /log"RESUMEand logd's hold/resume reverted (actuator and test kept)log_after_a_refused_stop: "/log carries no "log refused stop: said after the refusal""AARCH64_TID = 16wait_until's look past the bound replaced byNonevirtio_used_ring: "wait selftest FAILED on a completion found after the bound"kick_all_but_self()+ 500 ms spin before the halt IPIpanic_halts_the_others_first: "cpu2 made a record 500 ms after the fatal one on cpu1"Relaxed(log-ring-tail-relaxed)--ci hoststep greentail/headloads swapped (log-ring-loads-swapped)--ci hoststep greenThe first five pairs ran their green arm at the same head as the red (Gates). The "round 1" rows are that round's measurements, at heads before 348b497, not repeated.
Independent oracles
mainbuilt from the merge-base in the same session.hw/char/virtio-console.c(flush_buf, v11.1.0), which drops on EAGAIN only for a console port.Measurements
Audio A/B (the round-1 review's instrument):
--nightly hda_tone(its own smp 2) and--nightly audio_tone_load(smp 1 and 8), 40 rounds per arm interleaved, 4 spinning host threads as the fixed load, the 1-minute load average recorded before every run. A boot is gapped when its first capture has a mid-tone silence; the harness's confirming re-boot is not counted.The arms, with the fork commit each tree pins. The fork checkout each arm's worktree actually held at build time was not recorded, and arm A's worktree is gone, so that the build used the pinned
stdis not established.rustit pinsmain, the merge-base)main, the merge-base)Run 1:
B is above A (one-sided Fisher, pooled 4/120 against 0/120: p = 0.062). B against C is p ≈ 0.19, and C gapped in round 7, where two of B's four were, so the sync cadence is not established as the cause. logd syncs every round since 6f35787.
Run 2, confirming, the final logd:
Every run's arm, round, exit and load, and every boot's gaps and underruns, are in the round-1 hand-back comment.
Interrupts off. The sampler is a measurement patch, never committed, applied identically to both arms; it is posted in the round-2 hand-back comment and applies to both 17eb66a and 11bb949 (
git apply --check, 0 each).hw::IrqGuardandserial::BackendGuardrecord, per outermost window once virtio-console is ready, the time from the close to the restore and the#[track_caller]site that opened it, and log each new longest; commandcargo test --test toyos-build -- --nightly --show-output audio_tone_load, 12 rounds per arm interleaved, the longest window per boot (confirming re-boots included). A =main17eb66a, B = 11bb949.log/console.rs:89(drain_inline, a chunk of records under the backend lock)virtio_console.rs:133(a burst's submit),:151(one look at its completion)log/console.rs:89virtio_console.rs:133,:151The 10.8 ms window of the old table did not recur in 16 smp-8 boots. The
usb-reset-movescue is armed in none of these boots and never appears.:133's bracket held the MMIO notify, which QEMU serves synchronously in the vCPU thread by running virtio-console's output into its chardev: emulated device work, which this branch now does after the bracket (§4). The sampler was not re-run after that change.:151is one used-ring load, which only vCPU descheduling or an NMI can stretch.Net lines (
git diff --numstat origin/main...HEADat a8c37cf, 186 files; production is everything outsidetests/,toyos/src/log/proof.rs,src/,issues/and lockfiles, inline#[cfg(test)]modules included): production +4961 −2075 = +2886; tests +1986 −852 = +1134;src/+105; issues +41; lockfiles +11. The same count at 348b497 gives production +2648 (the round-2 review, counting its own way, gave +2433). This round deleted the unpinnedtranslate, theconsole-unbufferedactuator andserial::write_console,log_flood's argument and the log-file wait the merge brought back; it added the copy race's actuator and test program,toyos_userbound::contiguouswith its host tests (+95), the resume ordering, init's flush count, the bounded hold-back,Doorbelland the UART burst.ConsoleLine's state and stdio's second mapping were not deleted: each needs something no code here says yet, recorded in its issue. The roast's "about −500" was not delivered.What I am unsure of
klogddrains records before queued lines whatever their age, and nothing drains the queue between the stop of every holder and the last word. Seen once under load; no deterministic stimulus yet.launcher_refusalsandport_poll_churnwere red once each in the load run, wide, and green alone; not investigated.copy-meets-a-remapholds the copy on a spinning CPU with interrupts off, answering shootdowns itself (tlb::poll); its red arm depends on the physical allocator handing the just-freed frame to the nextmmap, which it did in every run here.IrqGuardandBackendGuardwindows only, and was not re-run after the doorbell moved out of the bracket.flush_finalsays in the black box that it gave up, and still spins rather than parking with a deadline: the wire is a ticketSleepLock, which has no timed acquire.issues/isolation/a-syscall-writes-through-a-read-only-user-mapping.mdrecords a defect this branch fixes; it says the landing's first successor deletes it. The copy race, also onmain, is not filed separately.hda_toneran at its own smp 2; onlyaudio_tone_loadran at smp 8.🤖 Generated with Claude Code
https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK