Skip to content

Commit 4d5e413

Browse files
committed
feat(supervisor): cancel a resumed run's in-flight checkpoint
1 parent ca9a74e commit 4d5e413

2 files changed

Lines changed: 55 additions & 0 deletions

File tree

apps/supervisor/src/workloadServer/index.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,13 @@ const checkpointDeleteRequests = new Counter({
6060
registers: [register],
6161
});
6262

63+
const checkpointCancelRequests = new Counter({
64+
name: "checkpoint_cancel_requests_total",
65+
help: "Checkpoint cancel requests attempted when a run continues, by outcome",
66+
labelNames: ["result"], // "sent" | "no_client" | "not_applicable" | "http_error"
67+
registers: [register],
68+
});
69+
6370
const WorkloadActionParams = z.object({
6471
runFriendlyId: z.string(),
6572
snapshotFriendlyId: z.string(),
@@ -283,6 +290,35 @@ export class WorkloadServer extends EventEmitter<WorkloadServerEvents> {
283290
checkpointDeleteRequests.inc({ result: "sent" });
284291
}
285292

293+
/**
294+
* cancelCheckpoints tells the checkpoint service a resumed run's in-flight checkpoint is moot.
295+
* Must be called after the reply is sent: it never delays the runner. Without it the checkpoint
296+
* is abandoned by a periodic snapshot poll instead, up to that interval later.
297+
*/
298+
private async cancelCheckpoints(runFriendlyId: string): Promise<void> {
299+
if (!this.checkpointClient) {
300+
checkpointCancelRequests.inc({ result: "no_client" });
301+
return;
302+
}
303+
304+
if (this.snapshotService) {
305+
checkpointCancelRequests.inc({ result: "not_applicable" });
306+
return;
307+
}
308+
309+
const [error, accepted] = await tryCatch(
310+
this.checkpointClient.cancelCheckpoints({ runFriendlyId })
311+
);
312+
313+
if (error || !accepted) {
314+
checkpointCancelRequests.inc({ result: "http_error" });
315+
this.logger.error("Failed to request checkpoint cancel", { runFriendlyId, error });
316+
return;
317+
}
318+
319+
checkpointCancelRequests.inc({ result: "sent" });
320+
}
321+
286322
/**
287323
* Sets common route meta on the wide-event state from URL params.
288324
*/
@@ -643,6 +679,8 @@ export class WorkloadServer extends EventEmitter<WorkloadServerEvents> {
643679
}
644680

645681
reply.json(continuationResult.data as WorkloadContinueRunExecutionResponseBody);
682+
683+
await this.cancelCheckpoints(params.runFriendlyId);
646684
}
647685
),
648686
}

packages/core/src/v3/serverOnly/checkpointClient.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,4 +157,21 @@ export class CheckpointClient {
157157

158158
return true;
159159
}
160+
161+
async cancelCheckpoints({ runFriendlyId }: { runFriendlyId: string }): Promise<boolean> {
162+
const res = await fetch(
163+
new URL(`/api/v1/runs/${runFriendlyId}/checkpoints/cancel`, this.opts.apiUrl),
164+
{ method: "POST" }
165+
);
166+
167+
if (!res.ok) {
168+
this.logger.error("[CheckpointClient] Cancel checkpoints request failed", {
169+
runFriendlyId,
170+
status: res.status,
171+
});
172+
return false;
173+
}
174+
175+
return true;
176+
}
160177
}

0 commit comments

Comments
 (0)