mirror of
https://github.com/smittix/intercept.git
synced 2026-07-07 17:18:11 -07:00
fix: ADS-B device release leak and startup performance
Move adsb_active_device/sdr_type assignment to immediately after claim_sdr_device so stop_adsb() can always release the device, even during startup. Sync sdr_type_str after SDRType fallback to prevent claim/release key mismatch. Clear active device on all error paths. Replace blind 3s sleep for dump1090 readiness with port-polling loop (100ms intervals, 3s max). Replace subprocess.run() in rtl_test probe with Popen + select-based early termination on success/error detection. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+21
-3
@@ -758,6 +758,7 @@ def start_adsb():
|
|||||||
sdr_type = SDRType(sdr_type_str)
|
sdr_type = SDRType(sdr_type_str)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
sdr_type = SDRType.RTL_SDR
|
sdr_type = SDRType.RTL_SDR
|
||||||
|
sdr_type_str = sdr_type.value
|
||||||
|
|
||||||
# For RTL-SDR, use dump1090. For other hardware, need readsb with SoapySDR
|
# For RTL-SDR, use dump1090. For other hardware, need readsb with SoapySDR
|
||||||
if sdr_type == SDRType.RTL_SDR:
|
if sdr_type == SDRType.RTL_SDR:
|
||||||
@@ -796,6 +797,10 @@ def start_adsb():
|
|||||||
'message': error
|
'message': error
|
||||||
}), 409
|
}), 409
|
||||||
|
|
||||||
|
# Track claimed device immediately so stop_adsb() can always release it
|
||||||
|
adsb_active_device = device
|
||||||
|
adsb_active_sdr_type = sdr_type_str
|
||||||
|
|
||||||
# Create device object and build command via abstraction layer
|
# Create device object and build command via abstraction layer
|
||||||
sdr_device = SDRFactory.create_default_device(sdr_type, index=device)
|
sdr_device = SDRFactory.create_default_device(sdr_type, index=device)
|
||||||
builder = SDRFactory.get_builder(sdr_type)
|
builder = SDRFactory.get_builder(sdr_type)
|
||||||
@@ -822,11 +827,24 @@ def start_adsb():
|
|||||||
)
|
)
|
||||||
write_dump1090_pid(app_module.adsb_process.pid)
|
write_dump1090_pid(app_module.adsb_process.pid)
|
||||||
|
|
||||||
time.sleep(DUMP1090_START_WAIT)
|
# Poll for dump1090 readiness instead of blind sleep
|
||||||
|
dump1090_ready = False
|
||||||
|
poll_interval = 0.1
|
||||||
|
elapsed = 0.0
|
||||||
|
while elapsed < DUMP1090_START_WAIT:
|
||||||
|
if app_module.adsb_process.poll() is not None:
|
||||||
|
break # Process exited early — handle below
|
||||||
|
if check_dump1090_service():
|
||||||
|
dump1090_ready = True
|
||||||
|
break
|
||||||
|
time.sleep(poll_interval)
|
||||||
|
elapsed += poll_interval
|
||||||
|
|
||||||
if app_module.adsb_process.poll() is not None:
|
if app_module.adsb_process.poll() is not None:
|
||||||
# Process exited - release device and get error message
|
# Process exited - release device and get error message
|
||||||
app_module.release_sdr_device(device_int, sdr_type_str)
|
app_module.release_sdr_device(device_int, sdr_type_str)
|
||||||
|
adsb_active_device = None
|
||||||
|
adsb_active_sdr_type = None
|
||||||
stderr_output = ''
|
stderr_output = ''
|
||||||
if app_module.adsb_process.stderr:
|
if app_module.adsb_process.stderr:
|
||||||
try:
|
try:
|
||||||
@@ -872,8 +890,6 @@ def start_adsb():
|
|||||||
})
|
})
|
||||||
|
|
||||||
adsb_using_service = True
|
adsb_using_service = True
|
||||||
adsb_active_device = device # Track which device is being used
|
|
||||||
adsb_active_sdr_type = sdr_type_str
|
|
||||||
thread = threading.Thread(target=parse_sbs_stream, args=(f'localhost:{ADSB_SBS_PORT}',), daemon=True)
|
thread = threading.Thread(target=parse_sbs_stream, args=(f'localhost:{ADSB_SBS_PORT}',), daemon=True)
|
||||||
thread.start()
|
thread.start()
|
||||||
|
|
||||||
@@ -894,6 +910,8 @@ def start_adsb():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Release device on failure
|
# Release device on failure
|
||||||
app_module.release_sdr_device(device_int, sdr_type_str)
|
app_module.release_sdr_device(device_int, sdr_type_str)
|
||||||
|
adsb_active_device = None
|
||||||
|
adsb_active_sdr_type = None
|
||||||
return jsonify({'status': 'error', 'message': str(e)})
|
return jsonify({'status': 'error', 'message': str(e)})
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+39
-9
@@ -413,16 +413,50 @@ def probe_rtlsdr_device(device_index: int) -> str | None:
|
|||||||
lib_paths + [current_ld] if current_ld else lib_paths
|
lib_paths + [current_ld] if current_ld else lib_paths
|
||||||
)
|
)
|
||||||
|
|
||||||
result = subprocess.run(
|
# Use Popen with early termination instead of run() with full timeout.
|
||||||
|
# rtl_test prints device info to stderr quickly, then keeps running
|
||||||
|
# its test loop. We kill it as soon as we see success or failure.
|
||||||
|
proc = subprocess.Popen(
|
||||||
['rtl_test', '-d', str(device_index), '-t'],
|
['rtl_test', '-d', str(device_index), '-t'],
|
||||||
capture_output=True,
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
text=True,
|
text=True,
|
||||||
timeout=3,
|
|
||||||
env=env,
|
env=env,
|
||||||
)
|
)
|
||||||
output = result.stderr + result.stdout
|
|
||||||
|
|
||||||
if 'usb_claim_interface' in output or 'Failed to open' in output:
|
import select
|
||||||
|
error_found = False
|
||||||
|
deadline = time.monotonic() + 3.0
|
||||||
|
|
||||||
|
try:
|
||||||
|
while time.monotonic() < deadline:
|
||||||
|
remaining = deadline - time.monotonic()
|
||||||
|
if remaining <= 0:
|
||||||
|
break
|
||||||
|
# Wait for stderr output with timeout
|
||||||
|
ready, _, _ = select.select(
|
||||||
|
[proc.stderr], [], [], min(remaining, 0.1)
|
||||||
|
)
|
||||||
|
if ready:
|
||||||
|
line = proc.stderr.readline()
|
||||||
|
if not line:
|
||||||
|
break # EOF — process closed stderr
|
||||||
|
if 'usb_claim_interface' in line or 'Failed to open' in line:
|
||||||
|
error_found = True
|
||||||
|
break
|
||||||
|
if 'Found' in line and 'device' in line.lower():
|
||||||
|
# Device opened successfully — no need to wait longer
|
||||||
|
break
|
||||||
|
if proc.poll() is not None:
|
||||||
|
break # Process exited
|
||||||
|
finally:
|
||||||
|
try:
|
||||||
|
proc.kill()
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
proc.wait()
|
||||||
|
|
||||||
|
if error_found:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"RTL-SDR device {device_index} USB probe failed: "
|
f"RTL-SDR device {device_index} USB probe failed: "
|
||||||
f"device busy or unavailable"
|
f"device busy or unavailable"
|
||||||
@@ -434,10 +468,6 @@ def probe_rtlsdr_device(device_index: int) -> str | None:
|
|||||||
f'or try a different device.'
|
f'or try a different device.'
|
||||||
)
|
)
|
||||||
|
|
||||||
except subprocess.TimeoutExpired:
|
|
||||||
# rtl_test opened the device successfully and is running the
|
|
||||||
# test — that means the device *is* available.
|
|
||||||
pass
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.debug(f"RTL-SDR probe error for device {device_index}: {e}")
|
logger.debug(f"RTL-SDR probe error for device {device_index}: {e}")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user