mirror of
https://github.com/morluto/rea.git
synced 2026-10-26 17:35:11 +00:00
fix(windows): retain primary snapshot and cancellation failures
Refs #4502, #4504. Keep the original snapshot failure alongside disposal uncertainty and retain exact cancellation reasons at the TypeScript runtime boundary. Native Windows execution and addon fault behavior were not verified on this macOS host; checked handle-release ownership in #4505 remains open.
This commit is contained in:
1 parent
d292c445ee
commit
5f9ac2c98a
3 files changed
+54
-3
No files matched your search
@@ -642,9 +642,10 @@ static void completeSnapshot(napi_env env, napi_status status, void* pointer) {
|
||||
disposeSnapshot(*work->root, work->destination, work->path);
|
||||
napi_reject_deferred(env, work->deferred, failureValue(env, failure));
|
||||
} catch (const Failure& cleanupFailure) {
|
||||
const Failure combined(
|
||||
failure.constraint + "; incomplete snapshot cleanup failed: " + cleanupFailure.constraint,
|
||||
work->path, cleanupFailure.win32);
|
||||
Failure combined = failure;
|
||||
combined.cleanupConstraint = cleanupFailure.constraint;
|
||||
combined.cleanupWin32 = cleanupFailure.win32;
|
||||
combined.cleanupPath = cleanupFailure.path;
|
||||
napi_reject_deferred(env, work->deferred, failureValue(env, combined));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -13,6 +13,7 @@ vi.mock("./WindowsNativeLoader.js", async (importOriginal) => {
|
||||
|
||||
import {
|
||||
WindowsPrivateRuntime,
|
||||
WindowsSnapshotCancelledError,
|
||||
windowsPrivateRuntime,
|
||||
} from "./WindowsPrivateRuntime.js";
|
||||
|
||||
@@ -149,3 +150,29 @@ describe("Windows private runtime cleanup retry", () => {
|
||||
await runtime.close();
|
||||
});
|
||||
});
|
||||
|
||||
it.each([995, 5])(
|
||||
"classifies native snapshot code %i independently of a late abort",
|
||||
async (code) => {
|
||||
const controller = new AbortController();
|
||||
const failure = nativeFailure(code);
|
||||
nativeCall.mockImplementation((operation: string) => {
|
||||
if (operation === "runtime_create") return observation;
|
||||
if (operation === "runtime_snapshot") return Promise.reject(failure);
|
||||
return null;
|
||||
});
|
||||
const runtime = WindowsPrivateRuntime.create("C:\\", "rea-");
|
||||
const pending = runtime.snapshot(
|
||||
"C:\\target.bin",
|
||||
"target.bin",
|
||||
controller.signal,
|
||||
);
|
||||
controller.abort(null);
|
||||
const result: unknown = await pending.catch((cause: unknown) => cause);
|
||||
if (code === 995) {
|
||||
expect(result).toBeInstanceOf(WindowsSnapshotCancelledError);
|
||||
expect(result).toMatchObject({ reason: null, cause: failure });
|
||||
} else expect(result).toBe(failure);
|
||||
await runtime.close();
|
||||
},
|
||||
);
|
||||
@@ -36,6 +36,22 @@ const transientRuntimeCleanupSchema = z.object({
|
||||
code: z.literal("ERR_REA_WINDOWS_NATIVE"),
|
||||
win32Code: z.union([z.literal(32), z.literal(33)]),
|
||||
});
|
||||
const snapshotCancellationSchema = z.object({
|
||||
code: z.literal("ERR_REA_WINDOWS_NATIVE"),
|
||||
win32Code: z.literal(995),
|
||||
});
|
||||
|
||||
/** A native snapshot acknowledged cancellation, with the caller reason retained. */
|
||||
export class WindowsSnapshotCancelledError extends Error {
|
||||
constructor(
|
||||
readonly reason: unknown,
|
||||
cause: unknown,
|
||||
) {
|
||||
super("Windows snapshot cancelled", { cause });
|
||||
this.name = "WindowsSnapshotCancelledError";
|
||||
}
|
||||
}
|
||||
|
||||
const RUNTIME_CLEANUP_ATTEMPTS = 10;
|
||||
const RUNTIME_CLEANUP_RETRY_DELAY_MS = 100;
|
||||
|
||||
@@ -114,6 +130,13 @@ export class WindowsPrivateRuntime {
|
||||
if (signal?.aborted === true) cancel();
|
||||
try {
|
||||
return snapshotSchema.parse(await pending);
|
||||
} catch (cause: unknown) {
|
||||
if (
|
||||
signal?.aborted === true &&
|
||||
snapshotCancellationSchema.safeParse(cause).success
|
||||
)
|
||||
throw new WindowsSnapshotCancelledError(signal.reason, cause);
|
||||
throw cause;
|
||||
} finally {
|
||||
signal?.removeEventListener("abort", cancel);
|
||||
if (this.#pending === pending) this.#pending = undefined;
|
||||
|
||||
Reference in new issue
Block a user