diff --git a/src/PolyTrader.App.Avalonia/Program.cs b/src/PolyTrader.App.Avalonia/Program.cs
index 90cbf1f..b64c48e 100644
--- a/src/PolyTrader.App.Avalonia/Program.cs
+++ b/src/PolyTrader.App.Avalonia/Program.cs
@@ -120,7 +120,21 @@ namespace PolyTrader.App.Avalonia
finally
{
// Geordnetes Herunterfahren der Module – erst nach dem Ende der Oberfläche.
- host.StopAsync(TimeSpan.FromSeconds(30)).GetAwaiter().GetResult();
+ //
+ // NICHT direkt auf diesem Thread warten. Nach StartWithClassicDesktopLifetime trägt der
+ // Main-Thread noch den SynchronizationContext von Avalonia, dessen Dispatcher aber nicht
+ // mehr pumpt. Eine Fortsetzung im Stop-Pfad, die dorthin zurückwill, läuft dann nie - der
+ // Prozess blieb nach "Beenden" am 14.09.2026 auf der Linux-VM unbegrenzt stehen: Fenster
+ // offen, Port zu, Main-Thread in GetResult(), selbst SIGTERM ohne Wirkung. Damit griff
+ // auch kein Update, der update-agent wartete vergeblich auf das Prozessende.
+ // Task.Run startet ohne diesen Kontext; die Obergrenze fängt zusätzlich einen Dienst ab,
+ // der sein Token nicht beachtet.
+ var sw = System.Diagnostics.Stopwatch.StartNew();
+ var stop = Task.Run(() => host.StopAsync(TimeSpan.FromSeconds(30)));
+ if (stop.Wait(TimeSpan.FromSeconds(45)))
+ bootLog.Info($"Dienste in {sw.Elapsed.TotalSeconds:0.0} s beendet.");
+ else
+ bootLog.Warning("Dienste nach 45 s noch nicht beendet - Anwendung wird trotzdem geschlossen.");
host.Dispose();
}
}
diff --git a/src/PolyTrader.App.Avalonia/Shell/AvaloniaUiHost.cs b/src/PolyTrader.App.Avalonia/Shell/AvaloniaUiHost.cs
index 238d97b..33d3260 100644
--- a/src/PolyTrader.App.Avalonia/Shell/AvaloniaUiHost.cs
+++ b/src/PolyTrader.App.Avalonia/Shell/AvaloniaUiHost.cs
@@ -100,7 +100,18 @@ namespace PolyTrader.App.Avalonia.Shell
/// Zeigt die Sicherheitsabfrage (Beenden-Schaltfläche 10 s gesperrt) und fährt bei Bestätigung
/// herunter. Aus jeder Seite aufrufbar – auch aus Modul-Seiten, die die App nicht kennen.
///
- public async void RequestShutdown()
+ public void RequestShutdown() => RequestShutdown(onConfirmed: null);
+
+ ///
+ /// Wie , mit einem Schritt zwischen Bestätigung und Herunterfahren.
+ ///
+ ///
+ /// Läuft erst, wenn die Sicherheitsabfrage bestätigt ist - liefert er false, wird nicht
+ /// beendet. Für den Update-Weg: Der update-agent darf erst starten, wenn feststeht, dass die
+ /// Anwendung auch endet. Vorher lief seine Wartefrist schon während der 10-Sekunden-Sperre ab,
+ /// und nach „Abbrechen" wartete er ins Leere.
+ ///
+ public async void RequestShutdown(Func? onConfirmed)
{
if (ShutdownConfirmed || _shutdownDialogOpen) return;
_shutdownDialogOpen = true;
@@ -111,6 +122,7 @@ namespace PolyTrader.App.Avalonia.Shell
? await dlg.ShowDialog(_shell)
: await dlg.ShowDialog(new Window());
if (!confirmed) return;
+ if (onConfirmed != null && !onConfirmed()) return;
ShutdownConfirmed = true;
if (global::Avalonia.Application.Current?.ApplicationLifetime
diff --git a/src/PolyTrader.App.Avalonia/Views/LauncherWindow.axaml.cs b/src/PolyTrader.App.Avalonia/Views/LauncherWindow.axaml.cs
index d79285d..d4f43f1 100644
--- a/src/PolyTrader.App.Avalonia/Views/LauncherWindow.axaml.cs
+++ b/src/PolyTrader.App.Avalonia/Views/LauncherWindow.axaml.cs
@@ -269,7 +269,12 @@ namespace PolyTrader.App.Avalonia.Views
try { licenseKey = SecretProtection.Unprotect(settings?.LicenseKey ?? string.Empty); }
catch { /* siehe CheckForUpdateAsync */ }
- UpdateClient.LaunchUpdateAgent(
+ // Derselbe Weg wie ein Klick auf "Beenden" - inklusive Sicherheitsabfrage. Der Agent startet
+ // aber erst NACH deren Bestätigung: Am 14.09.2026 lief er schon beim Klick auf "Installieren"
+ // los, seine 40 s Wartefrist verstrichen während Download und 10-Sekunden-Sperre, und er
+ // brach ohne Update ab. 120 s decken das Herunterfahren ab (Dienste bis 45 s, siehe
+ // Program.Main); läuft die Anwendung danach noch, verändert der Agent nichts.
+ Func launchAgent = () => UpdateClient.LaunchUpdateAgent(
agentPath: agentPath,
projectId: dcOptions.ProjectSlug,
channel: dcOptions.Channel,
@@ -278,12 +283,13 @@ namespace PolyTrader.App.Avalonia.Views
currentVersion: BuildInfo.Version,
exitCurrentApp: false,
waitForCurrentProcess: true,
- waitTimeoutSeconds: 40,
+ waitTimeoutSeconds: 120,
licenseKey: licenseKey);
- // Derselbe Weg wie ein Klick auf "Beenden" - inklusive Sicherheitsabfrage. Der Agent
- // wartet ohnehin per --wait-for-pid auf unser Prozessende.
- _uiHost.RequestShutdown();
+ if (_uiHost is AvaloniaUiHost shell)
+ shell.RequestShutdown(onConfirmed: launchAgent);
+ else if (launchAgent())
+ _uiHost.RequestShutdown();
}
// ===== Account-Übersicht =====