fix(javascript): preserve cleanup failures during preparation cancellation (#1654)

This commit is contained in:
Ysopwnable authored and GitHub committed 2026-10-11 08:05:08 +08:00
1 parent bc7846db91
commit 890dfa49f6
3 files changed
+78 -13

No files matched your search

@@ -1,6 +1,9 @@
import { createHash } from "node:crypto";
import { expect, it } from "vitest";
import { JavaScriptAnalysisWorker } from "../../../src/javascript/analysis/JavaScriptAnalysisWorker.js";
import { cleanupOwnedProcessGroup } from "../../../src/process/ProcessOwnership.js";
import { ProviderCleanupError } from "../../../src/domain/providerCleanupError.js";
import { projectAnalysisError } from "../../../src/domain/analysisErrorProjection.js";
import { spawnOwnedProviderProcess } from "../../../src/process/ProviderProcess.js";
import type { JavaScriptArtifactFile } from "../../../src/domain/javascript/javascriptArtifactFiles.js";
@@ -91,3 +94,51 @@ it("reports an actual startup deadline and closes its owned resources before sub
await next.close();
}
});
it("preserves a failed owned cleanup retry when the next caller cancels before acquisition", async () => {
const controller = new AbortController();
let releaseAllowed = false;
let cleanupAttempts = 0;
const worker = new JavaScriptAnalysisWorker(
{ heapMb: 128, timeoutMs: 30_000 },
async (request) => {
const launched = await spawnOwnedProviderProcess(request);
return {
...launched,
async cleanup() {
cleanupAttempts += 1;
if (!releaseAllowed) {
if (cleanupAttempts > 1) controller.abort();
throw new Error("Fixture owned cleanup is unavailable");
}
return launched.cleanup === undefined
? cleanupOwnedProcessGroup(launched.ownership)
: launched.cleanup();
},
};
},
);
const next = new JavaScriptAnalysisWorker({ heapMb: 128, timeoutMs: 30_000 });
try {
const file = source("app.mjs", "export const observed = 7;");
const first = await worker.analyze(file, 100);
if (!first.ok) throw first.error.error;
await expect(worker.close()).rejects.toBeInstanceOf(ProviderCleanupError);
const interrupted = await next.analyze(file, 100, controller.signal);
if (interrupted.ok)
throw new Error("Unreleased ownership must block subsequent analysis");
expect(controller.signal.aborted).toBe(true);
expect(projectAnalysisError(interrupted.error.error)).toMatchObject({
code: "cleanup_incomplete",
});
releaseAllowed = true;
await worker.close();
const recovered = await next.analyze(file, 100);
if (!recovered.ok) throw recovered.error.error;
expect(recovered.value.javascript.parse_status).toBe("complete");
} finally {
releaseAllowed = true;
await worker.close();
await next.close();
}
});