mirror of
https://github.com/morluto/rea
synced 2026-10-27 06:33:32 -04:00
e65c3955f9
Record cancellation at file checkpoints and before owner handoff. Keep physical hash failures, exact reasons, completed facts and failed-close owners distinct through artifact and provider projections and cleanup retries. Refs #4598. Validated with 566 passing affected tests and 22 platform skips; final Kuna cleanup reason rerun passed 43 tests. No Android device or real JADX/Kuna execution claimed.
183 lines
5.6 KiB
TypeScript
183 lines
5.6 KiB
TypeScript
import type { FileHandle } from "node:fs/promises";
|
|
|
|
import { OwnedFileHandle } from "./OwnedFileHandle.js";
|
|
import {
|
|
openRegularFile,
|
|
RegularFileAdmissionFailure,
|
|
assertRegularFileActive,
|
|
RegularFileOperationCancelled,
|
|
} from "./RegularFile.js";
|
|
|
|
export type RegularFileOperationOutcome<Value> =
|
|
| { readonly kind: "completed"; readonly value: Value }
|
|
| { readonly kind: "failed"; readonly cause: unknown };
|
|
|
|
type CompletedRegularFileOutcome<Value> = Extract<
|
|
RegularFileOperationOutcome<Value>,
|
|
{ readonly kind: "completed" }
|
|
>;
|
|
|
|
/** Cancellation after confirmed close, retaining the completed physical read. */
|
|
export class RegularFileCancelled<Value = unknown> extends Error {
|
|
readonly _tag = "RegularFileCancelled";
|
|
readonly outcome: CompletedRegularFileOutcome<Value>;
|
|
readonly reason: unknown;
|
|
|
|
constructor(
|
|
readonly path: string,
|
|
readonly owner: OwnedFileHandle,
|
|
completion: {
|
|
readonly outcome: CompletedRegularFileOutcome<Value>;
|
|
readonly reason: unknown;
|
|
},
|
|
) {
|
|
super(`Regular file operation cancelled after closing ${path}`, {
|
|
cause: completion.reason,
|
|
});
|
|
this.name = this._tag;
|
|
this.outcome = completion.outcome;
|
|
this.reason = completion.reason;
|
|
}
|
|
}
|
|
|
|
/** A failed close transfers its exact descriptor owner and operation outcome. */
|
|
export class RegularFileCleanupFailure<Value = unknown> extends Error {
|
|
constructor(
|
|
readonly path: string,
|
|
readonly owner: OwnedFileHandle,
|
|
readonly outcome: RegularFileOperationOutcome<Value>,
|
|
readonly cleanupCause: unknown,
|
|
readonly retention:
|
|
| { readonly kind: "not_requested" }
|
|
| { readonly kind: "callback_returned" }
|
|
| { readonly kind: "callback_threw"; readonly cause: unknown } = {
|
|
kind: "not_requested",
|
|
},
|
|
readonly completionCancellation?: { readonly reason: unknown },
|
|
) {
|
|
super(`Regular file cleanup failed for ${path}`, {
|
|
cause: new AggregateError(
|
|
[
|
|
...(outcome.kind === "failed" ? [outcome.cause] : []),
|
|
cleanupCause,
|
|
...(retention.kind === "callback_threw" ? [retention.cause] : []),
|
|
],
|
|
"File operation and cleanup failures",
|
|
),
|
|
});
|
|
this.name = "RegularFileCleanupFailure";
|
|
}
|
|
}
|
|
|
|
export interface OwnedFileOperationOptions {
|
|
readonly signal?: AbortSignal;
|
|
readonly retainFailedClose?: (owner: OwnedFileHandle) => void;
|
|
}
|
|
|
|
/** Read only cancellation facts recorded by the operation or its final close. */
|
|
export const regularFileCancellation = (
|
|
cause: unknown,
|
|
): { readonly reason: unknown } | undefined => {
|
|
const primary =
|
|
cause instanceof RegularFileCleanupFailure &&
|
|
cause.outcome.kind === "failed"
|
|
? cause.outcome.cause
|
|
: cause;
|
|
if (
|
|
primary instanceof RegularFileOperationCancelled ||
|
|
primary instanceof RegularFileCancelled
|
|
)
|
|
return { reason: primary.reason };
|
|
return cause instanceof RegularFileCleanupFailure &&
|
|
cause.outcome.kind === "completed"
|
|
? cause.completionCancellation
|
|
: undefined;
|
|
};
|
|
|
|
export interface OwnedRegularFileOptions extends OwnedFileOperationOptions {
|
|
readonly symlinks?: "follow" | "reject";
|
|
}
|
|
|
|
/** Run an operation while one admitted descriptor and its cleanup owner stay together. */
|
|
export const withOwnedRegularFile = async <Value>(
|
|
path: string,
|
|
options: OwnedRegularFileOptions,
|
|
operation: (handle: FileHandle) => Promise<Value>,
|
|
): Promise<Value> => {
|
|
const handle = await openRegularFile(path, {
|
|
symlinks: options.symlinks ?? "follow",
|
|
signal: options.signal,
|
|
}).catch((cause: unknown) => {
|
|
if (!(cause instanceof RegularFileAdmissionFailure)) throw cause;
|
|
const retention = retainOwner(options.retainFailedClose, cause.owner);
|
|
throw new RegularFileCleanupFailure(
|
|
path,
|
|
cause.owner,
|
|
{ kind: "failed", cause: cause.cause },
|
|
cause.cleanupCause,
|
|
retention,
|
|
);
|
|
});
|
|
return withOwnedFileHandle(
|
|
path,
|
|
new OwnedFileHandle(handle),
|
|
options,
|
|
operation,
|
|
);
|
|
};
|
|
|
|
/** Own an already admitted descriptor through its operation and final close. */
|
|
export const withOwnedFileHandle = async <Value>(
|
|
path: string,
|
|
owner: OwnedFileHandle,
|
|
options: OwnedFileOperationOptions,
|
|
operation: (handle: FileHandle) => Promise<Value>,
|
|
): Promise<Value> => {
|
|
const handle = owner.handle;
|
|
let outcome: RegularFileOperationOutcome<Value>;
|
|
try {
|
|
assertRegularFileActive(options.signal);
|
|
outcome = { kind: "completed", value: await operation(handle) };
|
|
} catch (cause: unknown) {
|
|
outcome = { kind: "failed", cause };
|
|
}
|
|
try {
|
|
await owner.close();
|
|
} catch (cleanupCause: unknown) {
|
|
const completionCancellation =
|
|
outcome.kind === "completed" && options.signal?.aborted === true
|
|
? { reason: options.signal.reason }
|
|
: undefined;
|
|
// Retain the physical read outcome even if the caller cancelled during
|
|
// close: consumers may need completed observations such as a file digest.
|
|
throw new RegularFileCleanupFailure(
|
|
path,
|
|
owner,
|
|
outcome,
|
|
cleanupCause,
|
|
retainOwner(options.retainFailedClose, owner),
|
|
completionCancellation,
|
|
);
|
|
}
|
|
if (outcome.kind === "failed") throw outcome.cause;
|
|
if (options.signal?.aborted === true)
|
|
throw new RegularFileCancelled(path, owner, {
|
|
outcome,
|
|
reason: options.signal.reason,
|
|
});
|
|
return outcome.value;
|
|
};
|
|
|
|
const retainOwner = (
|
|
retain: OwnedRegularFileOptions["retainFailedClose"],
|
|
owner: OwnedFileHandle,
|
|
): RegularFileCleanupFailure["retention"] => {
|
|
if (retain === undefined) return { kind: "not_requested" };
|
|
try {
|
|
retain(owner);
|
|
return { kind: "callback_returned" };
|
|
} catch (cause: unknown) {
|
|
return { kind: "callback_threw", cause };
|
|
}
|
|
};
|