Commit e473f7e1 by PLN (Algolia)

docs(log): 041 — park the GIG UP rewire behind the Ardour double-launch bug

The rewire (tray Launch Gig -> POST /api/rig, arm-then-confirm) is
written and loads clean, but testing it end-to-end killed Ardour:
launchers.launch('ardour') reported 'launched' while Ardour was already
running, a second instance collided with the first over the session
lock, and both exited -- taking all 12 orbit links with them.

A false negative in is_running() wired to a tray button is worse than
the mislabeled button it replaces, so it does not ship until launch()
fails closed. Parked as a patch beside the note rather than committed
(unvalidated) or left loose in the worktree (this checkout is shared and
another session commits into it by pathspec).

Carries the rest of the session's open ends too: the perf.sh redeploy
that is committed but not live, gig-up's four pre-existing NO-GO
blockers, perf-tray's 2.6GB RSS, and why gig-down is blocked on the
Freebox being down.
parent 43a44247
diff --git a/perf-tray.py b/perf-tray.py
index 5b67bf1..abfd0c3 100644
--- a/perf-tray.py
+++ b/perf-tray.py
@@ -58,6 +58,7 @@ BRIDGE_URL = "http://127.0.0.1:8773/"
# menu can do, so it gets its own confirm step (see PerfTray.on_sc_clicked).
SC_UNIT = "parvagues-sc"
SC_CONFIRM_MS = 10_000 # how long an armed restart stays armed
+GIG_CONFIRM_MS = 10_000 # same, for a GIG UP that would start the sound
SC_READY_S = 30 # measured 2026-08-14: active→responder armed in 27 s
@@ -311,13 +312,31 @@ class PerfTray:
# not focus a window. It can, through KWin's scripting bus; see
# launchers.focus_window. So this is a top-level entry AND a real focus.
#
- # Deliberately editor-only: it does not start SuperCollider, boot Tidal
- # or arm Ardour. #116 is the standing rule that the hot path must not be
- # able to start the sound by accident — one wrong click before a set is
- # a stuck scsynth, not a convenience.
- self.gig_act = QAction("🌊 Launch Gig", self.menu)
- self.gig_act.triggered.connect(lambda: self._launch("pulsar"))
+ # PLN, 2026-09-05: "click launch gig started pulsar, not ardour??" and
+ # then "is our launch gig now autodoing?".
+ #
+ # It was editor-only: `_launch("pulsar")`, nothing else. The label
+ # promised a gig and delivered an editor, so on 2026-09-05 PLN pressed
+ # it, got Pulsar, waited, and opened Ardour by hand 86 s later. The real
+ # launcher is the Bridge's RIG UP — which /api/rig showed had never run
+ # once in that Bridge process's 6.7 days, because nothing points at it.
+ #
+ # So this now calls the SAME reconciler (POST /api/rig), rather than
+ # growing a third launch order next to gig-up.sh's and rig.py's. The
+ # Bridge owns a `running` flag, which is why the tray posts to it
+ # instead of importing rig directly: two processes must not converge at
+ # once.
+ #
+ # #116 still holds — the hot path must not start the sound by accident —
+ # but it is satisfied the way refresh_sc already satisfies its mirror:
+ # arm, then confirm. When the rig is already up a click is just a focus,
+ # which is the common case and needs no guard; only a click that would
+ # actually START things arms first. No modal, per #136: a box stealing
+ # focus mid-set is its own hazard.
+ self.gig_act = QAction("🌊 GIG UP", self.menu)
+ self.gig_act.triggered.connect(self.on_gig_clicked)
self.menu.addAction(self.gig_act)
+ self._gig_armed = False
# PLN, 2026-08-14: "add a restart SC command in parvagues tray gui menu".
#
@@ -479,11 +498,14 @@ class PerfTray:
self.rig_menu.setTitle(f"Rig ▸ {up}/{total} up")
if getattr(self, "gig_act", None) is not None:
pulsar = next((i for i in snap["apps"] if i["key"] == "pulsar"), None)
- # Say which of the two things the click will do. "Focus" vs "Open" is
- # the difference between a no-op-looking click and a confident one.
- self.gig_act.setText("🌊 Launch Gig — focus ParVagues"
- if pulsar and pulsar["running"]
- else "🌊 Launch Gig — open ParVagues")
+ # Say exactly what the click will do. Three states, because the click
+ # means three different things: raise a window, arm, or converge.
+ if self._gig_armed:
+ self.gig_act.setText("⚠ GIG UP — click again to START THE RIG")
+ elif self._rig_is_up(snap):
+ self.gig_act.setText("🌊 GIG UP — focus ParVagues")
+ else:
+ self.gig_act.setText("🌊 GIG UP — converge + open the rig")
self.gig_act.setEnabled(bool(pulsar and pulsar["available"]))
# ------------------------------------------------------------ SuperDirt
@@ -492,6 +514,71 @@ class PerfTray:
# systemctl, and doing that 30x a minute for a label nobody is looking at is
# the per-tick cost this rig keeps getting bitten by.
+ def _rig_is_up(self, snap):
+ """True when a click has nothing left to start — SuperDirt up AND the
+ editor open. Anything less and GIG UP has real work to do."""
+ try:
+ active, up = sc_state()
+ except Exception:
+ return False
+ if not active or (up is not None and up < SC_READY_S):
+ return False
+ pulsar = next((i for i in snap.get("apps", []) if i["key"] == "pulsar"), None)
+ return bool(pulsar and pulsar["running"])
+
+ def on_gig_clicked(self):
+ try:
+ snap = LA.snapshot()
+ except Exception:
+ snap = {"apps": []}
+ # Already up: this is a focus, not a launch. One click, no guard.
+ if self._rig_is_up(snap):
+ self._gig_armed = False
+ self._launch("pulsar")
+ return
+ if not self._gig_armed:
+ self._gig_armed = True
+ QTimer.singleShot(GIG_CONFIRM_MS, self._disarm_gig)
+ self.refresh()
+ return
+ self._gig_armed = False
+ self._rig_up()
+
+ def _disarm_gig(self):
+ self._gig_armed = False
+ try:
+ self.refresh()
+ except Exception:
+ pass
+
+ def _rig_up(self):
+ """Hand the job to the Bridge's converge, then open it so the verdict is
+ WATCHED. rig.py:71-73 records the original complaint: RIG UP showed a
+ spinner and no window for over a minute (converge is ~100 s on a cold
+ boot), so PLN launched Ardour by hand believing it had failed. Opening
+ the dashboard is the fix for that — the log is the progress bar."""
+ import json
+ import urllib.error
+ import urllib.request
+ req = urllib.request.Request(
+ BRIDGE_URL.rstrip("/") + "/api/rig",
+ data=json.dumps({"launch_apps": True}).encode(),
+ headers={"Content-Type": "application/json"}, method="POST")
+ try:
+ with urllib.request.urlopen(req, timeout=10) as r:
+ json.loads(r.read().decode() or "{}")
+ except Exception as e:
+ # The Bridge is the only thing that can converge. If it is not
+ # answering, say so and do NOT silently fall back to opening the
+ # editor — that is exactly the half-launch that started all this.
+ self.tray.showMessage(
+ "perf-tray",
+ "GIG UP needs the Bridge (%s): %s\n"
+ "systemctl --user start parvagues-bridge" % (BRIDGE_URL, e),
+ QSystemTrayIcon.Warning, 8000)
+ return
+ self._open_url(BRIDGE_URL)
+
def refresh_sc(self):
try:
active, up = sc_state()
---
log: 041
title: "PARKED GIG UP rewire, blocked on Ardour double-launch"
date: 2026-09-05
task: "OPEN / PARKED resume point, not an achievement"
tags: [rig, tray, bridge, launchers, ardour, parked]
shareable: false
---
## STATUS: PARKED. Written, not shipped. Do not ship as-is.
## Cap (what & why)
PLN asked "is our launch gig now autodoing?" — the tray's `🌊 Launch Gig` was
`_launch("pulsar")` and nothing else, so it opened an editor and no rig. The
rewire points it at the one real reconciler (`POST /api/rig`) instead of growing
a third launch order beside `gig-up.sh`'s and `rig.py`'s.
It is written and syntactically clean, and the tray runs with it loaded. It is
**parked because testing it end-to-end broke the rig**, which exposed a defect
that must be fixed first.
## The blocker (fix this before shipping the rewire)
`POST /api/rig``rig.py``launchers.launch("ardour")` returned
`launched — launched Ardour` **while Ardour was already running** (pid 1833333).
`is_running(spec)` gave a FALSE NEGATIVE, so a second instance started, the two
collided over the session lock, and BOTH exited. Ardour vanished mid-session and
all 12 orbit links went with it.
Wiring that to a tray button is strictly worse than the mislabeled button it
replaces: one wrong click could kill a running Ardour during a set.
- Spec: `tools/bridge/launchers.py:67-70`, `"exe": r"[Aa]rdour[-\d.]*$"`.
- Real process: `/usr/lib/ardour9/ardour-9.7.0 "…/Tidal Live/Tidal Live.ardour"`.
- Detection: `is_running()` at `tools/bridge/launchers.py:236-259`.
- Already-running branch: `tools/bridge/launchers.py:281-289`.
- Note `perf.sh:563` matches the same process fine with a plain `/ardour`
substring, so the process IS matchable — the `exe`-anchored regex is the
suspect (it anchors on `$`, and the argv0 is `ardour-9.7.0`, which the
`[-\d.]*$` should cover — so reproduce before assuming).
## EXACT resume steps
1. Reproduce cold, read-only, with Ardour running:
`python3 -c "import sys; sys.path.insert(0,'tools/bridge'); import launchers as L; print(L.is_running(next(s for s in L.SPECS if s['key']=='ardour')))"`
(adjust the spec accessor to whatever `launchers.py` actually exposes).
Expect `True`; if it prints `False`, that is the bug, reproduced.
2. Fix detection, then add a guard so `launch()` can NEVER start a second
Ardour: gate on the session lock / an existing `ardour` JACK client, not only
on a process regex. A false negative must fail closed, not launch.
3. Re-apply the rewire: `git apply armada/tasks/041-gigup-rewire.patch`
(the patch in this directory IS the parked work — `perf-tray.py` only).
4. Re-test `POST /api/rig` with everything already up. Required result:
`ardour: focused`, never `ardour: launched`.
5. Then commit the rewire.
## What the patch contains (perf-tray.py)
- `🌊 Launch Gig``🌊 GIG UP`, `triggered``on_gig_clicked`.
- `_rig_is_up(snap)`: SuperDirt active AND past `SC_READY_S` AND Pulsar running.
- Arm-then-confirm, mirroring `refresh_sc`'s idiom (`GIG_CONFIRM_MS`, 10s): a
click when the rig is already up is just a focus and needs no guard; only a
click that would START things arms first. No modal (#136).
- `_rig_up()`: POSTs `{"launch_apps": true}` to `/api/rig`, then opens the
Bridge so the converge log is watched — `rig.py:71-73` records the original
complaint (spinner, no window for a minute, so PLN launched Ardour by hand).
On a Bridge error it warns and does NOT fall back to opening the editor.
- Three-state label so the click always says what it will do.
## Also outstanding (discovered, not parked code)
- `perf.sh` fix is committed but **NOT LIVE**: needs
`sudo install -m755 -o root -g root perf.sh /usr/local/sbin/perf-audio`
(outside the sudoers whitelist, so PLN must run it). Dormant meanwhile — the
removed block only fired when no Pulsar was running.
- `gig-up.sh` verdict was **NO-GO**, four pre-existing blockers unrelated to
this session's changes: boot helpers (ghc error → `tools/check-boot.sh`),
surface grid (`tools/check-drift.sh`), mute map
(`python3 tools/fix-mute-roles.py --apply`), and a `Tidal :6010` warn.
- `perf-tray` peaked **2.6 GB RSS** over 4h15m. Leak worth a look.
- `gig-down` lifecycle is BLOCKED: Freebox was down, so `Tidal Live/export/`
(25G) cannot be verified as archived and must not be deleted. `peaks/` (4.2G)
is the only unconditionally-safe reclaim, and it belongs AFTER a gig — Ardour
rebuilds peaks across 53G at session-open. `dead/`, `analysis/`, `backup/` are
all empty; there is no large risk-free win.
- Take101 (12 orbits × L/R = 24 files) was already missing from `interchange/`
and triggered Ardour's missing-source dialog. PLN: "idontcare for todays takes
they were tests at best" — cleared and saved, no action.
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