Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions apps/hub/src/projects/coordinator-startup.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { afterEach, expect, setSystemTime, test } from "bun:test";
import type { DB } from "@intx/db";
import { workflowRun } from "@intx/db/schema";
import type { SidecarAllocationStatus } from "@intx/types";
import { awaitCoordinatorReady } from "./coordinator-startup.ts";

const run = {
id: "run",
anchorRunId: "run",
address: "run@tenant",
status: "running",
expiresAt: null,
cancellationRequestedAt: null,
};

// Each poll reads the next allocation status, then moves the clock on by `stepMs`.
function nativeStates(statuses: SidecarAllocationStatus[], stepMs: number) {
let polls = 0;
const db = {
select: () => ({
from: (table: unknown) => ({
where: async () => {
if (table === workflowRun) return [run];
const status = statuses[Math.min(polls, statuses.length - 1)];
polls += 1;
setSystemTime(new Date(Date.now() + stepMs));
return [{ status, nextAttemptAt: null }];
},
}),
}),
};
return db as unknown as DB["db"];
}

afterEach(() => {
setSystemTime();
});

test("a coordinator ready after more than 120 s still starts", async () => {
await awaitCoordinatorReady(
nativeStates(["provisioning", "allocated"], 121_000),
"tenant",
"run",
);
});

test("a start still pending past 270 s fails", async () => {
await expect(
awaitCoordinatorReady(
nativeStates(["provisioning"], 271_000),
"tenant",
"run",
),
).rejects.toMatchObject({ code: "coordinator_start_failed" });
});
7 changes: 5 additions & 2 deletions apps/hub/src/projects/coordinator-startup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ import { ProjectError } from "./errors.ts";
import type { Actor } from "../lib/actor.ts";

const START_FAILED = "coordinator_start_failed";
const READY_WAIT_MS = 120_000;
// Longer than the native sidecar ensure and connection stages (120 s each) normally take, and
// shorter than the web's 5-minute request ceiling, so a failed start reaches the person who
// created the project instead of archiving it after they stopped waiting.
const READY_WAIT_MS = 270_000;
const READY_POLL_MS = 250;

type BoundRun = { runId: string; ended: boolean };
Expand Down Expand Up @@ -188,7 +191,7 @@ async function attach(
return true;
}

async function awaitCoordinatorReady(
export async function awaitCoordinatorReady(
db: DB["db"],
tenantId: string,
runId: string,
Expand Down