fix: multiple issues preventing NAM from surviving rate/period changes
CI / test (push) Has been cancelled
CI / test (push) Has been cancelled
Root causes of 'changing bit rate / sample rate kills NAM': 1. Zombie jackd from stale second Python process — when stop_jack() kills jackd, the other process's auto-restart respawns jackd with OLD config between stop and start. start_jack() sees a running jackd and returns True immediately without verifying it's actually accepting connections. 2. Zombie Python process from incomplete service restart — two main.py instances running, each with its own AudioSystem and JackAudioClient. Profile change kills one JACK but the other respawns it. jack_client.start() then fails with server_error because the stale SHM conflicts. 3. start_jack() returned True for a dead jackd — process exists but JackServer::Open failed with -1 (bad period/rate for device). No SHM socket was created, but the process check passed. Fixes: - _ensure_singleton(): kill any other main.py process at boot - start_jack(): verify jack_default_0_0 SHM socket exists before returning True; kill zombie process if socket is missing - Profile handler: clean SHM right before jack_client.start() - JackAudioClient.start(): retry once with SHM cleanup on server_error (0x21)
This commit is contained in:
@@ -55,6 +55,28 @@ from src.system.config import DEFAULT_CONFIG_PATH, load_config, save_config, DEF
|
||||
_AUTH_PIN_SEEN = False
|
||||
|
||||
|
||||
def _ensure_singleton() -> None:
|
||||
"""Kill any other main.py processes (from stale service restarts)."""
|
||||
import subprocess, os
|
||||
my_pid = os.getpid()
|
||||
try:
|
||||
r = subprocess.run(
|
||||
["pgrep", "-f", "python3.*main.py"],
|
||||
capture_output=True, text=True, timeout=3,
|
||||
)
|
||||
if r.returncode == 0:
|
||||
for pid_str in r.stdout.strip().split():
|
||||
pid = int(pid_str)
|
||||
if pid != my_pid:
|
||||
try:
|
||||
os.kill(pid, 9)
|
||||
logger.info("Killed stale pedal process PID %d", pid)
|
||||
except (OSError, ProcessLookupError):
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def _ensure_auth_pin(config: dict, config_path: Path) -> str:
|
||||
"""Generate a random 6-digit auth PIN on first boot and persist it to config.
|
||||
|
||||
@@ -790,6 +812,9 @@ def main() -> int:
|
||||
|
||||
Returns exit code 0 on clean shutdown, 1 on boot failure.
|
||||
"""
|
||||
# Kill any stale pedal processes before doing anything else
|
||||
_ensure_singleton()
|
||||
|
||||
# Lock process memory early to prevent page faults in RT callback
|
||||
try:
|
||||
import ctypes
|
||||
|
||||
+16
-3
@@ -288,10 +288,23 @@ class AudioSystem:
|
||||
logger.info("JACK disabled in config")
|
||||
return False
|
||||
|
||||
# Already running?
|
||||
# Already running? Check that the running JACK matches our
|
||||
# desired config — a zombie jackd from another process can
|
||||
# satisfy _jack_is_running() but have wrong settings.
|
||||
if _jack_is_running():
|
||||
logger.info("JACK already running")
|
||||
return True
|
||||
# Verify via /dev/shm/jack_default_0_0 socket that the
|
||||
# running JACK is actually accepting connections.
|
||||
if Path("/dev/shm/jack_default_0_0").is_socket():
|
||||
logger.info("JACK already running and operational — reusing")
|
||||
return True
|
||||
# Process exists but no socket — likely a zombie that
|
||||
# spawned but failed to init audio. Kill it first.
|
||||
logger.warning("JACK process exists but not operational — killing")
|
||||
try:
|
||||
subprocess.run(["killall", "-9", "jackd"], capture_output=True, timeout=3)
|
||||
time.sleep(0.5)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
profile = self.config.latency_profile
|
||||
jack_env = os.environ.copy()
|
||||
|
||||
Reference in New Issue
Block a user