aboutsummaryrefslogtreecommitdiff
path: root/docs/specs
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-07-09 16:29:48 -0500
committerCraig Jennings <c@cjennings.net>2026-07-09 16:29:48 -0500
commit05696bb71d4eec5404263ae950426fb199323048 (patch)
treeb4273b8447b2bd273f7269985267ca5ade0a3ca8 /docs/specs
parent3cf92e272426e72a1be7b60fa8d1ad9b2416ece2 (diff)
downloadarchsetup-05696bb71d4eec5404263ae950426fb199323048.tar.gz
archsetup-05696bb71d4eec5404263ae950426fb199323048.zip
docs: correct the audio-doctor spec's timeout claim, close Phase 0
The draft said build_status had no pactl timeout and froze the panel. Driving it disproved that: pactl.run defaults to timeout=8 and raises PactlTimeout, so the panel degrades. The real defect was narrower and is now fixed. Two of build_status's four reads sat outside its degrade guard, so a server that answered the device lists and then stopped answering raised out of a function contracted to return ok:False, and waybar's audio module died rather than dimming. Corrected in place with the reasoning, rather than dropped, since a spec that quietly loses a wrong claim teaches nothing about why it was wrong.
Diffstat (limited to 'docs/specs')
-rw-r--r--docs/specs/2026-07-09-audio-doctor-spec.org21
1 files changed, 13 insertions, 8 deletions
diff --git a/docs/specs/2026-07-09-audio-doctor-spec.org b/docs/specs/2026-07-09-audio-doctor-spec.org
index 611a6ca..5c4f844 100644
--- a/docs/specs/2026-07-09-audio-doctor-spec.org
+++ b/docs/specs/2026-07-09-audio-doctor-spec.org
@@ -8,12 +8,13 @@
:PROPERTIES:
:ID: 6ce3f2ef-052e-40cb-9067-42f8e665f813
:END:
-- [2026-07-09 Thu] DRAFT — written from the roam-inbox ask "look into a doctor button for the audio panel: what happens if pulseaudio or its equivalent is borked? would we know? how could we test for it? what remedies would we have?" Probe set, classifier, and remedy tiers below are grounded in a live survey of ratio's stack. Seven Decisions are open.
+- [2026-07-09 Thu] DRAFT — written from the roam-inbox ask "look into a doctor button for the audio panel: what happens if pulseaudio or its equivalent is borked? would we know? how could we test for it? what remedies would we have?" Probe set, classifier, and remedy tiers below are grounded in a live survey of ratio's stack. Six Decisions are open; Phase 0 shipped ahead of the rest.
* DRAFT Status
:PROPERTIES:
:ID: 1cc2b355-9450-4ace-bdb3-b935f7051918
:END:
+- [2026-07-09 Thu] DRAFT — Phase 0 shipped (dotfiles =4d42eb3=) and its Decision closed. The first draft claimed =build_status()= had no timeout and froze the panel; driving it disproved that — =pactl.run()= already times out. The real bug was narrower: two of the four reads sat outside the degrade guard, killing waybar's audio module. The claim is corrected in place rather than quietly dropped.
- [2026-07-09 Thu] DRAFT — spine complete, Decisions open. Not ready to build: the remedy tiers touch a running meeting's audio, and the classifier's precedence needs Craig's call before code.
* Metadata
@@ -61,7 +62,7 @@ The third row is the trap. The panel speaks =pactl=, so a dead =pipewire-pulse=
Today, no — and worse than no. Two findings from the live survey:
-1. *=pactl= hangs on a hung server.* Against a refused socket it fails in ~0ms ("Connection refused"). Against a socket that accepts and never answers, it blocks indefinitely — measured: still blocked at the 5s timeout. A hung server is precisely the case the doctor exists for, so *every probe carries a timeout*, and the doctor may not reuse the panel's =status.build_status()=, which has no timeout and would hang the GTK thread with it.
+1. *=pactl= hangs on a hung server.* Against a refused socket it fails in ~0ms ("Connection refused"). Against a socket that accepts and never answers, it blocks indefinitely — measured: still blocked at the 5s timeout. A hung server is precisely the case the doctor exists for, so *every probe carries a timeout*. The engine's own =pactl.run()= already does this and raises =PactlTimeout=; the doctor must do the same, and must never call a bare =subprocess.run=.
2. *The panel cannot diagnose itself.* Its only data source is the layer most likely to be down. The doctor's probes therefore read =systemctl --user= (never blocks on the audio graph) and =pw-dump= / =wpctl= (PipeWire-native, alive when the Pulse layer is not) *before* they touch =pactl=.
@@ -165,7 +166,7 @@ Mirror the net panel's module split, which already separates these concerns clea
Three constraints that fall out of the survey and are easy to get wrong:
-1. *Timeouts everywhere.* =cmd.run(..., timeout=N)= on every probe. A =pactl= call with no timeout will hang the doctor against the exact fault it is diagnosing. Test this with a socket that accepts and never answers — a refused socket does not reproduce it.
+1. *Timeouts everywhere, and guard every read.* Bound every probe. Beyond that: a function that promises to degrade must guard *all* of its reads, not the first two. That was the Phase 0 bug. Test with a fault that answers early calls and hangs on later ones — a server that hangs on the first call masks everything behind it, and a refused socket does not reproduce the hang at all.
2. *Probe order is load-bearing.* =systemctl= first (cannot hang), then =pw-dump= (alive when Pulse is dead), then =pactl= (may hang). Reversing this makes the doctor unable to report on the case it was built for.
@@ -212,7 +213,7 @@ Rejected on evidence. =pactl= hangs against a hung server, and it is dead when t
Rejected. Remedies 3 through 5 are audible and, in a meeting, disruptive. Diagnosis is free and should be; repair is a press.
-* Decisions [0/7]
+* Decisions [2/8]
** TODO Where does the DOCTOR key live, and does it replace anything?
The net panel puts diagnose and repair behind one console key. The audio panel's console row already carries INPUT, OUTPUT, and PUSH TALK. Does DOCTOR join that row, or sit in a section header like the maint console's CLEAN UP / REVIEW & FIX pair?
@@ -223,8 +224,11 @@ The maint console is CLI-first with the GUI as a face; the net panel ships =net
** TODO Confirm the classifier precedence above, particularly rows 3 and 4.
Should "pulse compat hung" and "pulse compat down" be one verdict with a detail line, or two verdicts? They share a remedy (restart =pipewire-pulse=) but they are different failures, and the hung case is the one that freezes other tooling.
-** TODO Does the panel's own status build get the same timeout treatment?
-=status.build_status()= calls =pactl= with no timeout today. Against a hung server the panel freezes, not just the doctor. Fixing that is a small change with real value and is arguably a prerequisite bug fix rather than part of this feature. Split it out?
+** DONE Does the panel's own status build get the same timeout treatment?
+CLOSED: [2026-07-09 Thu]
+Investigated and answered: it already had one. =pactl.run()= defaults to =timeout=8= and raises =PactlTimeout=, so the panel degrades rather than freezing. The draft of this spec claimed otherwise; the claim was wrong.
+
+What the investigation *did* find is a narrower, real bug, now fixed (dotfiles =4d42eb3=): =build_status()= guarded its two device-list reads but called =default_sink()= and =default_source()= outside the guard, so a server that answered the lists and then stopped answering raised =PactlTimeout= out of a function whose contract is to degrade. =waybar-audio= calls it with nothing catching it, so the bar's audio module died rather than dimming. A server that hangs on the *first* call never exposed this — =list_sinks= raised inside the guard and masked everything behind it, which is why the obvious test would have passed against broken code.
** TODO Which remedies are Auto, which Confirm, which Arm?
The table above proposes 1–2 Auto, 3–4 Confirm, 5 Arm. Remedy 4 drops Pulse clients briefly — is a Confirm enough, or does it want the arm treatment too when a stream is currently playing?
@@ -239,8 +243,9 @@ Tier 4 can count xrun and suspend lines in the unit journal. That is a *quality*
Sequenced so each phase is independently green and independently useful. Nothing here starts until the Decisions are closed.
-** Phase 0 — the timeout bug fix (prerequisite)
-Give =status.build_status()= and every existing =pactl= call a timeout, with a regression test using an accept-and-never-answer socket. This is a real bug on its own: a hung audio server currently freezes the audio panel. Ships independently of the doctor.
+** DONE Phase 0 — the degrade-on-hang bug fix (prerequisite)
+CLOSED: [2026-07-09 Thu]
+Shipped ahead of the rest (dotfiles =4d42eb3=). =build_status()= now guards all four pactl reads, not just the two list reads, so a half-wedged server dims the bar's audio module instead of killing it. =fake-pactl= grew =FAKE_PACTL_SLEEP_DEFAULTS= to express "answers the lists, hangs on the defaults" — the blanket sleep knob cannot, because the first call would hang and mask the bug.
** Phase 1 — =diag.py=, the four probe tiers
Bounded probes, a context dict, fakes for every outcome including hung. No classifier, no GUI. =audio diag --json= prints the context.