From 725a1833636b3da7816057ccfdf6818a01859046 Mon Sep 17 00:00:00 2001 From: PR Batch Tester Date: Wed, 28 Oct 2026 01:40:55 +0000 Subject: [PATCH 1/2] fix(desktop): a slow first start no longer opens the recovery window at 60 s Co-Authored-By: Claude Opus 5.5 --- .../src/backend/DesktopBackendManager.test.ts | 37 +++++++++++++++++++ .../src/backend/DesktopBackendManager.ts | 3 +- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/apps/desktop/src/backend/DesktopBackendManager.test.ts b/apps/desktop/src/backend/DesktopBackendManager.test.ts index 54fb312ec308f..2760e78c3514e 100644 --- a/apps/desktop/src/backend/DesktopBackendManager.test.ts +++ b/apps/desktop/src/backend/DesktopBackendManager.test.ts @@ -631,4 +631,41 @@ describe("DesktopBackendManager", () => { }).pipe(Effect.provide(TestClock.layer())), ), ); + + it.effect("waits out a slow startup and reports one that never becomes ready", () => + Effect.scoped( + Effect.gen(function* () { + const reports = yield* Queue.unbounded(); + const startInstance = (status: Ref.Ref, onReady?: Effect.Effect) => + makeTestInstance({ + spawnerLayer: Layer.succeed( + ChildProcessSpawner.ChildProcessSpawner, + ChildProcessSpawner.make(() => + Effect.succeed(makeProcess({ exitCode: Effect.never })), + ), + ), + httpClientLayer: layerHttpClient((request) => + Ref.get(status).pipe(Effect.map((code) => responseForRequest(request, code))), + ), + onStartupFailure: (failure) => Queue.offer(reports, failure).pipe(Effect.asVoid), + ...(onReady ? { onReady } : {}), + }).pipe(Effect.tap((instance) => instance.start)); + + // A large state database took about 66 s to migrate and start listening. + const slowStatus = yield* Ref.make(503); + const ready = yield* Deferred.make(); + yield* startInstance(slowStatus, Deferred.succeed(ready, undefined).pipe(Effect.asVoid)); + yield* TestClock.adjust(Duration.seconds(66)); + yield* Ref.set(slowStatus, 200); + yield* TestClock.adjust(Duration.millis(100)); + yield* Deferred.await(ready); + assert.isTrue(Option.isNone(yield* Queue.poll(reports))); + + yield* startInstance(yield* Ref.make(503)); + yield* TestClock.adjust(Duration.minutes(3)); + const failure = yield* Queue.take(reports); + assert.equal(failure.reason, "The server started but did not become ready in time."); + }).pipe(Effect.provide(TestClock.layer())), + ), + ); }); diff --git a/apps/desktop/src/backend/DesktopBackendManager.ts b/apps/desktop/src/backend/DesktopBackendManager.ts index cbc0516509d0b..20700a678cb5e 100644 --- a/apps/desktop/src/backend/DesktopBackendManager.ts +++ b/apps/desktop/src/backend/DesktopBackendManager.ts @@ -34,7 +34,8 @@ import * as DesktopWslEnvironment from "../wsl/DesktopWslEnvironment.ts"; const INITIAL_RESTART_DELAY = Duration.millis(500); const MAX_RESTART_DELAY = Duration.seconds(10); const MAX_PREFLIGHT_FAILURE_ATTEMPTS = 5; -const DEFAULT_BACKEND_READINESS_TIMEOUT = Duration.minutes(1); +// Migrations on a 22 GB state database take over a minute; a timeout opens the recovery window. +const DEFAULT_BACKEND_READINESS_TIMEOUT = Duration.minutes(3); const DEFAULT_BACKEND_READINESS_INTERVAL = Duration.millis(100); const DEFAULT_BACKEND_READINESS_REQUEST_TIMEOUT = Duration.seconds(1); // Match the service launcher's grace period so backends can stop provider sessions. From b9cd74da540b4215a473ced3a8564c0c2f17e178 Mon Sep 17 00:00:00 2001 From: PR Batch Tester Date: Wed, 28 Oct 2026 01:46:39 +0000 Subject: [PATCH 2/2] test(desktop): check the full readiness grace period --- apps/desktop/src/backend/DesktopBackendManager.test.ts | 5 +++-- apps/desktop/src/backend/DesktopBackendManager.ts | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/desktop/src/backend/DesktopBackendManager.test.ts b/apps/desktop/src/backend/DesktopBackendManager.test.ts index 2760e78c3514e..220143d6cf45c 100644 --- a/apps/desktop/src/backend/DesktopBackendManager.test.ts +++ b/apps/desktop/src/backend/DesktopBackendManager.test.ts @@ -651,7 +651,6 @@ describe("DesktopBackendManager", () => { ...(onReady ? { onReady } : {}), }).pipe(Effect.tap((instance) => instance.start)); - // A large state database took about 66 s to migrate and start listening. const slowStatus = yield* Ref.make(503); const ready = yield* Deferred.make(); yield* startInstance(slowStatus, Deferred.succeed(ready, undefined).pipe(Effect.asVoid)); @@ -662,7 +661,9 @@ describe("DesktopBackendManager", () => { assert.isTrue(Option.isNone(yield* Queue.poll(reports))); yield* startInstance(yield* Ref.make(503)); - yield* TestClock.adjust(Duration.minutes(3)); + yield* TestClock.adjust(Duration.seconds(179)); + assert.isTrue(Option.isNone(yield* Queue.poll(reports))); + yield* TestClock.adjust(Duration.seconds(1)); const failure = yield* Queue.take(reports); assert.equal(failure.reason, "The server started but did not become ready in time."); }).pipe(Effect.provide(TestClock.layer())), diff --git a/apps/desktop/src/backend/DesktopBackendManager.ts b/apps/desktop/src/backend/DesktopBackendManager.ts index 20700a678cb5e..1930be355513f 100644 --- a/apps/desktop/src/backend/DesktopBackendManager.ts +++ b/apps/desktop/src/backend/DesktopBackendManager.ts @@ -34,7 +34,7 @@ import * as DesktopWslEnvironment from "../wsl/DesktopWslEnvironment.ts"; const INITIAL_RESTART_DELAY = Duration.millis(500); const MAX_RESTART_DELAY = Duration.seconds(10); const MAX_PREFLIGHT_FAILURE_ATTEMPTS = 5; -// Migrations on a 22 GB state database take over a minute; a timeout opens the recovery window. +// Large state databases can take over a minute to become ready. const DEFAULT_BACKEND_READINESS_TIMEOUT = Duration.minutes(3); const DEFAULT_BACKEND_READINESS_INTERVAL = Duration.millis(100); const DEFAULT_BACKEND_READINESS_REQUEST_TIMEOUT = Duration.seconds(1);