Commit 1834cb17 by PLN (Algolia)

gig_record: never listen to a tap, and survive a large graph

master_sink() took the first thing Master was linked to, so a spectrogram tap
listed before the real sink pointed the recorder at a source that does not
exist: RUNNING while capturing nothing. Only alsa_output/bluez_output count.
It also quit awk at the first hit, so under pipefail a large pw-link graph
gave SIGPIPE (141) and killed 'check' and, by the same race, 'start'.
parent b03f787e
......@@ -50,7 +50,15 @@ master_sink() {
pw-link -l 2>/dev/null | awk '
/^ardour:Master\/audio_out 1$/ { inblk = 1; next }
/^[^ ]/ { inblk = 0 }
inblk && /\|->/ { sub(/^[ \t]*\|-> /, ""); sub(/:.*$/, ""); print; exit }
# Only a SOUND DEVICE counts. Master also feeds taps that are not sinks (the midiviz
# spectrogram, qjackctl, a meter); taking the first link of any kind pointed the
# recorder at `midiviz-spectro-<pid>.monitor` on 2026-10-11: a source that does not
# exist, so the black box reported RUNNING while capturing nothing.
# Read to the END and print only the first hit. Quitting early (`exit`) closes the
# pipe while pw-link is still writing the rest of the graph, and under
# `set -o pipefail` that SIGPIPE (141) kills the whole script: an intermittent,
# size-dependent failure, including at the moment the recorder starts.
inblk && !done && /\|->.*(alsa|bluez)_output\./ { sub(/^[ \t]*\|-> /, ""); sub(/:.*$/, ""); print; done = 1 }
'
}
......
......@@ -100,3 +100,48 @@ def test_the_parser_ignores_other_clients_master_ports(tmp_path):
ardour:Master/audio_out 1
|-> {UMC}:playback_AUX0""")
assert run_master_sink(tmp_path, listing) == UMC
def test_a_tap_that_is_not_a_sink_is_never_the_recording_target(tmp_path):
"""2026-10-11: Master also feeds midiviz's spectrogram. It was listed BEFORE the
real sink, so the recorder was pointed at `midiviz-spectro-<pid>.monitor`, a source
that does not exist: RUNNING, capturing nothing. Only a sound device counts."""
listing = textwrap.dedent(f"""\
ardour:Master/audio_out 1
|-> midiviz-spectro-3729726:input_MONO
|-> qjackctl:in_1
|-> {LAPTOP}:playback_FL""")
assert run_master_sink(tmp_path, listing) == LAPTOP
def test_a_master_feeding_only_taps_has_no_target_so_the_caller_falls_back(tmp_path):
listing = "ardour:Master/audio_out 1\n |-> midiviz-spectro-1:input_MONO\n"
assert run_master_sink(tmp_path, listing) == ""
def test_a_bluetooth_headset_is_a_valid_target(tmp_path):
bt = "bluez_output.70_26_05_D0_D5_90.1"
listing = f"ardour:Master/audio_out 1\n |-> {bt}:playback_FL\n"
assert run_master_sink(tmp_path, listing) == bt
def test_a_large_graph_does_not_kill_the_script_with_a_sigpipe(tmp_path):
"""The awk used to `exit` at its first hit. With pw-link still writing a big graph
that closes the pipe under it, and `set -euo pipefail` turns the SIGPIPE (141) into
a dead script: `gig_record.sh check` returned 141 on 2026-10-11, and the same race
sits in `start`. Reading to the end makes it size-independent."""
stubs = tmp_path / "bin"
stubs.mkdir()
pw = stubs / "pw-link"
pw.write_text(textwrap.dedent(f"""\
#!/bin/sh
printf 'ardour:Master/audio_out 1\\n |-> {LAPTOP}:playback_FL\\n'
i=0; while [ $i -lt 40000 ]; do echo "SuperCollider:out_$i"; echo " |-> ardour:Tidal 01/audio_in 1"; i=$((i+1)); done
"""))
pw.chmod(0o755)
env = dict(os.environ, PATH=f"{stubs}:{os.environ['PATH']}")
r = subprocess.run(["bash", "-c",
f'set -euo pipefail; source <(sed -n "/^master_sink()/,/^}}/p" "{SCRIPT}"); got="$(master_sink)"; echo "$got"'],
env=env, capture_output=True, text=True, timeout=60)
assert r.returncode == 0, r.returncode
assert r.stdout.strip() == LAPTOP
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment