fix(satellite): Scan haengt nicht mehr ewig und blockiert keine anderen Auftraege
Der Satellit HomeOffice war verbunden (Heartbeat lief), beantwortete sat_discover aber nicht mehr — auch ein zweiter Scan blieb ohne Antwort. Ursache: Nachrichten werden nacheinander abgearbeitet (async for … await _handle), und der Scan lief im Loop. Haengt ein Schritt (mDNS/SSDP/ARP/SNMP/ ADB), steht der ganze Satellit — Tunnel und Befehle eingeschlossen. - sat_discover laeuft als eigener Task, der Loop bleibt frei. - Ein Scan ist auf SCAN_MAX_SEC (90 s) begrenzt; danach kommt die letzte bekannte Liste zurueck, und das Log nennt den haengenden Schritt. - Diagnostic: "Scanne Netz …" endet nach 2 Minuten mit einem Hinweis statt fuer immer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+14
-1
@@ -2763,7 +2763,7 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (msg.type === 'sat_devices') {
|
if (msg.type === 'sat_devices') {
|
||||||
if (msg.satellite) { satDevices[msg.satellite] = { devices: msg.devices || [], location: msg.location, ts: Date.now() }; }
|
if (msg.satellite) { satDevices[msg.satellite] = { devices: msg.devices || [], location: msg.location, ts: Date.now() }; delete satScanError[msg.satellite]; }
|
||||||
satScanning = null;
|
satScanning = null;
|
||||||
renderSatellites();
|
renderSatellites();
|
||||||
return;
|
return;
|
||||||
@@ -5634,6 +5634,7 @@
|
|||||||
let satellites = []; // [{id, location, caps, control, online}]
|
let satellites = []; // [{id, location, caps, control, online}]
|
||||||
let satDevices = {}; // id → {devices, location, ts}
|
let satDevices = {}; // id → {devices, location, ts}
|
||||||
let satScanning = null; // id des gerade scannenden Satelliten
|
let satScanning = null; // id des gerade scannenden Satelliten
|
||||||
|
const satScanError = {}; // id → Meldung, wenn ein Scan nicht beantwortet wurde
|
||||||
let satCreds = {}; // satId → { ip → [typen] } (welche Creds gesetzt sind, OHNE Secrets)
|
let satCreds = {}; // satId → { ip → [typen] } (welche Creds gesetzt sind, OHNE Secrets)
|
||||||
let credsModalSat = ''; // aktuell im Modal bearbeiteter Satellit
|
let credsModalSat = ''; // aktuell im Modal bearbeiteter Satellit
|
||||||
let credsModalIp = '';
|
let credsModalIp = '';
|
||||||
@@ -6161,10 +6162,20 @@
|
|||||||
}).join('');
|
}).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Antwortet der Satellit nicht, haengt "Scanne Netz …" sonst fuer immer.
|
||||||
|
// Der Satellit selbst bricht einen Scan nach 90 s ab (SCAN_MAX_SEC).
|
||||||
function scanSatellite(id) {
|
function scanSatellite(id) {
|
||||||
satScanning = id;
|
satScanning = id;
|
||||||
|
delete satScanError[id];
|
||||||
renderSatellites();
|
renderSatellites();
|
||||||
send({ action: 'sat_discover', satellite: id });
|
send({ action: 'sat_discover', satellite: id });
|
||||||
|
setTimeout(() => {
|
||||||
|
if (satScanning !== id) return;
|
||||||
|
satScanning = null;
|
||||||
|
satScanError[id] = 'Keine Antwort nach 2 Minuten. Der Satellit ist verbunden, beantwortet den Scan aber nicht — '
|
||||||
|
+ 'vermutlich hängt er. Satellit neu starten (bzw. aktualisieren) hilft.';
|
||||||
|
renderSatellites();
|
||||||
|
}, 120000);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Anzeigenamen fuer einen Satelliten oder Host-Agenten vergeben.
|
/** Anzeigenamen fuer einen Satelliten oder Host-Agenten vergeben.
|
||||||
@@ -6218,6 +6229,8 @@
|
|||||||
let devHtml;
|
let devHtml;
|
||||||
if (scanning) {
|
if (scanning) {
|
||||||
devHtml = '<div style="color:#FFD60A;margin-top:6px;">Scanne Netz …</div>';
|
devHtml = '<div style="color:#FFD60A;margin-top:6px;">Scanne Netz …</div>';
|
||||||
|
} else if (satScanError[s.id]) {
|
||||||
|
devHtml = '<div style="color:#FF9F0A;margin-top:6px;">⚠ ' + escapeHtml(satScanError[s.id]) + '</div>';
|
||||||
} else if (dev) {
|
} else if (dev) {
|
||||||
if (!dev.devices.length) {
|
if (!dev.devices.length) {
|
||||||
devHtml = '<div style="color:#8888AA;margin-top:6px;">Keine Geraete gefunden.</div>';
|
devHtml = '<div style="color:#8888AA;margin-top:6px;">Keine Geraete gefunden.</div>';
|
||||||
|
|||||||
+46
-24
@@ -181,6 +181,9 @@ APK_MAX_BYTES = int(os.environ.get("APK_MAX_BYTES", str(64 * 1024 * 1024)) or st
|
|||||||
|
|
||||||
SCAN_INTERVAL_SEC = int(os.environ.get("SCAN_INTERVAL_SEC", "300") or "300")
|
SCAN_INTERVAL_SEC = int(os.environ.get("SCAN_INTERVAL_SEC", "300") or "300")
|
||||||
DISCOVER_TIMEOUT_SEC = float(os.environ.get("DISCOVER_TIMEOUT_SEC", "6") or "6")
|
DISCOVER_TIMEOUT_SEC = float(os.environ.get("DISCOVER_TIMEOUT_SEC", "6") or "6")
|
||||||
|
# Obergrenze fuer einen ganzen Scan. Haengt ein Schritt (mDNS, SNMP, ADB …),
|
||||||
|
# kommt nach dieser Zeit die letzte bekannte Liste zurueck statt gar nichts.
|
||||||
|
SCAN_MAX_SEC = float(os.environ.get("SCAN_MAX_SEC", "90") or "90")
|
||||||
DEVICE_CACHE_TTL_SEC = int(os.environ.get("DEVICE_CACHE_TTL_SEC", "120") or "120")
|
DEVICE_CACHE_TTL_SEC = int(os.environ.get("DEVICE_CACHE_TTL_SEC", "120") or "120")
|
||||||
|
|
||||||
# http.get/http.post: Body-Ausschnitt. Default grosszuegig (ganze Statusseiten
|
# http.get/http.post: Body-Ausschnitt. Default grosszuegig (ganze Statusseiten
|
||||||
@@ -1797,6 +1800,7 @@ class Satellite:
|
|||||||
self._devices: list[dict] = []
|
self._devices: list[dict] = []
|
||||||
self._devices_ts: float = 0.0
|
self._devices_ts: float = 0.0
|
||||||
self._scanning = False
|
self._scanning = False
|
||||||
|
self._scan_stage = ""
|
||||||
# Offene Port-Tunnel: tunnelId -> {"writer", "task", "last", "ziel"}
|
# Offene Port-Tunnel: tunnelId -> {"writer", "task", "last", "ziel"}
|
||||||
self._tunnels: dict[str, dict] = {}
|
self._tunnels: dict[str, dict] = {}
|
||||||
|
|
||||||
@@ -1812,26 +1816,40 @@ class Satellite:
|
|||||||
break
|
break
|
||||||
return self._devices
|
return self._devices
|
||||||
self._scanning = True
|
self._scanning = True
|
||||||
|
self._scan_stage = "start"
|
||||||
try:
|
try:
|
||||||
loop = asyncio.get_event_loop()
|
await asyncio.wait_for(self._scan_inner(), SCAN_MAX_SEC)
|
||||||
mdns = await loop.run_in_executor(None, _discover_mdns, DISCOVER_TIMEOUT_SEC)
|
except asyncio.TimeoutError:
|
||||||
ssdp = await loop.run_in_executor(None, _discover_ssdp, DISCOVER_TIMEOUT_SEC)
|
logger.warning("[scan] nach %.0fs abgebrochen — haengt bei: %s. Liefere die letzte Liste (%d Geraete).",
|
||||||
arp = await loop.run_in_executor(None, _discover_arp)
|
SCAN_MAX_SEC, self._scan_stage, len(self._devices))
|
||||||
_self = [_self_device()] if LIST_SELF else []
|
|
||||||
_self = [d for d in _self if d]
|
|
||||||
self._devices = _apply_names(_merge_devices(mdns, ssdp, arp, _self))
|
|
||||||
if SNMP_DISCOVERY:
|
|
||||||
await self._enrich_snmp(self._devices)
|
|
||||||
await self._enrich_adb(self._devices)
|
|
||||||
self._devices_ts = time.time()
|
|
||||||
n_snmp = sum(1 for d in self._devices if d.get("snmpCapable"))
|
|
||||||
n_adb = sum(1 for d in self._devices if d.get("adbCapable"))
|
|
||||||
logger.info("[scan] %d Geraete (mdns=%d ssdp=%d arp=%d, snmp=%d, adb=%d)",
|
|
||||||
len(self._devices), len(mdns), len(ssdp), len(arp), n_snmp, n_adb)
|
|
||||||
finally:
|
finally:
|
||||||
self._scanning = False
|
self._scanning = False
|
||||||
return self._devices
|
return self._devices
|
||||||
|
|
||||||
|
async def _scan_inner(self) -> None:
|
||||||
|
"""Ein kompletter Scan; _scan begrenzt ihn auf SCAN_MAX_SEC."""
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
self._scan_stage = "mdns"
|
||||||
|
mdns = await loop.run_in_executor(None, _discover_mdns, DISCOVER_TIMEOUT_SEC)
|
||||||
|
self._scan_stage = "ssdp"
|
||||||
|
ssdp = await loop.run_in_executor(None, _discover_ssdp, DISCOVER_TIMEOUT_SEC)
|
||||||
|
self._scan_stage = "arp"
|
||||||
|
arp = await loop.run_in_executor(None, _discover_arp)
|
||||||
|
_self = [_self_device()] if LIST_SELF else []
|
||||||
|
_self = [d for d in _self if d]
|
||||||
|
devices = _apply_names(_merge_devices(mdns, ssdp, arp, _self))
|
||||||
|
if SNMP_DISCOVERY:
|
||||||
|
self._scan_stage = "snmp"
|
||||||
|
await self._enrich_snmp(devices)
|
||||||
|
self._scan_stage = "adb"
|
||||||
|
await self._enrich_adb(devices)
|
||||||
|
self._devices = devices
|
||||||
|
self._devices_ts = time.time()
|
||||||
|
n_snmp = sum(1 for d in self._devices if d.get("snmpCapable"))
|
||||||
|
n_adb = sum(1 for d in self._devices if d.get("adbCapable"))
|
||||||
|
logger.info("[scan] %d Geraete (mdns=%d ssdp=%d arp=%d, snmp=%d, adb=%d)",
|
||||||
|
len(self._devices), len(mdns), len(ssdp), len(arp), n_snmp, n_adb)
|
||||||
|
|
||||||
async def _enrich_snmp(self, devices: list) -> None:
|
async def _enrich_snmp(self, devices: list) -> None:
|
||||||
"""Fragt jedes Geraet mit IP parallel per SNMP-System-Group ab und haengt
|
"""Fragt jedes Geraet mit IP parallel per SNMP-System-Group ab und haengt
|
||||||
die Infos an. Verbessert Name/Typ, wenn bisher nur eine IP bekannt war."""
|
die Infos an. Verbessert Name/Typ, wenn bisher nur eine IP bekannt war."""
|
||||||
@@ -2020,6 +2038,16 @@ class Satellite:
|
|||||||
return True
|
return True
|
||||||
return target in (SATELLITE_ID.lower(), SATELLITE_LOCATION.lower())
|
return target in (SATELLITE_ID.lower(), SATELLITE_LOCATION.lower())
|
||||||
|
|
||||||
|
async def _discover_reply(self, payload: dict) -> None:
|
||||||
|
devices = await self._scan(force=bool(payload.get("force")))
|
||||||
|
await self._send({
|
||||||
|
"type": "sat_devices",
|
||||||
|
"payload": {"requestId": payload.get("requestId", ""), "satellite": SATELLITE_ID,
|
||||||
|
"location": SATELLITE_LOCATION, "devices": devices,
|
||||||
|
"net": NET},
|
||||||
|
"timestamp": int(time.time() * 1000),
|
||||||
|
})
|
||||||
|
|
||||||
async def _handle(self, raw: str) -> None:
|
async def _handle(self, raw: str) -> None:
|
||||||
try:
|
try:
|
||||||
msg = json.loads(raw)
|
msg = json.loads(raw)
|
||||||
@@ -2031,15 +2059,9 @@ class Satellite:
|
|||||||
if mtype == "sat_discover":
|
if mtype == "sat_discover":
|
||||||
if not self._for_me(payload):
|
if not self._for_me(payload):
|
||||||
return
|
return
|
||||||
req_id = payload.get("requestId", "")
|
# Eigener Task: ein langer Scan darf den Nachrichten-Loop (Tunnel,
|
||||||
devices = await self._scan(force=bool(payload.get("force")))
|
# Befehle, weitere Scans) nicht anhalten.
|
||||||
await self._send({
|
asyncio.create_task(self._discover_reply(payload))
|
||||||
"type": "sat_devices",
|
|
||||||
"payload": {"requestId": req_id, "satellite": SATELLITE_ID,
|
|
||||||
"location": SATELLITE_LOCATION, "devices": devices,
|
|
||||||
"net": NET},
|
|
||||||
"timestamp": int(time.time() * 1000),
|
|
||||||
})
|
|
||||||
|
|
||||||
elif mtype == "sat_tunnel_open":
|
elif mtype == "sat_tunnel_open":
|
||||||
if not self._for_me(payload):
|
if not self._for_me(payload):
|
||||||
|
|||||||
Reference in New Issue
Block a user