@@ -133,11 +133,16 @@ class EngineBundledLauncher {
133133 }
134134
135135 if (! forceRestart) {
136- // If the dashboard previously wrote "starting" but no PID was ever recorded
137- // (e.g. first install, app closed quickly, or reboot), waiting a full health
138- // window just delays startup and can look like a hang.
139- final shouldWaitForExisting =
140- cfg.pid != 0 && cfg.status != EngineStatus .stopped;
136+ // Wait for an in-flight or already-up engine on disk. Do not require pid != 0:
137+ // reconciled / external engines may show running with pid 0 until the next
138+ // writer refreshes; HTTP health is the real signal.
139+ final shouldWaitForExisting = cfg.status != EngineStatus .stopped &&
140+ cfg.status != EngineStatus .failed &&
141+ (cfg.pid != 0 ||
142+ cfg.status == EngineStatus .starting ||
143+ cfg.status == EngineStatus .healthChecking ||
144+ cfg.status == EngineStatus .running ||
145+ cfg.status == EngineStatus .restarting);
141146 final diskRunning = shouldWaitForExisting
142147 ? await _waitRunningOnDiskWithin (cfg.host, _healthWindow)
143148 : null ;
@@ -214,12 +219,15 @@ class EngineBundledLauncher {
214219 );
215220 await EngineConfigStore .writeAtomic (cfg);
216221
217- final runningCfg =
218- await _waitRunningOnDiskWithin (cfg.host, _healthWindow);
222+ final runningCfg = await _waitRunningOnDiskWithin (
223+ cfg.host,
224+ _healthWindow,
225+ expectedPid: childPid,
226+ );
219227 if (runningCfg != null &&
220228 runningCfg.status == EngineStatus .running &&
221- runningCfg.pid == childPid &&
222- await _strictHealth (runningCfg.host, runningCfg.port )) {
229+ await _strictHealth ( runningCfg.host, runningCfg.port) &&
230+ (runningCfg.pid == 0 || runningCfg.pid == childPid )) {
223231 return EngineBootstrapOutcome (
224232 success: true ,
225233 activeHost: runningCfg.host,
@@ -283,10 +291,13 @@ class EngineBundledLauncher {
283291 } catch (_) {}
284292 }
285293
294+ /// When [expectedPid] is set and disk has a non-zero pid that differs, keep waiting
295+ /// (stale row). [pid] 0 on disk + healthy HTTP counts as ready (external / reconciled).
286296 static Future <EngineConfig ?> _waitRunningOnDiskWithin (
287297 String connectHost,
288- Duration window,
289- ) async {
298+ Duration window, {
299+ int ? expectedPid,
300+ }) async {
290301 final deadline = DateTime .now ().add (window);
291302 while (DateTime .now ().isBefore (deadline)) {
292303 final disk = await EngineConfigStore .read ();
@@ -295,8 +306,13 @@ class EngineBundledLauncher {
295306 }
296307 if (disk != null &&
297308 disk.status == EngineStatus .running &&
298- disk.pid != 0 &&
299309 await _strictHealth (connectHost, disk.port)) {
310+ if (expectedPid != null &&
311+ disk.pid != 0 &&
312+ disk.pid != expectedPid) {
313+ await Future <void >.delayed (_healthTick);
314+ continue ;
315+ }
300316 return disk;
301317 }
302318 await Future <void >.delayed (_healthTick);
0 commit comments