Fix stalled audio pipeline cleanup and scanner stop race condition

- Kill audio pipeline when startup produces no data instead of leaving
  zombie processes running
- Skip unnecessary 1s USB release delay when no processes were active
- Remove racy fresh=1 pipeline restart from stream endpoint
- Await stopScanner() before starting direct listen to prevent race

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Smittix
2026-02-07 15:39:51 +00:00
parent cdfc10c854
commit 32b373bf2c
2 changed files with 11 additions and 11 deletions

View File

@@ -839,9 +839,13 @@ def _start_audio_stream(frequency: float, modulation: str):
try: try:
ready, _, _ = select.select([audio_process.stdout], [], [], 4.0) ready, _, _ = select.select([audio_process.stdout], [], [], 4.0)
if not ready: if not ready:
logger.warning("Audio pipeline produced no data in startup window") logger.warning("Audio pipeline produced no data in startup window — killing stalled pipeline")
_stop_audio_stream_internal()
return
except Exception as e: except Exception as e:
logger.warning(f"Audio startup check failed: {e}") logger.warning(f"Audio startup check failed: {e}")
_stop_audio_stream_internal()
return
audio_running = True audio_running = True
audio_frequency = frequency audio_frequency = frequency
@@ -866,6 +870,8 @@ def _stop_audio_stream_internal():
audio_running = False audio_running = False
audio_frequency = 0.0 audio_frequency = 0.0
had_processes = audio_process is not None or audio_rtl_process is not None
# Kill the pipeline processes and their groups # Kill the pipeline processes and their groups
if audio_process: if audio_process:
try: try:
@@ -892,7 +898,8 @@ def _stop_audio_stream_internal():
audio_rtl_process = None audio_rtl_process = None
# Pause for SDR device USB interface to be released by kernel # Pause for SDR device USB interface to be released by kernel
time.sleep(1.0) if had_processes:
time.sleep(1.0)
# ============================================ # ============================================
@@ -1400,13 +1407,6 @@ def audio_probe() -> Response:
@listening_post_bp.route('/audio/stream') @listening_post_bp.route('/audio/stream')
def stream_audio() -> Response: def stream_audio() -> Response:
"""Stream WAV audio.""" """Stream WAV audio."""
# Optionally restart pipeline so the stream starts with a fresh header
if request.args.get('fresh') == '1' and audio_running:
try:
_start_audio_stream(audio_frequency or 0.0, audio_modulation or 'fm')
except Exception as e:
logger.error(f"Audio stream restart failed: {e}")
# Wait for audio to be ready (up to 2 seconds for modulation/squelch changes) # Wait for audio to be ready (up to 2 seconds for modulation/squelch changes)
for _ in range(40): for _ in range(40):
if audio_running and audio_process: if audio_running and audio_process:

View File

@@ -319,7 +319,7 @@ function stopScanner() {
? `/controller/agents/${listeningPostCurrentAgent}/listening_post/stop` ? `/controller/agents/${listeningPostCurrentAgent}/listening_post/stop`
: '/listening/scanner/stop'; : '/listening/scanner/stop';
fetch(endpoint, { method: 'POST' }) return fetch(endpoint, { method: 'POST' })
.then(() => { .then(() => {
if (!isAgentMode && typeof releaseDevice === 'function') releaseDevice('scanner'); if (!isAgentMode && typeof releaseDevice === 'function') releaseDevice('scanner');
listeningPostCurrentAgent = null; listeningPostCurrentAgent = null;
@@ -2245,7 +2245,7 @@ async function _startDirectListenInternal() {
try { try {
if (isScannerRunning) { if (isScannerRunning) {
stopScanner(); await stopScanner();
} }
const freqInput = document.getElementById('radioScanStart'); const freqInput = document.getElementById('radioScanStart');