mirror of
https://github.com/smittix/intercept.git
synced 2026-07-29 19:18:10 -07:00
Fix waterfall-to-listen SDR busy race condition
Wait for server-side WebSocket stop confirmation before closing the connection, ensuring the IQ process is fully terminated and the USB device released. Add retry logic with back-off in the audio start endpoint as defense-in-depth for any remaining timing gaps. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+26
-11
@@ -1310,20 +1310,35 @@ def start_audio() -> Response:
|
|||||||
_stop_waterfall_internal()
|
_stop_waterfall_internal()
|
||||||
time.sleep(0.2)
|
time.sleep(0.2)
|
||||||
|
|
||||||
# Release waterfall device claim if the WebSocket waterfall is still
|
# Claim device for listening audio. The WebSocket waterfall handler
|
||||||
# holding it. The JS client sends a stop command and closes the
|
# may still be tearing down its IQ capture process (thread join +
|
||||||
# WebSocket before requesting audio, but the backend handler may not
|
# safe_terminate can take several seconds), so we retry with back-off
|
||||||
# have finished its cleanup yet.
|
# to give the USB device time to be fully released.
|
||||||
device_status = app_module.get_sdr_device_status()
|
|
||||||
if device_status.get(device) == 'waterfall':
|
|
||||||
app_module.release_sdr_device(device)
|
|
||||||
time.sleep(0.3)
|
|
||||||
|
|
||||||
# Claim device for listening audio
|
|
||||||
if listening_active_device is None or listening_active_device != device:
|
if listening_active_device is None or listening_active_device != device:
|
||||||
if listening_active_device is not None:
|
if listening_active_device is not None:
|
||||||
app_module.release_sdr_device(listening_active_device)
|
app_module.release_sdr_device(listening_active_device)
|
||||||
error = app_module.claim_sdr_device(device, 'listening')
|
listening_active_device = None
|
||||||
|
|
||||||
|
error = None
|
||||||
|
max_claim_attempts = 6
|
||||||
|
for attempt in range(max_claim_attempts):
|
||||||
|
# Force-release a stale waterfall registry entry on each
|
||||||
|
# attempt — the WebSocket handler may not have finished
|
||||||
|
# cleanup yet.
|
||||||
|
device_status = app_module.get_sdr_device_status()
|
||||||
|
if device_status.get(device) == 'waterfall':
|
||||||
|
app_module.release_sdr_device(device)
|
||||||
|
|
||||||
|
error = app_module.claim_sdr_device(device, 'listening')
|
||||||
|
if not error:
|
||||||
|
break
|
||||||
|
if attempt < max_claim_attempts - 1:
|
||||||
|
logger.debug(
|
||||||
|
f"Device claim attempt {attempt + 1}/{max_claim_attempts} "
|
||||||
|
f"failed, retrying in 0.5s: {error}"
|
||||||
|
)
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
if error:
|
if error:
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'status': 'error',
|
'status': 'error',
|
||||||
|
|||||||
@@ -3943,11 +3943,31 @@ async function stopWaterfall() {
|
|||||||
|
|
||||||
// WebSocket path
|
// WebSocket path
|
||||||
if (waterfallUseWebSocket && waterfallWebSocket) {
|
if (waterfallUseWebSocket && waterfallWebSocket) {
|
||||||
|
const ws = waterfallWebSocket;
|
||||||
try {
|
try {
|
||||||
if (waterfallWebSocket.readyState === WebSocket.OPEN) {
|
if (ws.readyState === WebSocket.OPEN) {
|
||||||
waterfallWebSocket.send(JSON.stringify({ cmd: 'stop' }));
|
// Wait for server to confirm stop (it terminates the IQ
|
||||||
|
// process and releases the USB device before responding).
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
const timeout = setTimeout(resolve, 4000);
|
||||||
|
const prevHandler = ws.onmessage;
|
||||||
|
ws.onmessage = (event) => {
|
||||||
|
if (typeof event.data === 'string') {
|
||||||
|
try {
|
||||||
|
const msg = JSON.parse(event.data);
|
||||||
|
if (msg.status === 'stopped') {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
resolve();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
if (prevHandler) prevHandler(event);
|
||||||
|
};
|
||||||
|
ws.send(JSON.stringify({ cmd: 'stop' }));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
waterfallWebSocket.close();
|
ws.close();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('[WATERFALL] WebSocket stop error:', e);
|
console.error('[WATERFALL] WebSocket stop error:', e);
|
||||||
}
|
}
|
||||||
@@ -3958,8 +3978,6 @@ async function stopWaterfall() {
|
|||||||
if (typeof releaseDevice === 'function') {
|
if (typeof releaseDevice === 'function') {
|
||||||
releaseDevice('waterfall');
|
releaseDevice('waterfall');
|
||||||
}
|
}
|
||||||
// Allow backend WebSocket handler to finish cleanup and release SDR
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 300));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user