mirror of
https://github.com/morluto/rea.git
synced 2026-11-15 19:23:24 +00:00
fix(javascript): refuse member-mutation work beyond a fixed allowance (#11144)
* fix(javascript): refuse member-mutation work beyond a fixed allowance Member-mutation indexing retained every scheduled alias state until its query finished, with no bound on the phase. Conditional self-selections before one member write (#8294) multiplied those states until the worker heap was exhausted. Charge each scheduled alias state to a per-phase work meter. Past 524,288 states the phase is refused with a resource_constraint that names the fixed work allowance. The application and web bundle workers carry the reported limit to the parent, which rebuilds the refusal with that allowance instead of heap advice. Refs #8294 #6127 #6344 #7170 #7510 #8298 * test(javascript): prove the member-mutation meter spans the phase
This commit is contained in:
1 parent
4eafd9dee5
commit
d07ab79f9b
12 files changed
+371
-34
No files matched your search
@@ -14,6 +14,11 @@ import { ProcessOwnershipEvidenceOwner } from "../../../src/process/ProcessOwner
|
||||
import type { OwnedProcessGroup } from "../../../src/process/ProcessOwnership.js";
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
import { ProviderCleanupError } from "../../../src/domain/providerCleanupError.js";
|
||||
import { AnalysisResourceConstraintError } from "../../../src/domain/analysisErrorCore.js";
|
||||
import {
|
||||
JAVASCRIPT_MEMBER_MUTATION_WORK_ALLOWANCE,
|
||||
JAVASCRIPT_MEMBER_MUTATION_WORK_LIMIT,
|
||||
} from "../../../src/domain/javascript/javascriptSemanticWorkAllowance.js";
|
||||
import { projectAnalysisError } from "../../../src/domain/analysisErrorProjection.js";
|
||||
import { spawnOwnedProviderProcess } from "../../../src/process/ProviderProcess.js";
|
||||
import { javaScriptWorkerMemoryLimitBytes } from "../../../src/process/JavaScriptWorkerMemoryLimit.js";
|
||||
@@ -701,7 +706,13 @@ it("formats a real worker's declined semantic budget once", async () => {
|
||||
});
|
||||
|
||||
// The handshake mirrors the real server: only Node.js reports an enforced V8 heap limit.
|
||||
const resourceFailureWorker = (heapLimit: string) =>
|
||||
const resourceFailureWorker = (
|
||||
heapLimit: string,
|
||||
failure: { readonly resource: string; readonly limits: unknown } = {
|
||||
resource: "memory",
|
||||
limits: null,
|
||||
},
|
||||
) =>
|
||||
new JavaScriptAnalysisWorker({ heapMb: 128, timeoutMs: 30_000 }, (request) =>
|
||||
spawnOwnedProviderProcess({
|
||||
...request,
|
||||
@@ -711,7 +722,7 @@ const resourceFailureWorker = (heapLimit: string) =>
|
||||
process.stdout.write(JSON.stringify({ kind: "ready", heap_limit_bytes: heap }) + "\\n");
|
||||
require("node:readline").createInterface({ input: process.stdin }).once("line", (line) => {
|
||||
const { id } = JSON.parse(line);
|
||||
process.stdout.write(JSON.stringify({ kind: "failure", id, reason: "resource", resource: "memory", limits: null, message: "fixture" }) + "\\n");
|
||||
process.stdout.write(JSON.stringify({ kind: "failure", id, reason: "resource", ...${JSON.stringify(failure)}, message: "fixture" }) + "\\n");
|
||||
});`,
|
||||
],
|
||||
}),
|
||||
@@ -749,6 +760,41 @@ it("reports the worker heap limit only when the runtime enforces V8 heap flags",
|
||||
}
|
||||
});
|
||||
|
||||
it("names the fixed work allowance a worker refusal reports", async () => {
|
||||
const worker = resourceFailureWorker(
|
||||
'process.versions.bun === undefined ? require("node:v8").getHeapStatistics().heap_size_limit : null',
|
||||
{
|
||||
resource: "cpu",
|
||||
limits: {
|
||||
[JAVASCRIPT_MEMBER_MUTATION_WORK_LIMIT]:
|
||||
JAVASCRIPT_MEMBER_MUTATION_WORK_ALLOWANCE,
|
||||
},
|
||||
},
|
||||
);
|
||||
try {
|
||||
const result = await worker.analyze(
|
||||
source("app.mjs", "export const observed = 7;"),
|
||||
{ nodes: 100, unknowns: 100 },
|
||||
);
|
||||
if (result.ok) throw new Error("expected a reported resource failure");
|
||||
const error = result.error.error;
|
||||
if (!(error instanceof AnalysisResourceConstraintError))
|
||||
throw new Error("expected a resource constraint");
|
||||
expect(error).toMatchObject({
|
||||
resource: "cpu",
|
||||
retryable: false,
|
||||
remediationAction: undefined,
|
||||
fixedAllowance: {
|
||||
kind: "work",
|
||||
limit: JAVASCRIPT_MEMBER_MUTATION_WORK_ALLOWANCE,
|
||||
unit: "alias states",
|
||||
},
|
||||
});
|
||||
} finally {
|
||||
await worker.close();
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects a worker heap report that contradicts the launching runtime", async () => {
|
||||
const worker = resourceFailureWorker(
|
||||
"process.versions.bun === undefined ? null : 1",
|
||||
|
||||
Reference in new issue
Block a user