fix: drain resumable upload teardown
BT-UP-06: close() previously aborted the lifetime and closed the checkpoint store immediately, so a caller could not wait for an active operation's terminal settlement and a late provider result could still race the store. The runtime now tracks every admitted operation until it settles. close() stays the compatibility facade that closes admission and starts the drain, while dispose() returns that same single-flight promise: it aborts the operation registry, awaits actual settlement, and only then closes the checkpoint store and cancellation channel. lifecycle() exposes OPEN, CLOSING and CLOSED, and a draining runtime refuses new admission. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
000a2581af
commit
78f1bb273e
@@ -347,6 +347,65 @@ function executorFor(
|
||||
}
|
||||
|
||||
describe("production resumable upload runtime", () => {
|
||||
it("disposes through one drain that proves quiescence", async () => {
|
||||
// BT-UP-06. close() closes admission; dispose() awaits real settlement.
|
||||
const checkpoints = new MemoryCheckpointStore();
|
||||
const harness = createControlHarness();
|
||||
let releaseUpload: (() => void) | undefined;
|
||||
const runtime = createResumableUploadRuntime({
|
||||
controlPlane: harness.control,
|
||||
partExecutor: executorFor(harness, {
|
||||
delay: async () => {
|
||||
await new Promise<void>((resolve) => {
|
||||
releaseUpload = resolve;
|
||||
});
|
||||
},
|
||||
}),
|
||||
checkpoints,
|
||||
mutationLock: noContentionLock,
|
||||
crypto,
|
||||
policy: runtimePolicy(),
|
||||
now: () => 1_000,
|
||||
random: () => 0,
|
||||
sleep: async () => {},
|
||||
});
|
||||
|
||||
const uploading = runtime.upload({
|
||||
uploadKey: "upload_key_01",
|
||||
purpose: "attachment",
|
||||
mediaType: "application/octet-stream",
|
||||
source: byteStreamSource(new Uint8Array([1, 2, 3, 4, 5])),
|
||||
signal: activeSignal,
|
||||
});
|
||||
await vi.waitFor(() => expect(releaseUpload).toBeDefined());
|
||||
expect(runtime.lifecycle()).toBe("OPEN");
|
||||
|
||||
const first = runtime.dispose();
|
||||
const second = runtime.dispose();
|
||||
// Duplicate dispose is single-flight.
|
||||
expect(first).toBe(second);
|
||||
expect(runtime.lifecycle()).toBe("CLOSING");
|
||||
|
||||
// New admission is refused while draining.
|
||||
await expect(
|
||||
runtime.upload({
|
||||
uploadKey: "upload_key_02",
|
||||
purpose: "attachment",
|
||||
mediaType: "application/octet-stream",
|
||||
source: byteStreamSource(new Uint8Array([1])),
|
||||
signal: activeSignal,
|
||||
}),
|
||||
).resolves.toMatchObject({ ok: false, error: { code: "UNAVAILABLE" } });
|
||||
// The checkpoint store cannot close before the operation settles.
|
||||
expect(checkpoints.closed).toBe(false);
|
||||
|
||||
releaseUpload?.();
|
||||
await first;
|
||||
await uploading;
|
||||
expect(runtime.lifecycle()).toBe("CLOSED");
|
||||
expect(checkpoints.closed).toBe(true);
|
||||
});
|
||||
|
||||
it("has a valid production default policy", () => {
|
||||
expect(() => resolveResumableUploadRuntimePolicy()).not.toThrow();
|
||||
expect(() =>
|
||||
|
||||
Reference in New Issue
Block a user