From f6811f8a468faeccd76f198d9ade169d43256a77 Mon Sep 17 00:00:00 2001 From: 7ttp <117663341+7ttp@users.noreply.github.com> Date: Tue, 11 Aug 2026 13:57:21 +0530 Subject: [PATCH] fix(cli): serialize container health checks --- .../shared/db-bootstrap/health-check.ts | 2 +- .../db-bootstrap/health-check.unit.test.ts | 48 ++++++++++++++++++- 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/apps/cli/src/legacy/shared/db-bootstrap/health-check.ts b/apps/cli/src/legacy/shared/db-bootstrap/health-check.ts index c6487463d6..31932fd0f7 100644 --- a/apps/cli/src/legacy/shared/db-bootstrap/health-check.ts +++ b/apps/cli/src/legacy/shared/db-bootstrap/health-check.ts @@ -381,7 +381,7 @@ export function legacyWaitForHealthyServices( onSuccess: (): LegacyHealthCheckFailure | undefined => undefined, }), ), - { concurrency: "unbounded" }, + { concurrency: 1 }, ); const failures = outcomes.filter( (outcome): outcome is LegacyHealthCheckFailure => outcome !== undefined, diff --git a/apps/cli/src/legacy/shared/db-bootstrap/health-check.unit.test.ts b/apps/cli/src/legacy/shared/db-bootstrap/health-check.unit.test.ts index ae48658fc2..bdccc83bee 100644 --- a/apps/cli/src/legacy/shared/db-bootstrap/health-check.unit.test.ts +++ b/apps/cli/src/legacy/shared/db-bootstrap/health-check.unit.test.ts @@ -37,7 +37,10 @@ const isLogChunks = (script: LogScript): script is LogChunks => Array.isArray(sc function mockHealthSpawner( inspectResponse: (containerId: string, callIndex: number) => string, logs: Readonly> = {}, - opts: { readonly runtime?: "docker" | "podman" } = {}, + opts: { + readonly runtime?: "docker" | "podman"; + readonly beforeInspect?: (containerId: string) => Effect.Effect; + } = {}, ) { const counts = new Map(); const encoder = new TextEncoder(); @@ -86,6 +89,9 @@ function mockHealthSpawner( let stderrChunks: LogChunks = []; if (args[0] === "container" && args[1] === "inspect") { const containerId = args[2] ?? ""; + if (opts.beforeInspect !== undefined) { + yield* opts.beforeInspect(containerId); + } const callIndex = counts.get(containerId) ?? 0; counts.set(containerId, callIndex + 1); stdoutChunks = [inspectResponse(containerId, callIndex)]; @@ -151,6 +157,46 @@ const unusedHttpClientLayer = Layer.succeed( ); describe("legacyWaitForHealthyServices", () => { + it.effect("checks containers serially in their start order", () => + Effect.gen(function* () { + const firstStarted = yield* Deferred.make(); + const releaseFirst = yield* Deferred.make(); + const secondStarted = yield* Deferred.make(); + const mock = mockHealthSpawner( + () => runningHealthy, + {}, + { + beforeInspect: (containerId) => + containerId === "supabase_storage_proj" + ? Deferred.succeed(firstStarted, undefined).pipe( + Effect.andThen(Deferred.await(releaseFirst)), + ) + : Deferred.succeed(secondStarted, undefined), + }, + ); + + const fiber = yield* legacyWaitForHealthyServices( + mock.spawner, + ["supabase_storage_proj", "supabase_studio_proj"], + { timeoutSeconds: 1 }, + ).pipe(Effect.provide(unusedHttpClientLayer), Effect.forkChild({ startImmediately: true })); + + yield* Deferred.await(firstStarted); + yield* Effect.yieldNow; + const secondStartedBeforeRelease = yield* Deferred.isDone(secondStarted); + yield* Deferred.succeed(releaseFirst, undefined); + const exit = yield* Fiber.await(fiber); + + expect(Exit.isSuccess(exit)).toBe(true); + expect(secondStartedBeforeRelease).toBe(false); + expect( + mock.spawned + .filter((args) => args[0] === "container" && args[1] === "inspect") + .map((args) => args[2]), + ).toEqual(["supabase_storage_proj", "supabase_studio_proj"]); + }), + ); + it.effect( "polls on a 1-second backoff until the container reports healthy, without waiting a full timeout", () =>