Fix Haenger beim Anlegen eines DHCP-Pools (bugs.md #11)

SSHTransport.connect()/run() hatten keinen Timeout - im Gegensatz zu
RestTransport (timeoutInterval=5). Jeder erste Schreibvorgang einer
Session loest ueber ensureSessionBackup() eine dedizierte SSH-
Verbindung aus; haengt die, blieb isApplying unbegrenzt aktiv (kein
Fehler, kein Recovery, nur Force-Quit). Live vom Nutzer bestaetigt
(Experte-Tab, dauerhaft haengend) bevor der Fix geschrieben wurde.

Neuer genererischer SSHTransport.withTimeout(_:operation:) (Task-
Group-Race gegen eine Deadline), angewendet auf connect() (10s) und
run() (30s). 3 neue Regressionstests fuer die Race-Logik isoliert.

Nebenbefund: xcodegen generate ueberschreibt Info.plist komplett aus
project.yml (kein Merge) - ein Regenerieren fuer die neue Testdatei
setzte die Version stillschweigend von 1.1.0 auf 1.0 zurueck. Version
jetzt explizit in project.yml verankert.

Build + alle 106 Unit-Tests gruen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Kay
2026-09-17 21:51:11 +02:00
co-authored by Claude Sonnet 5
parent e9e701a94d
commit cae59db39e
4 changed files with 141 additions and 35 deletions
+24 -10
View File
@@ -324,14 +324,14 @@ Nutzer, da UI-Automatisierung hier nicht verfügbar ist.
## 2026-09-17 (Nachtrag 4: Nutzer-gemeldet, live in der App)
### 11. Hänger beim Anlegen eines DHCP-Pools
**Status:** offen (Nutzer-Meldung, Ursache noch nicht bestätigt — siehe Code-Audit unten)
**Confidence:** mittel (plausibler Root-Cause-Kandidat per Code-Audit gefunden, noch nicht live
reproduziert/bestätigt, da UI-Automatisierung nicht verfügbar ist)
**Status:** fixed (Build + 106 Unit-Tests grün, 3 neue Regressionstests — Mechanismus isoliert
verifiziert, Root Cause vom Nutzer bestätigt, nicht erneut live im UI nachgestellt)
**Confidence:** hoch (Nutzer bestätigte exakt das vorhergesagte Bild — Experte-Tab, dauerhaft
hängend, kein Fehlertext — bevor der Fix geschrieben wurde)
Nutzer-Meldung: "Hänger beim Anlegen eines DHCP-Pools" — App bleibt beim Anlegen eines
`/ip pool`-Eintrags hängen. Noch keine weiteren Details bekannt (welcher Bereich — Experte-Tab
oder Einrichten-Assistent LAN-Schritt; ob sich die App von selbst erholt oder ein Neustart nötig
ist; ob vorher eine Fehlermeldung erscheint).
`/ip pool`-Eintrags hängen. Rückfrage bestätigte: Experte-Tab, bleibt dauerhaft hängen (kein
Selbst-Erholen, Neustart nötig) — exakt das Bild, das der Code-Audit-Kandidat vorhergesagt hatte.
**Code-Audit-Befund, plausibler Kandidat:** `SSHTransport.connect()` (`SSHTransport.swift`) setzt
für den `Citadel.SSHClient.connect(...)`-Aufruf **keinerlei Timeout** — im Gegensatz zu
@@ -350,7 +350,21 @@ plausibler Kandidat, kein reproduzierter Fehler. Gleiche Lücke beträfe auch `C
applyViaSSH`/`flushConnections` (beide nutzen ebenfalls `SSHTransport.connect()` ohne Timeout) und
`UpdateService`/`FactoryResetService`/`NetworkToolsService`, die dieselbe Transport-Klasse nutzen.
**Fix-Ansatz (noch nicht umgesetzt):** `SSHTransport.connect()` mit einem Timeout umgeben (z.B.
`withThrowingTaskGroup`/`Task.sleep`-Race, ähnlich wie RESTs `timeoutInterval`), damit ein
hängender Verbindungsaufbau nach einigen Sekunden als Fehler zurückkommt statt die UI unbegrenzt
zu blockieren.
**Fix:** neuer generischer `SSHTransport.withTimeout(_:operation:)` (`withThrowingTaskGroup`-Race
zwischen der echten Operation und einem `Task.sleep`-Deadline-Task, verliert die Operation den
Wettlauf wird sie abgebrochen). Angewendet auf `connect()` (10s) und `run(_:)` (30s, großzügiger
bemessen, da Exports/Backups auf schwacher Hardware legitim länger brauchen können). Beide
Methoden bleiben `async throws`, keine Signaturänderung für Aufrufer.
**Verifikation:** die Race-Logik selbst ist per 3 neuer Unit-Tests isoliert bewiesen (unabhängig
von Citadel/echtem Netzwerk) — ein hängender Vorgang wirft nach der Deadline (getestet mit
200ms statt Produktions-Werten, damit der Test schnell bleibt), ein schneller Vorgang liefert sein
Ergebnis unverändert, ein eigener Fehler der Operation selbst propagiert unverändert durch. Nicht
erneut live im Experte-Tab nachgestellt (bräuchte einen erneuten, absichtlich provozierten
Netzwerk-Hänger) — der Nutzer hat die Ursache aber bereits vor dem Fix exakt bestätigt.
Nebenbefund beim Umsetzen: `xcodegen generate` überschreibt `Info.plist` komplett aus
`project.yml`s `info.properties` (kein Merge mit der Datei auf der Platte) — ein Regenerieren für
diese neue Testdatei setzte die App-Version dabei stillschweigend von 1.1.0 auf 1.0 zurück.
`CFBundleShortVersionString`/`CFBundleVersion` jetzt explizit in `project.yml` verankert, damit
das nicht wieder passiert.