From 14c89aea7d008b52d3111765d3ad2c469d1a8763 Mon Sep 17 00:00:00 2001 From: Richard Date: Mon, 14 Sep 2026 11:52:06 +0200 Subject: [PATCH] Beenden haengt nicht mehr, Update-Agent startet erst nach Bestaetigung Beim Updatetest auf der Linux-VM (14.09.2026) zeigte sich zweierlei: 1. Nach "Beenden" blieb der Prozess unbegrenzt stehen - Fenster offen, Port zu, Main-Thread in host.StopAsync(...).GetResult(), selbst SIGTERM ohne Wirkung. Betraf jedes Beenden der Oberflaeche, nicht nur den Update-Weg. Der Main-Thread traegt nach StartWithClassicDesktopLifetime noch Avalonias SynchronizationContext, dessen Dispatcher nicht mehr pumpt. Der Stopp laeuft jetzt per Task.Run ohne diesen Kontext, mit Obergrenze 45 s und Log der Dauer. 2. Der update-agent startete schon beim Klick auf "Installieren". Seine 40 s Wartefrist verstrichen waehrend Download und 10-Sekunden-Sperre, er brach ohne Update ab. Er startet jetzt erst nach bestaetigtem Beenden, Frist 120 s. Co-Authored-By: Claude Opus 5 --- src/PolyTrader.App.Avalonia/Program.cs | 16 +++++++++++++++- .../Shell/AvaloniaUiHost.cs | 14 +++++++++++++- .../Views/LauncherWindow.axaml.cs | 16 +++++++++++----- 3 files changed, 39 insertions(+), 7 deletions(-) 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 =====