fix: stop bt-a2dp-jack during profile changes to prevent JACK race
CI / test (push) Has been cancelled

The bt-a2dp-jack systemd unit runs an independent JACK instance for
Bluetooth audio. When stop_jack() kills jackd, bt-a2dp-jack restarts
it within 5 seconds with default parameters. By the time start_jack()
runs in the profile handler, _jack_is_running() already returns True
for the bt-a2dp JACK, causing start_jack() to return immediately
without configuring our period/rate. The pedal's JACK client then
fails with server_error (0x21) because the running JACK doesn't match.

Fix:
- stop_jack() temporarily stops bt-a2dp-jack before killing jackd
- Profile handler restarts bt-a2dp-jack after JACK is configured
- Rollback path also restarts bt-a2dp-jack
This commit is contained in:
2026-06-18 17:29:52 -04:00
parent 61886858f6
commit 92c6bb9587
2 changed files with 25 additions and 0 deletions
+9
View File
@@ -371,6 +371,15 @@ class AudioSystem:
def stop_jack(self) -> None:
"""Gracefully stop the JACK server."""
# Stop the Bluetooth A2DP bridge first — it restarts jackd
# independently and can race with our profile changes.
try:
subprocess.run(
["systemctl", "stop", "bt-a2dp-jack"],
capture_output=True, timeout=5,
)
except Exception:
pass
try:
subprocess.run(
["killall", "jackd"],
+16
View File
@@ -1739,6 +1739,12 @@ class WebServer:
jack_client.start()
except Exception:
pass
# Restart bt-a2dp bridge even on rollback so Bluetooth audio recovers
try:
import subprocess
subprocess.run(["systemctl", "start", "bt-a2dp-jack"], capture_output=True, timeout=5)
except Exception:
pass
raise HTTPException(
status_code=500,
detail=f"Failed to start JACK with period={target_profile['period']}, rate={target_profile['rate']} — rolled back",
@@ -1787,6 +1793,16 @@ class WebServer:
except Exception as e:
logger.warning("connect_fx_ports() failed after profile switch: %s", e)
# Restart Bluetooth A2DP bridge so it can connect to our JACK
try:
import subprocess
subprocess.run(
["systemctl", "start", "bt-a2dp-jack"],
capture_output=True, timeout=5,
)
except Exception:
pass
# ── Persist to config.yaml ──────────────────────────────
if self.deps.config is not None:
audio_cfg = self.deps.config.setdefault("audio", {})