fix(javascript): transient Windows private-runtime source-transfer (#8769)

* fix(javascript): transient Windows private-runtime source-transfer

Parent source-transfer into the Windows private runtime root failed with
EPERM on path-based exclusive create. Write through a native transient
private-runtime path that closes the handle after flush so per-job rm
still works, and yield around the sync write so abort deadlines can fire.

* fix(javascript): yield timer phase before Windows source-transfer write
This commit is contained in:
Beqa authored and GitHub committed 2026-11-05 18:32:43 +08:00
1 parent 7b6fc6f13d
commit 4362a0848e
6 files changed
+281 -8

No files matched your search

+15 -2
View File
@@ -824,7 +824,7 @@ napi_value filesystemCall(napi_env env, const std::wstring& operation,
set(env, result, "handle", wrap(env, std::move(file)));
return result;
}
if (operation == L"runtime_write") {
if (operation == L"runtime_write" || operation == L"runtime_write_transient") {
bool isBuffer; check(napi_is_buffer(env, args[2], &isBuffer));
require(isBuffer, "Private write requires a byte buffer", root.path, ERROR_INVALID_PARAMETER);
void* bytes; size_t length; check(napi_get_buffer_info(env, args[2], &bytes, &length));
@@ -832,7 +832,20 @@ napi_value filesystemCall(napi_env env, const std::wstring& operation,
auto file = createPrivateFile(root, wide(env, args[1]));
writeBytes(file.get(), bytes, static_cast<DWORD>(length), root.path);
require(FlushFileBuffers(file.get()), "Flush private runtime file failed", root.path);
root.immutableFiles.push_back(std::move(file));
if (operation == L"runtime_write") {
// Retain the write lease with FILE_SHARE_READ only so later opens cannot
// acquire write or delete access until runtime_close releases it.
root.immutableFiles.push_back(std::move(file));
} else {
// Close without retaining. Per-job path deletion (and child readFile) can
// proceed while the private DACL still protects the staged bytes.
try {
requireReleased(file, "Close transient runtime file failed", root.path);
} catch (...) {
parkUnresolved(root, file);
throw;
}
}
return null(env);
}
if (operation == L"runtime_snapshot") {
@@ -0,0 +1,158 @@
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, describe, expect, it, vi } from "vitest";
const writeFileMock = vi.hoisted(() => vi.fn());
const windowsWriteTransientFile = vi.hoisted(() => vi.fn());
const windowsWriteFile = vi.hoisted(() => vi.fn());
const windowsPrivateRuntime = vi.hoisted(() =>
vi.fn(() => ({
writeTransientFile: windowsWriteTransientFile,
writeFile: windowsWriteFile,
})),
);
vi.mock("node:fs/promises", async (importOriginal) => {
const actual = await importOriginal<typeof import("node:fs/promises")>();
return {
...actual,
writeFile: (...args: Parameters<typeof actual.writeFile>) =>
writeFileMock(...args),
};
});
vi.mock("../../windows/WindowsPrivateRuntime.js", () => ({
windowsPrivateRuntime,
}));
import { writeJavaScriptAnalysisSourceTransfer } from "./JavaScriptAnalysisSourceTransfer.js";
afterEach(() => {
writeFileMock.mockReset();
windowsWriteTransientFile.mockReset();
windowsWriteFile.mockReset();
windowsPrivateRuntime.mockReset();
windowsPrivateRuntime.mockImplementation(() => ({
writeTransientFile: windowsWriteTransientFile,
writeFile: windowsWriteFile,
}));
});
describe("writeJavaScriptAnalysisSourceTransfer", () => {
it("uses the transient private-runtime write on Windows and never path-creates", async () => {
const eperm = Object.assign(new Error("EPERM"), { code: "EPERM" });
writeFileMock.mockRejectedValue(eperm);
const path = join("C:\\rea-javascript-analysis-fixture", "job.source");
await writeJavaScriptAnalysisSourceTransfer(path, "source-bytes", {
platform: "win32",
});
expect(windowsPrivateRuntime).toHaveBeenCalledWith(
"C:\\rea-javascript-analysis-fixture",
);
expect(windowsWriteTransientFile).toHaveBeenCalledWith(
"job.source",
"source-bytes",
);
expect(windowsWriteFile).not.toHaveBeenCalled();
expect(writeFileMock).not.toHaveBeenCalled();
});
it("honors cancellation before a Windows private-runtime write", async () => {
const controller = new AbortController();
controller.abort("cancelled");
await expect(
writeJavaScriptAnalysisSourceTransfer(
join("C:\\rea-javascript-analysis-fixture", "job.source"),
"source-bytes",
{ platform: "win32", signal: controller.signal },
),
).rejects.toBe("cancelled");
expect(windowsPrivateRuntime).not.toHaveBeenCalled();
expect(windowsWriteTransientFile).not.toHaveBeenCalled();
expect(writeFileMock).not.toHaveBeenCalled();
});
it("yields through a timer phase so a pending deadline abort can fire", async () => {
const controller = new AbortController();
setTimeout(() => {
controller.abort("cancelled-during-yield");
}, 0);
await expect(
writeJavaScriptAnalysisSourceTransfer(
join("C:\\rea-javascript-analysis-fixture", "job.source"),
"source-bytes",
{ platform: "win32", signal: controller.signal },
),
).rejects.toBe("cancelled-during-yield");
expect(windowsWriteTransientFile).not.toHaveBeenCalled();
expect(windowsPrivateRuntime).not.toHaveBeenCalled();
expect(writeFileMock).not.toHaveBeenCalled();
});
it("honors cancellation after a completed Windows private-runtime write", async () => {
const controller = new AbortController();
windowsWriteTransientFile.mockImplementation(() => {
controller.abort("cancelled-after-write");
});
await expect(
writeJavaScriptAnalysisSourceTransfer(
join("C:\\rea-javascript-analysis-fixture", "job.source"),
"source-bytes",
{ platform: "win32", signal: controller.signal },
),
).rejects.toBe("cancelled-after-write");
expect(windowsWriteTransientFile).toHaveBeenCalledOnce();
expect(windowsWriteFile).not.toHaveBeenCalled();
});
it("keeps exclusive owner-only create on POSIX", async () => {
writeFileMock.mockImplementation(
async (...args: Parameters<typeof writeFile>) => {
const actual =
await vi.importActual<typeof import("node:fs/promises")>(
"node:fs/promises",
);
return actual.writeFile(...args);
},
);
const root = await mkdtemp(join(tmpdir(), "rea-js-source-transfer-"));
const path = join(root, "job.source");
try {
await writeJavaScriptAnalysisSourceTransfer(path, "source-bytes", {
platform: "linux",
});
expect(await readFile(path, "utf8")).toBe("source-bytes");
expect(writeFileMock).toHaveBeenCalledWith(path, "source-bytes", {
flag: "wx",
mode: 0o600,
signal: undefined,
});
expect(windowsPrivateRuntime).not.toHaveBeenCalled();
await expect(
writeJavaScriptAnalysisSourceTransfer(path, "again", {
platform: "linux",
}),
).rejects.toMatchObject({ code: "EEXIST" });
} finally {
await rm(root, { recursive: true, force: true });
}
});
it("surfaces EPERM from the POSIX exclusive create path", async () => {
const eperm = Object.assign(new Error("EPERM"), { code: "EPERM" });
writeFileMock.mockRejectedValue(eperm);
await expect(
writeJavaScriptAnalysisSourceTransfer("/tmp/job.source", "source-bytes", {
platform: "linux",
}),
).rejects.toMatchObject({ code: "EPERM" });
expect(windowsPrivateRuntime).not.toHaveBeenCalled();
});
});
@@ -0,0 +1,45 @@
import { writeFile } from "node:fs/promises";
import { win32 } from "node:path";
import { windowsPrivateRuntime } from "../../windows/WindowsPrivateRuntime.js";
/**
* Parent source-transfer write into the worker-owned private runtime root.
*
* Windows private roots install a protected DACL and retain the directory
* handle; Node path-based exclusive create can fail with EPERM there. Write
* through the native private-runtime owner with a transient handle so the
* worker can `readFile` the path and the parent can still `rm` `${id}.source`
* after the job. POSIX keeps exclusive owner-only create.
*/
export const writeJavaScriptAnalysisSourceTransfer = async (
path: string,
source: string,
options: {
readonly platform?: NodeJS.Platform;
readonly signal?: AbortSignal;
} = {},
): Promise<void> => {
const platform = options.platform ?? process.platform;
options.signal?.throwIfAborted();
if (platform === "win32") {
// Yield through a timer phase so ProviderStartupDeadline setTimeout
// callbacks and other macrotasks can deliver abort before the sync
// native create/write/flush/close; check again after it returns.
await new Promise<void>((resolve) => {
setTimeout(resolve, 0);
});
options.signal?.throwIfAborted();
windowsPrivateRuntime(win32.dirname(path)).writeTransientFile(
win32.basename(path),
source,
);
options.signal?.throwIfAborted();
return;
}
await writeFile(path, source, {
flag: "wx",
mode: 0o600,
signal: options.signal,
});
};
@@ -7,8 +7,9 @@ import type {
JavaScriptSemanticFileProjection,
} from "../../domain/javascript/javascriptSemanticFileProjection.js";
import { createHash, randomUUID } from "node:crypto";
import { readFile, rm, stat, writeFile } from "node:fs/promises";
import { readFile, rm, stat } from "node:fs/promises";
import { join } from "node:path";
import { writeJavaScriptAnalysisSourceTransfer } from "./JavaScriptAnalysisSourceTransfer.js";
import { StringDecoder } from "node:string_decoder";
import { fileURLToPath } from "node:url";
import { getHeapStatistics } from "node:v8";
@@ -333,11 +334,11 @@ export class JavaScriptAnalysisWorker {
);
supervisor.resetOutput();
this.#phase = "source-transfer";
await writeFile(join(rootPath, `${id}.source`), file.text.value, {
flag: "wx",
mode: 0o600,
signal: deadline.signal,
});
await writeJavaScriptAnalysisSourceTransfer(
join(rootPath, `${id}.source`),
file.text.value,
{ signal: deadline.signal },
);
const { text: _text, ...metadata } = file;
let offeredResultBudget = 0;
const request = javaScriptWorkerRequestSchema.parse({
+43
View File
@@ -162,6 +162,49 @@ const admittedFile = {
handle: { kind: "admitted-file" },
};
describe("Windows private runtime transient write", () => {
it("writes through runtime_write_transient and never retains via runtime_write", async () => {
const operations: string[] = [];
nativeCall.mockImplementation((operation: string) => {
operations.push(operation);
if (operation === "runtime_create") return observation;
return null;
});
const runtime = WindowsPrivateRuntime.create("C:\\", "rea-");
runtime.writeTransientFile("job.source", "source-bytes");
expect(operations).toEqual(["runtime_create", "runtime_write_transient"]);
expect(nativeCall).toHaveBeenCalledWith("runtime_write_transient", [
observation.handle,
"job.source",
Buffer.from("source-bytes"),
]);
expect(operations).not.toContain("runtime_write");
await runtime.close();
});
it("keeps immutable runtime_write distinct from transient staging", async () => {
const operations: string[] = [];
nativeCall.mockImplementation((operation: string) => {
operations.push(operation);
if (operation === "runtime_create") return observation;
return null;
});
const runtime = WindowsPrivateRuntime.create("C:\\", "rea-");
runtime.writeFile("descriptor.json", "fixture");
runtime.writeTransientFile("job.source", "source-bytes");
expect(operations).toEqual([
"runtime_create",
"runtime_write",
"runtime_write_transient",
]);
await runtime.close();
});
});
describe("Windows private runtime admitted file close", () => {
it("retries the same file wrapper before the runtime tree walk", async () => {
const failure = nativeFailure(5);
+13
View File
@@ -112,6 +112,19 @@ export class WindowsPrivateRuntime {
]);
}
/**
* Write a new private file once, flush, and close the writer without retaining
* it in `immutableFiles`. Use for staged job inputs that a child must read and
* the parent must later `rm` while the runtime lease stays open.
*/
writeTransientFile(relativePath: string, content: string): void {
this.authority.call("runtime_write_transient", [
this.observation.handle,
relativePath,
Buffer.from(content),
]);
}
/** Copy admitted source bytes and digest the bytes actually written. */
async snapshot(
sourcePath: string,