fix: close leaked file descriptors on mode switch (#169)

SSE EventSource connections for AIS, ACARS, VDL2, and radiosonde were
not closed when switching modes, causing fd exhaustion after repeated
switches. Also fixes socket leaks on exception paths in AIS/ADS-B
stream parsers, closes subprocess pipes in safe_terminate/cleanup, and
caches skyfield timescale at module level to avoid per-request fd churn.

Closes #169

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Smittix
2026-03-02 13:38:21 +00:00
parent ff9961b846
commit 8379f42ec3
10 changed files with 99 additions and 38 deletions
+4
View File
@@ -4158,6 +4158,10 @@
sstv_general: () => typeof SSTVGeneral !== 'undefined' && SSTVGeneral.destroy?.(),
websdr: () => typeof WebSDR !== 'undefined' && WebSDR.destroy?.(),
spystations: () => typeof SpyStations !== 'undefined' && SpyStations.destroy?.(),
ais: () => { if (aisEventSource) { aisEventSource.close(); aisEventSource = null; } },
acars: () => { if (acarsMainEventSource) { acarsMainEventSource.close(); acarsMainEventSource = null; } },
vdl2: () => { if (vdl2MainEventSource) { vdl2MainEventSource.close(); vdl2MainEventSource = null; } },
radiosonde: () => { if (radiosondeEventSource) { radiosondeEventSource.close(); radiosondeEventSource = null; } },
};
if (previousMode && previousMode !== mode && moduleDestroyMap[previousMode]) {
try { moduleDestroyMap[previousMode](); } catch(e) { console.warn(`[switchMode] destroy ${previousMode} failed:`, e); }
+3 -1
View File
@@ -254,7 +254,9 @@
acarsMainEventSource.onerror = function() {
setTimeout(() => {
if (document.getElementById('stopAcarsBtn').style.display === 'block') {
const panel = document.getElementById('acarsMode');
if (panel && panel.classList.contains('active') &&
document.getElementById('stopAcarsBtn').style.display === 'block') {
startAcarsMainSSE();
}
}, 2000);
+3 -1
View File
@@ -164,7 +164,9 @@
aisEventSource.onerror = function() {
setTimeout(() => {
if (document.getElementById('stopAisBtn').style.display === 'block') {
const panel = document.getElementById('aisMode');
if (panel && panel.classList.contains('active') &&
document.getElementById('stopAisBtn').style.display === 'block') {
startAisSSE();
}
}, 2000);
+3 -1
View File
@@ -220,7 +220,9 @@
radiosondeEventSource.onerror = function() {
setTimeout(() => {
if (document.getElementById('stopRadiosondeBtn').style.display === 'block') {
const panel = document.getElementById('radiosondeMode');
if (panel && panel.classList.contains('active') &&
document.getElementById('stopRadiosondeBtn').style.display === 'block') {
startRadiosondeSSE();
}
}, 2000);
+29 -27
View File
@@ -169,32 +169,32 @@
.catch(err => alert('Error: ' + err.message));
}
function stopVdl2Mode() {
fetch('/vdl2/stop', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ source: 'vdl2_mode' })
})
.then(async (r) => {
const text = await r.text();
const data = text ? JSON.parse(text) : {};
if (!r.ok || (data.status !== 'stopped' && data.status !== 'success')) {
throw new Error(data.message || `HTTP ${r.status}`);
}
return data;
})
.then(() => {
document.getElementById('startVdl2Btn').style.display = 'block';
document.getElementById('stopVdl2Btn').style.display = 'none';
document.getElementById('vdl2StatusText').textContent = 'Standby';
document.getElementById('vdl2StatusText').style.color = 'var(--accent-yellow)';
if (vdl2MainEventSource) {
vdl2MainEventSource.close();
vdl2MainEventSource = null;
}
})
.catch(err => alert('Failed to stop VDL2: ' + err.message));
}
function stopVdl2Mode() {
fetch('/vdl2/stop', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ source: 'vdl2_mode' })
})
.then(async (r) => {
const text = await r.text();
const data = text ? JSON.parse(text) : {};
if (!r.ok || (data.status !== 'stopped' && data.status !== 'success')) {
throw new Error(data.message || `HTTP ${r.status}`);
}
return data;
})
.then(() => {
document.getElementById('startVdl2Btn').style.display = 'block';
document.getElementById('stopVdl2Btn').style.display = 'none';
document.getElementById('vdl2StatusText').textContent = 'Standby';
document.getElementById('vdl2StatusText').style.color = 'var(--accent-yellow)';
if (vdl2MainEventSource) {
vdl2MainEventSource.close();
vdl2MainEventSource = null;
}
})
.catch(err => alert('Failed to stop VDL2: ' + err.message));
}
function startVdl2MainSSE() {
if (vdl2MainEventSource) vdl2MainEventSource.close();
@@ -212,7 +212,9 @@
vdl2MainEventSource.onerror = function() {
setTimeout(() => {
if (document.getElementById('stopVdl2Btn').style.display === 'block') {
const panel = document.getElementById('vdl2Mode');
if (panel && panel.classList.contains('active') &&
document.getElementById('stopVdl2Btn').style.display === 'block') {
startVdl2MainSSE();
}
}, 2000);