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 =====