From e16cf50f2c99fc28d92b7f77c6f335bd300080eb Mon Sep 17 00:00:00 2001 From: foxyz Date: Sat, 17 Oct 2026 02:04:55 +0300 Subject: [PATCH] Windows: every start ended itself after 3 s (1.3.0, 1.3.1) Win32Window::Create calls Destroy() before it makes the window, which ran FlutterWindow::OnDestroy and armed the 3 s exit timer at launch: the window showed, then the process ended with code 0, no crash, no close lines. The timer is only armed with an engine to tear down. Found and fixed by Fun Maker's agent on Windows (a debugger caught the exit on the timer's thread). The smoke runs missed it (they closed at ~4 s anyway): the app now stays up 10 s, and the scripts require 'closing' and 'quitting' from its own close. Co-Authored-By: Claude Opus 5.5 --- apps/desktop/lib/src/platform/smoke.dart | 16 ++++++++++++---- apps/desktop/windows/runner/flutter_window.cpp | 6 +++++- docs/ARCHITECTURE.md | 4 +++- docs/DEVELOPMENT.md | 10 ++++++---- tool/smoke_linux.sh | 2 ++ tool/smoke_windows.ps1 | 2 ++ 6 files changed, 30 insertions(+), 10 deletions(-) diff --git a/apps/desktop/lib/src/platform/smoke.dart b/apps/desktop/lib/src/platform/smoke.dart index 3438881..6f9b8ea 100644 --- a/apps/desktop/lib/src/platform/smoke.dart +++ b/apps/desktop/lib/src/platform/smoke.dart @@ -12,9 +12,14 @@ import 'ports/player_host.dart'; // in a session player with a video output through the media pipeline, // play a second and close the normal way ([shutDownApp]). Progress goes to // `STARSYNC_SMOKE_OUT` (one line per step: `start`, `opened render=`, -// `closing`, or `skip ` / `fail `), so the script can tell a -// runner that cannot show a window (skip) from a close that hangs or -// leaves a process behind (fail). +// `closing`, `quitting`, or `skip ` / `fail `), so the script can +// tell a runner that cannot show a window (skip) from a close that hangs or +// leaves a process behind, or a process that ends on its own (fail). It +// stays up [smokeUptime] first: a start that dies by itself (the 1.3.0 +// Windows exit timer) ends before `closing`. + +/// How long the smoke run keeps the app up before closing it. +const smokeUptime = Duration(seconds: 10); /// The smoke run's media, or null for a normal start. String? get smokeMedia { @@ -65,6 +70,9 @@ Future runSmoke(AppGraph graph, String media) async { } catch (e) { step('fail $e'); } + final wait = smokeUptime - sw.elapsed; + if (wait > Duration.zero) await Future.delayed(wait); step('closing'); - await shutDownApp(); + // Reached only through the app's own close (not a kill on the way). + await shutDownApp(beforeQuit: () async => step('quitting')); } diff --git a/apps/desktop/windows/runner/flutter_window.cpp b/apps/desktop/windows/runner/flutter_window.cpp index 6af950a..83ae4aa 100644 --- a/apps/desktop/windows/runner/flutter_window.cpp +++ b/apps/desktop/windows/runner/flutter_window.cpp @@ -80,7 +80,11 @@ bool FlutterWindow::OnCreate() { void FlutterWindow::OnDestroy() { // The engine and the plugins go now; the app released its players first. - ExitAfter(3000); + // Win32Window::Create runs this too, before there is an engine: only a + // real teardown may end the process (else every start dies at 3 s). + if (flutter_controller_) { + ExitAfter(3000); + } window_channel_ = nullptr; if (flutter_controller_) { flutter_controller_ = nullptr; diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 59746bf..63a7604 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -2293,7 +2293,9 @@ app kills itself (`Process.killPid`, no exit handlers; it only gets there if the close did not go through); the runners end the process 15 s after the first close request (Linux: a `delete-event` handler connected before window_manager's; Windows: the first `WM_CLOSE`), and Windows also 3 s -after the window is destroyed, while the engine and plugins go. +after the window is destroyed, while the engine and plugins go (only +with an engine: `Win32Window::Create` runs `OnDestroy` before the window +exists, and a timer armed there ended every start at 3 s). ## Logging diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index d119034..da305e5 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -210,10 +210,12 @@ updater tests, the release build and the close smoke run on that build `STARSYNC_SMOKE=` (a 5 s clip from ffmpeg, else a WAV the script writes) under its own profile (`STARSYNC_PROFILE=smoke`), opens the clip in a player with a video output through the media pipeline -(`platform/smoke.dart`), plays a second and closes the normal way; the -steps go to `STARSYNC_SMOKE_OUT` (`start`, `opened render=`, -`closing`, or `skip` / `fail` with why). It fails when the app does not -exit within 45 s or leaves a `starsync` process, skips with a warning +(`platform/smoke.dart`), plays it, stays up 10 s from its start and +closes the normal way; the steps go to `STARSYNC_SMOKE_OUT` (`start`, +`opened render=`, `closing`, `quitting` just before the +window closes, or `skip` / `fail` with why). It fails when the app ends +by itself before `closing` or never reaches `quitting`, does not exit +within 45 s or leaves a `starsync` process, skips with a warning when the runner cannot bring the app or its video output up, and prints the `smoke:`, `close:` and `mpv: render` lines of the app log. `tool/smoke_linux.sh` is the same on Linux (needs a display; after diff --git a/tool/smoke_linux.sh b/tool/smoke_linux.sh index 266df61..2c96a1a 100755 --- a/tool/smoke_linux.sh +++ b/tool/smoke_linux.sh @@ -34,6 +34,8 @@ grep -qx start "$OUT" 2>/dev/null || { [[ $exited == 1 ]] && { echo "skipped: th grep -q '^skip ' "$OUT" && { echo "skipped: $(grep '^skip ' "$OUT")"; exit 0; } grep -q '^fail ' "$OUT" && { echo "smoke failed: $(grep '^fail ' "$OUT")" >&2; exit 1; } grep -q '^opened ' "$OUT" || { echo "the media never opened" >&2; exit 1; } +grep -qx closing "$OUT" || { echo "the app ended on its own before closing" >&2; exit 1; } +grep -qx quitting "$OUT" || { echo "the close did not reach its end (killed on the way?)" >&2; exit 1; } [[ $exited == 1 ]] || { echo "the app did not close within $LIMIT s (P32)" >&2; exit 1; } [[ -z "$left" ]] || { echo "a starsync process was left behind (P32)" >&2; exit 1; } echo "smoke ok: opened and closed in $secs s, no process left" diff --git a/tool/smoke_windows.ps1 b/tool/smoke_windows.ps1 index b774fdb..ff7fe3b 100644 --- a/tool/smoke_windows.ps1 +++ b/tool/smoke_windows.ps1 @@ -64,6 +64,8 @@ if (-not ($lines -contains 'start')) { if ($lines | Where-Object { $_ -like 'skip *' }) { Write-Warning "skipped: $($lines -like 'skip *')"; exit 0 } if ($lines | Where-Object { $_ -like 'fail *' }) { Write-Error "smoke failed: $($lines -like 'fail *')"; exit 1 } if (-not ($lines | Where-Object { $_ -like 'opened *' })) { Write-Error 'the media never opened'; exit 1 } +if (-not ($lines -contains 'closing')) { Write-Error "the app ended on its own before closing (exit $($p.ExitCode))"; exit 1 } +if (-not ($lines -contains 'quitting')) { Write-Error 'the close did not reach its end (killed on the way?)'; exit 1 } if (-not $exited) { Write-Error "the app did not close within $Limit s (P32)"; exit 1 } if ($left) { Write-Error "a starsync process was left behind (P32)"; exit 1 } Write-Host "smoke ok: opened and closed in $secs s, no process left"