diff --git a/src/gateway/worker-environments/placement-dispatch-reclaim.test.ts b/src/gateway/worker-environments/placement-dispatch-reclaim.test.ts index 8e0a6f63c5df..006bb11dd9a2 100644 --- a/src/gateway/worker-environments/placement-dispatch-reclaim.test.ts +++ b/src/gateway/worker-environments/placement-dispatch-reclaim.test.ts @@ -65,9 +65,16 @@ describe("worker placement dispatch reclaim", () => { ]); }); - it("reconciles the workspace before destroying and reclaiming an active worker", async () => { - const harness = createHarness(placementStore); - await harness.service.dispatch(REQUEST); + it("reclaims an unchanged active placement through the fenced teardown lifecycle", async () => { + const harness = createHarness(placementStore, { + reconcileChanged: false, + reconcileCommitsManifest: false, + }); + await expect(harness.service.dispatch(REQUEST)).resolves.toMatchObject({ + state: "active", + turnClaim: null, + workspaceBaseManifestRef: MANIFEST_REF, + }); await expect( harness.service.reclaim({ @@ -77,9 +84,11 @@ describe("worker placement dispatch reclaim", () => { }), ).resolves.toMatchObject({ state: "reclaimed", - workspaceBaseManifestRef: harness.reconciledManifestRef, + turnClaim: null, + workspaceBaseManifestRef: MANIFEST_REF, }); + expect(placementStore.listPendingWorkspaceResults()).toEqual([]); expect(harness.log.slice(-11)).toEqual([ "tunnel:attached", "workspace:quiesce", diff --git a/src/gateway/worker-environments/placement-dispatch.test.ts b/src/gateway/worker-environments/placement-dispatch.test.ts index 215bf7c6f455..ac281ea1b8be 100644 --- a/src/gateway/worker-environments/placement-dispatch.test.ts +++ b/src/gateway/worker-environments/placement-dispatch.test.ts @@ -461,6 +461,30 @@ describe("worker placement dispatch", () => { expect(harness.log).toContain("workspace:resume"); }); + it("preserves a provider destroy failure after teardown owns the stopped tunnel", async () => { + const harness = createHarness(placementStore, { + destroyFails: true, + destroyFailureState: "destroying", + resumeFails: true, + }); + const active = await harness.service.dispatch(REQUEST); + + await expect(harness.service.reclaim(REQUEST)).rejects.toThrow("destroy pending"); + + expect(harness.environments.get(active.environmentId)).toMatchObject({ + state: "destroying", + ownerEpoch: active.activeOwnerEpoch, + }); + expect(harness.placements.current()).toMatchObject({ + state: "active", + turnClaim: { owner: "worker" }, + }); + expect(placementStore.listPendingWorkspaceResults()).toMatchObject([ + { workspaceAcceptedAtMs: expect.any(Number) }, + ]); + expect(harness.log).not.toContain("workspace:resume"); + }); + it.each([ "barrier", "workspace", diff --git a/src/gateway/worker-environments/placement-dispatch.ts b/src/gateway/worker-environments/placement-dispatch.ts index fd0b0afa896c..4991f52cef97 100644 --- a/src/gateway/worker-environments/placement-dispatch.ts +++ b/src/gateway/worker-environments/placement-dispatch.ts @@ -90,6 +90,19 @@ function requireProvisionedEnvironment( }; } +function isExactAttachedEnvironment( + environment: ReturnType, + placement: WorkerActiveDispatchPlacement, +): boolean { + return ( + environment?.environmentId === placement.environmentId && + environment.state === "attached" && + environment.ownerEpoch === placement.activeOwnerEpoch && + environment.attachedSessionIds.length === 1 && + environment.attachedSessionIds[0] === placement.sessionId + ); +} + export function createWorkerPlacementDispatchService(options: WorkerPlacementDispatchOptions) { const { environments, placements } = options; const failure = createPlacementFailureActions({ environments, placements }); @@ -250,13 +263,7 @@ export function createWorkerPlacementDispatchService(options: WorkerPlacementDis ); } const environment = environments.get(current.environmentId); - if ( - !environment || - environment.state !== "attached" || - environment.ownerEpoch !== current.activeOwnerEpoch || - environment.attachedSessionIds.length !== 1 || - environment.attachedSessionIds[0] !== current.sessionId - ) { + if (!isExactAttachedEnvironment(environment, current)) { throw new Error("Active cloud worker does not match its session placement"); } const journalOwner = { @@ -432,7 +439,10 @@ export function createWorkerPlacementDispatchService(options: WorkerPlacementDis }, }); } finally { - if (!destroyed) { + if ( + !destroyed && + isExactAttachedEnvironment(environments.get(current.environmentId), current) + ) { await quiescence.resume(); } }