fix(artifacts): honor cancellation at the output publication boundary

This commit is contained in:
morluto committed 2026-10-13 15:12:17 +08:00
1 parent 9049dbaf0c
commit e7882b34f4
7 files changed
+103 -16

No files matched your search

+22 -11
View File
@@ -280,9 +280,11 @@ export class SafeOutputTree {
}
/** Sync the owned output tree and prevent further writes through this instance. */
async commit(): Promise<void> {
async commit(signal?: AbortSignal): Promise<void> {
this.#assertWritable();
abortIfNeeded(signal);
await this.#assertOwnedDirectories();
abortIfNeeded(signal);
// Windows has no directory fsync; file contents are already synced in write().
if (this.platform === "win32") {
this.#published = true;
@@ -315,6 +317,7 @@ export class SafeOutputTree {
);
await output.handle.sync();
await parent.handle.sync();
abortIfNeeded(signal);
this.#published = true;
} catch (cause: unknown) {
sync = { kind: "failed", cause };
@@ -336,23 +339,26 @@ export class SafeOutputTree {
closeFailure,
this.#outputRoot,
);
if (sync.kind === "failed")
throw ArtifactReaderFailure.withCleanup(
new ArtifactReaderFailure(
"path",
"Could not durably sync extraction output",
{ cause: sync.cause },
),
cleanup,
);
if (sync.kind === "failed") {
const primary = isCancellationFailure(sync.cause)
? sync.cause
: new ArtifactReaderFailure(
"path",
"Could not durably sync extraction output",
{ cause: sync.cause },
);
throw ArtifactReaderFailure.withCleanup(primary, cleanup);
}
throw ArtifactReaderFailure.withCleanup(closeFailure, cleanup);
}
if (sync.kind === "failed")
if (sync.kind === "failed") {
if (isCancellationFailure(sync.cause)) throw sync.cause;
throw new ArtifactReaderFailure(
"path",
"Could not durably sync extraction output",
{ cause: sync.cause },
);
}
}
/** Remove only this operation's unsealed tree and verify absence. */
@@ -590,6 +596,11 @@ const abortIfNeeded = (signal?: AbortSignal): void => {
);
};
const isCancellationFailure = (
cause: unknown,
): cause is ArtifactReaderFailure =>
cause instanceof ArtifactReaderFailure && cause.reason === "cancelled";
const isAlreadyExists = (cause: unknown): boolean =>
cause instanceof Error && "code" in cause && cause.code === "EEXIST";