test: stabilize startup checks and document shutdown drain requirements
Mock the native shutdown boundary in the startup unit test and wait for the persisted process identity before allowing the runtime readiness fixture to listen. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
parent
c7b68b52b0
commit
777d891c25
|
|
@ -327,3 +327,10 @@ as incomplete. Active native turns keep their existing restart/reattach behavior
|
|||
Local and SSH session shutdown behavior is unchanged. A hard process kill cannot
|
||||
guarantee a provider-session checkpoint; scoped files and repository durability
|
||||
remain limited to the last successfully published work-folder checkpoint.
|
||||
|
||||
Deployments must allow enough graceful shutdown time for that drain and other
|
||||
server cleanup. On Railway, configure at least 60 seconds of draining time for
|
||||
the service before testing an app redeployment; the platform default is zero.
|
||||
This is a deployment prerequisite, not a fleet-default promotion. When upgrading
|
||||
from a release without the idle-session drain, park warm native sessions and
|
||||
verify their completed harness checkpoints before stopping the old app.
|
||||
|
|
|
|||
|
|
@ -345,6 +345,12 @@ vi.mock("../services/question-response-delivery.js", () => ({
|
|||
})),
|
||||
}));
|
||||
|
||||
vi.mock("../services/native-runtime/native-session-executor.js", () => ({
|
||||
closeIdleSandboxNativeSessionsForShutdown: vi.fn(async () => ({
|
||||
closed: 0, busy: 0, failed: 0,
|
||||
})),
|
||||
}));
|
||||
|
||||
vi.mock("../services/native-runtime/native-question-bridge.js", () => ({
|
||||
deliverNativeQuestionResponse: vi.fn(async () => "not_native"),
|
||||
nativeQuestionCancellationIdentity: vi.fn(() => null),
|
||||
|
|
|
|||
|
|
@ -6510,6 +6510,7 @@ describeEmbeddedPostgres("workspace runtime service control persistence", () =>
|
|||
const executionWorkspaceId = randomUUID();
|
||||
const provisionMarkerPath = path.join(workspaceRoot, "runtime-provisioning.marker");
|
||||
const markerPath = path.join(workspaceRoot, "runtime-spawned.marker");
|
||||
const readyMarkerPath = path.join(workspaceRoot, "runtime-ready.marker");
|
||||
const provisionScript = [
|
||||
`require("node:fs").writeFileSync(${JSON.stringify(provisionMarkerPath)}, "provisioning");`,
|
||||
"setTimeout(() => {}, 1200);",
|
||||
|
|
@ -6518,11 +6519,13 @@ describeEmbeddedPostgres("workspace runtime service control persistence", () =>
|
|||
`${JSON.stringify(process.execPath)} -e ${JSON.stringify(provisionScript)}`;
|
||||
const serverScript = [
|
||||
`require("node:fs").writeFileSync(${JSON.stringify(markerPath)}, "spawned");`,
|
||||
"setTimeout(() => {",
|
||||
"const ready = setInterval(() => {",
|
||||
` if (!require("node:fs").existsSync(${JSON.stringify(readyMarkerPath)})) return;`,
|
||||
" clearInterval(ready);",
|
||||
" require(\"node:http\")",
|
||||
" .createServer((_req, res) => { res.end(\"ok\"); })",
|
||||
" .listen(Number(process.env.PORT), \"127.0.0.1\");",
|
||||
"}, 100);",
|
||||
"}, 10);",
|
||||
"setInterval(() => {}, 1000);",
|
||||
].join(" ");
|
||||
const command = `${JSON.stringify(process.execPath)} -e ${JSON.stringify(serverScript)}`;
|
||||
|
|
@ -6589,7 +6592,9 @@ describeEmbeddedPostgres("workspace runtime service control persistence", () =>
|
|||
.from(workspaceRuntimeServices)
|
||||
.where(eq(workspaceRuntimeServices.executionWorkspaceId, executionWorkspaceId))
|
||||
.then((rows) => rows[0] ?? null);
|
||||
if (row?.status === status) return row;
|
||||
if (row?.status === status && (status !== "starting" || row.providerRef !== null)) {
|
||||
return row;
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, 25));
|
||||
}
|
||||
throw new Error(`Timed out waiting for persisted runtime service status ${status}`);
|
||||
|
|
@ -6670,6 +6675,10 @@ describeEmbeddedPostgres("workspace runtime service control persistence", () =>
|
|||
expect(startingRow.providerRef).toMatch(/^\d+$/);
|
||||
expect(startingRow.port).toEqual(expect.any(Number));
|
||||
|
||||
// A process marker can arrive before its PID is persisted. Hold readiness
|
||||
// until the complete starting row has been observed, rather than racing a
|
||||
// fixed delay on a busy test runner.
|
||||
await fs.writeFile(readyMarkerPath, "ready");
|
||||
const services = await startPromise;
|
||||
expect(services).toHaveLength(1);
|
||||
expect(services[0]).toMatchObject({
|
||||
|
|
|
|||
Loading…
Reference in New Issue