Repository navigation
hda_tone: the judge refuses multi-stream sessions, and a dropped cpal stream waits a bounded time on soundd and then lets go - #641
Conversation
The T14 has redded `hda_tone` on every boot since #536 with "soundd filled 104 period(s) the tone client had not covered". None of them were the tone's. `hda_client_stall`, the next job in the boot, stalls its client eight times for 13 periods each (`starve_max=13`): 104. On #638 its stream connected 2 ms after the tone's job exited, while soundd was still fading the tone's stream out (it removed it 4 ms later), so soundd never reached `clients=0` between the two. soundd counts one window across every client it holds and flushes it only when the last one leaves, so the tone's last window carried the stall's first three stalls (39) and the next window the other five (65), and `job_window`, which ends a session at the first `clients=0` line after the job's spawn, read both as the tone's. No line soundd writes can take such a window apart, so the judge now refuses a window whose sessions are not one stream each, by name, instead of summing another job's counts into its job. The host check stages that window with no underrun at all, which the judge passed. Run against 20 T14 logs, #638's `testcases` readback and 19 console captures of 7 boots after #536 and 12 before it: every `hda_tone` FAIL, on every boot after #536 and on the 3 before it where the next stream connected first, becomes this refusal; every PASS stays one; `shipped_client_departures` reads the same on all 20. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
cpal's ToyOS host at 18f2db2: dropping a `Stream` returns once soundd has let the stream go, which it says by closing the stream's signal pipe, where it used to return as soon as the close was sent, inside soundd's fade. A job that drops its stream and exits now leaves nothing for the next job's stream to share, which is what `hda_tone`'s window needs to be the tone's. Why the next job's stream got there first once #536 landed, measured as the interval from the tone job's exit record to the next job's `opening stream`: 2 to 4 ms on all 7 T14 boots after it, against the 5 to 6 ms soundd takes to fade and remove a stream; 12 to 69 ms on 9 of the 12 boots before it, and f5c8026, one of the 3 that lost, redded the same way. What held a job boundary before #536 is read from its code, not measured: `spawn` opened the next binary through `vfs::lock() .open_backing`, which first drained the write-back queue to the boot stick (`writeback::drain_held`), and logd wrote `/log` through the kernel's FAT under the same lock. #536 moved `/log` and `/boot` to fsd and took that drain out of `open_backing`. `hda_client_stall` stays red: its judge wants its first stream to find soundd suspended, and soundd suspends about 23 ms after the previous stream is removed, once the pipeline has played out (`issues/audio/hda-client-stall-reads-one-resume-where-its-judge-wants-two.md`). `issues/audio/hda-tone-reads-underruns-on-the-t14-where-its-judge-wants-none.md` goes: its exit condition is `hda_tone` PASS on the T14 at the head that lands this. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
|
Orchestrator runs at 1621eb5 (logs
|
|
Review of #641 at 1621eb5, round 1. Gate: CI Net Brief
BLOCKER
NOTE
REMOVE
SEND BACK |
ToyOSOrg/cpal `toyos-0.18.0` moves to 061f134 in both lockfiles that name it. At 18f2db2 a dropped `Stream` waited for soundd to close its signal pipe with nothing to bound the wait, so a soundd that stopped serving the stream held doom's `I_ShutdownSound` and `tone`'s exit for good. `Drop` now waits at most soundd's fade in whole periods and then the stream's ring of periods, 1280 frames or 29.02 ms at 44100 Hz. Past that it reports `HostUnavailable` through the error callback, naming soundd, and returns. `test_rs_cpal_drop_unreleased` is that case in a guest. The binary answers a cpal client's open as soundd does, reads the close, and then holds the signal pipe open without writing it. Its client, a stream built and dropped unplayed, has to come back from `Drop` with the refusal and exit 0. At 18f2db2 the client waits forever. No sound plays and soundd is not involved. `job_window` counts removals against sessions as well as connects. A stream soundd held at the job's spawn connected outside the window and is removed inside it. The audio checks stage both directions the count must refuse: - a tone whose window ended at an earlier stream's `clients=0` before its own stream connected, red under `streams > sessions`; - a tone beside a stream soundd held at its spawn, red without the removal count. The client-stall issue gets what this branch's T14 log shows. The deadline's fade and ring depth are copies of soundd's, filed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
|
Orchestrator runs at dfc9da0 (logs
|
|
Review of #641 at dfc9da0, round 2. Gate: CI
Ready for review. Net size (
Round-1 BLOCKERs
Brief
BLOCKER NOTE
REMOVE
LAND AFTER NAMED CHANGES |
ToyOSOrg/cpal `toyos-0.18.0` moves to 29180f7 in both lockfiles that name it. Once a `Drop` has given up on soundd, the stream thread ends at soundd's next signal, and the stream's end lets soundd remove it. The error callback reports nothing once the stream is dropped, so it cannot run after `Drop` has returned. The fills made during `Drop`'s wait are the app's audio again, so soundd's fade ramps from it and not from zeros. `play_tone` drops its stream after the tone and asserts that its error callback never ran. `hda_tone` and `soundd_log_stall` now red by name on a refusal from a healthy soundd, which no gate read before. `cpal_drop_unreleased` writes one signal once the client's `Drop` has come back with the refusal. It then needs the stream's connection to end while the client still lives. At 061f134 the stream thread fills that signal with silence and waits on, and the server waits with it. `cpal-drop-deadline-mirrors-soundds-fade-and-ring` is the same defect as `cpal-backend-hardcodes-the-format`: the fork compiles in soundd's numbers instead of reading them from the open's answer. It is folded into that file. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
Clean merge. Main's one new timed wait, `handler_post_without_a_pass`'s `drain_until(30 s)`, and its new shared member `cpal_drop_unreleased` were written against the width-scaled budget; the next commit brings them under this branch's ceiling rule. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
…ll, and main's new wait is under the rule
- `ceiling_self_check`'s two walks step `since`, `died` and `quiet` by
100 ms, the read loop's poll of a silent guest, instead of by whole
seconds. At whole seconds a backstop one second short,
`(ceiling * 2).max(ceiling + GUEST_QUIET - 1 s)`, passed both walks: at
`since = 5` the walk reached 19 s at quiet 14, and 19 is not past 19. It
now fails at `since = 4.2 s` ("timed out after 19s"), EXIT=101.
- The walks' comment goes: "any point" claimed more than the walk checks,
and the rest restated the loops.
- `handler_post_without_a_pass`'s drain, from main's #634, was 30 s under
the width-scaled budget. The test took 3-4 s in #634's whole runs
(4 s in 634r6), so it is 12 s.
- `cpal_drop_unreleased`, #641's new shared member, took 111 and 138 ms in
#641's whole runs, so the 5 s shared default covers it and it needs no
row.
- `deaf_cpu_hold` keeps its fixed 20 s. A binary test-runner spawns holds
no `logread`: test-runner hands down its namespace, and `logread` is a
`SysCap` duplicate, not a namespace entry. logd serves no reader port on
`tests/testcases`. The only record the hold could read is logd's file
under `/log`, which gives no notification, so waiting on it would be a
sleep-and-reread poll.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
…toyos-noredlist #588 landed first, and its `usb_stick_left` arms `usb-transport-break`, `usb-reset-moves`, `usb-reset-moves-after` and `usb-reset-moves-configured` again, its T14 row (`usbbreak`) arms `usb-transport-break`, and its metal record carries the `usbbreak` rows. Those stay, with `msc::transport_break`, `msc::reset_moves`, `RECOVERING` and `Quiet::Staged`. Everything else this branch deleted with `usb_transport_break` (90ecefe, b5c59cb, 402107b, 9ebf080) stays deleted: no test on either side arms it. Conflicts, every hunk: - `kernel/src/actuator.rs`, two hunks. First: main's eight USB rows. Kept `usb_transport_break`, `usb_reset_moves`, `usb_reset_moves_after` and `usb_reset_moves_configured`, with `usb_reset_moves`' doc losing its citation of the deleted `usb_transport_break`; dropped `usb_transport_offline`, `usb_reset_break`, `usb_transport_break_owed` and `usb_transport_break_flushed`, armed only by the deleted `usb_transport_break`, `log_flush_retry` and `partition_claim_departure`. Second: dropped `watch_window` (`blocking_read_window`, deleted in dc62efc), kept #634's `handler_post`. - `kernel/src/drivers/xhci/wait/msc.rs`, fourteen hunks, misaligned by #588's rewrite of the file. Resolved as main's file with this branch's deletions applied to it: `Asks` and `bot`'s `asks` argument at its five call sites; `transport_break`'s owed and flushed half (`WROTE`, `FLUSHED`, `AFTER_A_FLUSH`, `wrote`, `flushed`, their calls in `MscDevice::wrote` and the flush, and `arm`'s argument), so `arm` reads `usb-transport-break` alone; `return_silent` and its hook in `served`; `reset_break` and the wrapper around `climb`, whose body `climb_until_in_step` carries again; `short_read` and its hold and release around the data phase; `staged` and its checks in `bot`, the bad signature, the withheld CBW, the gone port and the unanswered wait; the bind's `slow_return` stall and INQUIRY staging, and `slow_return` itself; `read_serial`'s short ask. `reset_moves`, its three holds and `RECOVERING` are main's as they stand. - `kernel/src/watch.rs`, one hunk: dropped the `window` module (`watch-window`), kept #634's `handler_post` module. - `toyos-sched/src/watch.rs`, one hunk: the same, `window` dropped and `handler_post` kept. - `src/lib.rs`, one hunk: kept #652's `pub mod n2`, dropped `pub mod redlist`. - `tests/common/power.rs`, one hunk: main's only change there renamed `transport_break_chain`'s doc to `usb_stick_left`; its one caller is the deleted `usb_transport_break`, so it stays deleted. - `tests/common/usb.rs`, one hunk holding main's whole `usb_transport_break` region. Kept #588's `Held`, `usb_stick_left`, `a_stick_that_left_under_its_rung`, `transport_break_on_metal`, `transport_break_recovered` (which `tests/checks/usb.rs` judges) and `broke_on`; the rest is `usb_transport_break`'s and stays deleted: `a_read_whose_first_wait_spent_its_budget_goes_out_again`, `serial_short_is_not_read`, `cpu_of`, `line_with`, `Moved`, `a_stick_its_reset_moved_carries_on`, `abandoned_write_is_taken_offline`, `no_command_was_refused`, `port_gone_is_left_to_the_teardown`, `control_requests`, `command_blocks`, `every_reset_is_followed_by_a_test_unit_ready`, `is_a_rungs_configuration`, `every_port_reset_is_followed_by_a_test_unit_ready`, `every_reset_is_followed_by_both_clears` and `transport_gives_up`. - `tests/toyos.rs`, five hunks. `MACHINE_TESTS`: kept `usb_stick_left` and #634's `handler_post_without_a_pass`; dropped `usb_transport_break`, `xhci_full_speed_device` (5e42235), `blocking_read_window` and `user_copy_races_munmap` (7ea6be1). `METAL`: kept #588's `usb_stick_left` row. Dispatch: kept `usb_stick_left` and `handler_post_without_a_pass`; dropped `usb_transport_break`, `xhci_full_speed_device` and `smp_failed_ap_leaves_no_hole` (aedcf17). Outside the conflicts: - `kernel/src/drivers/xhci/wait/mod.rs`: `Quiet::Staged` and its line come back, which `transport_break::take` answers with; the auto-merge had taken 9ebf080's deletion. The `reset_break` and `return_silent` hooks stay deleted. - `src/metal.rs`: `FLASHABLE`'s `usb-transport-break` row comes back (b5c59cb took it), since `usb_stick_left`'s T14 arm flashes it. - `issues/kernel/a-held-disk-waits-for-a-pass-no-cpu-takes-when-every-cpu-is-in-a-call-on-it.md`: records `4f2bea143`, this merge's second parent, as the tree holding #588's version of `usb_transport_break`, in place of the instruction to record it. - `issues/boot-media/logd-ends-the-boots-log-on-one-refused-create-and-nothing-durable-says-so.md`: drops its citation of `power::transport_break_chain`, deleted in 90ecefe. The `rust` gitlink is main's. On this tree: `cargo run -- --build-only` exit 0; the kernel's `cargo check` for x86-64 and AArch64, bare, with `boot-actuators` and with `boot-actuators,test-actuators`, exit 0 each; `cargo test --test toyos-build --no-run` exit 0, no warning. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
The branch was at 649ea51 (#641). Three landings since sit under it: #642 (dc8212c), #659 (c1c5048) and #655 (5daab30). Two content conflicts, each main deleting what this branch's hunk stood beside: - kernel/src/object/ops.rs, close_ends_polls: #655 deleted the log's and the keyboard's close actuators, whose two arms this branch's `Process(_) => false` sat between. Main's two `false` arms stand and the process's is a third. - tests/toyos-rust-tests/src/bin/process_lifecycle.rs, the imports: #642 deleted `toyos::AsHandle` with the pid arm, its one user; this branch's `toyos::poller` import stands alone. Everything else merged by itself: #642's deletions in kernel/src/object/process.rs beside this branch's `Arc<Watch>`, #659's init changes beside the one doc sentence this branch deletes, and the `rust` gitlink at main's 95960d6c214. This commit is the resolution and nothing else. What #655's contract changes in this branch's own lines is the next commit's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013UDZQ6fSKw14e4w2TKTRfm
hda_tonehas redded on every T14 boot since #536 with "soundd filled 104 period(s) the tone client had not covered". The 104 periods were never the tone's.Root cause
hda_client_stall, the job after the tone in thetestcasesboot, stalls its client 8 times for 13 periods each (starve_max=13), so 8 × 13 = 104.clients=0between the two jobs. soundd counts one stats window across every client it holds and flushes it only when the last client leaves. The tone's last window (4.459 s to 6.460 s) therefore carried the stall's first 3 stalls (39), and the next window the other 5 (65).job_window(tests/common/audio.rs) ends a session at the firstclients=0line after the job's spawn, so it summed both windows into the tone.opening stream, against soundd's removal of the tone's stream:hda_toneredded the same way. f5c8026, the red filed inissues/audio/hda-tone-reads-underruns-on-the-t14-where-its-judge-wants-none.md, is one of them.spawnopened each binary throughvfs::lock().open_backing, which first ranwriteback::drain_heldto the boot stick. logd wrote/logthrough the kernel's FAT under the same lock. Storage: file servers for DATA, the log and the boot volume; the kernel's NVMe and FAT go #536 moved/logand/bootto fsd and took the drain out ofVfs::open_backing_identified. A job boundary no longer waits on a USB write, so it now fits inside soundd's fade.retain_active,userland/soundd/src/mix.rs), and its stats windows are defined to span every client it holds (toyos-mixer/src/stats.rs). The judge assumed each job's stream had a soundd session to itself, and nothing ensured that.The fix
job_windownow refuses, by name, any window whose sessions are not one stream each, instead of summing another job's counts into its own job.tests/checks/audio.rsstages three windows the judge must refuse:clients=0before its own stream connected (the review's case);toyos-0.18.0is at 29180f7, pinned inuserland/Cargo.lockandtests/toyos-rust-tests/Cargo.lock.Dropreturned during soundd's fade. It now reads the signal pipe until soundd closes it, filling what soundd asks for meanwhile with silence, since that is past the stream's end.AudioStream::closeis fire-and-forget, andtoyos/srcis outside this brief.Dropwaits on the stream thread's end, still the pipe's EOF, but for at most soundd's 5 ms fade in whole periods plus the stream's ring of 8 periods: 1280 frames, 29.02 ms at 44100 Hz. A mix loop that has not run for a whole ring has let the device play out everything it was given.DropreportsHostUnavailablethrough the error callback and returns: "soundd did not let go of the closed stream within 29.024943ms, its fade and a ring of periods".Dropthrough a channel whose sender only the thread holds. An unwinding data callback therefore ends the wait the same way the pipe's end does.Drophas given up, the stream thread ends at soundd's next signal instead of filling silence until soundd lets go. Dropping itsAudioStreamcloses the stream's connection and signal pipe, and soundd removes a stream on either.Dropgives up, as at 7e9775d and 18f2db2, so soundd's fade ramps from the app's audio and not from zeros.Dropgives up by taking the callback from under the lock each fill calls it with, so it is not called onceDrophas returned. A fill after that is silence.STATE_DEADunder the error callback's lock, which aDropthat gives up takes after storing it, so noDeviceNotAvailablelands afterDrophas returned.issues/audio/cpal-backend-hardcodes-the-format.mdrecords, the fork compiling in soundd's numbers, and it is folded in there.cpal_drop_unreleased, a guest test (tests/toyos-rust-tests/src/bin/cpal_drop_unreleased.rs).Drophas come back.Dropreturns, and says it on its stdout.play_tonereads its error callback (tests/toyos-rust-tests/src/tone.rs). It drops its stream after the tone and asserts that the callback never ran.hda_toneandsoundd_log_stalltherefore red by name, throughjob_passed, on a refusal from a healthy soundd. Both are metal-only (tests/toyos.rs:379,382).issues/audio/hda-tone-reads-underruns-on-the-t14-where-its-judge-wants-none.mdis deleted. Its exit condition is the T14 PASS below.issues/audio/hda-client-stall-reads-one-resume-where-its-judge-wants-two.mdrecords what the head's T14 log shows, below.Measured on the T14 at 1621eb5
The orchestrator's
--metal hda_tonerun (issuecomment-5917167316),orch/logs/641-T14-hda_tone.loglines 535–556, in log order:client 0 removedand itsclients=0flush come ahead ofexit: test_rs_audio_tone. The stall's first stream opens 3 ms after that exit.audio_idle_suspend: PASShda_tone: PASSshipped_client_departures: PASShda_client_stall: FAIL, "soundd resumed 1 time(s) — the second stream did not find a suspended daemon, so nothing here tests a resume:"Gates
At 597d60a, cpal at 29180f7:
cargo run -- --build-onlyagainst the local fork clone before pushing itcargo test --test toyos-build -- --listagainst the local fork clone (builds the guest crate; listscpal_drop_unreleased)cargo run -- --build-onlyat the pushed pin (userland's and the guest crate's cpal both from the 29180f7 checkout)cargo test --test toyos-build -- --listat the pinpin-061f134.patchapplied (the guest crate built from cpal's 061f134 checkout; listscpal_drop_unreleased)cargo metadata --lockedinuserland/and intests/toyos-rust-tests/cargo run -- --ci hostcargo test --test toyos-checksThe judge is unchanged since dfc9da0, where its mutations were measured:
metal_audio_judgeswithstreams != sessionsmadestreams > sessionsmetal_audio_judgeswith the removal count droppedmetal_audio_judgeswith the judge change reverted and the staged shared window kept (at 1621eb5)The orchestrator's runs at dfc9da0 (issuecomment-5918082157):
cpal_drop_unreleased: 0, in 150 ms.pin-18f2db2.patch: 1, timed out at the 300 s ceiling.--metal hda_tone: 0, PASS. T14--metal shipped_client_departures: 0, PASS.No guest or T14 run has been made at 597d60a.
pin-18f2db2.patchpins cpal back to 18f2db2 in both lockfiles. That is the base whose unbounded wait the review measured, and it keeps the guest test.pin-061f134.patchpins cpal back to 061f134 in both lockfiles and keeps the extended guest test. It is shown to build and has not been run.revert-fix.patchreverts the whole change onto 6e1f166. The T14 exited 1 with it (issuecomment-5917167316).hda_toneandshipped_client_departurespassed.testcasesreadback and 19 console captures, 7 from after Storage: file servers for DATA, the log and the boot volume; the kernel's NVMe and FAT go #536 and 12 from before.hda_tone's verdicts are round 1's on every log: 9 PASS, and 11 refused as a shared window.shipped_client_departurespasses on all 20.hda_client_stallhas the same 2 PASS and 18 FAIL as round 1. On 11 of those FAILs the removal count now names the shared window, "connected 2 and removed 3 stream(s)", where round 1 said "resumed 1 time(s)". On those boots the tone's removal lands inside the stall's window.Unsure
hda_client_stallstays red, and this does not fix it. Its issue now carries the head's log.orch/logs/641r2-r2-T14-hda_tone.log,…-shipped_client_departures.log) hold 10 real-soundd cpal drops, 5 per boot: the tone, the stall's two streams, and the twotones fromnull_sink_client_exits. Each ends inclient N removed (closed). The count ofdid not let golines is 0 in each log. The logs stamp in milliseconds and do not record the close, so no margin can be read from them.Drop's wait rests on the code. A QEMU test could reach that window only with a clock.Dropjoined the thread and waited on the same callback.play_tone's assert has no negative control that has been run. It reads only on metal.🤖 Generated with Claude Code
https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L