mirror of
https://github.com/morluto/rea.git
synced 2026-11-12 09:52:27 +00:00
fix(settings): keep invocation recovery on the launch seed
A one-shot CLI failure tells the caller to set the launch environment for the next REA process. update_analysis_settings stays on the continuing session. Reused JavaScript workers also keep the limit source that selected them.
This commit is contained in:
1 parent
073dda2a0a
commit
88202cbf67
10 files changed
+89
-20
No files matched your search
@@ -130,7 +130,12 @@ const sameLimits = (
|
||||
left: JavaScriptSourceAnalysisLimits,
|
||||
right: JavaScriptSourceAnalysisLimits,
|
||||
): boolean => {
|
||||
if (left.heapMb !== right.heapMb || left.timeoutMs !== right.timeoutMs)
|
||||
if (
|
||||
left.heapMb !== right.heapMb ||
|
||||
left.timeoutMs !== right.timeoutMs ||
|
||||
left.heapSource !== right.heapSource ||
|
||||
left.timeoutSource !== right.timeoutSource
|
||||
)
|
||||
return false;
|
||||
const leftPrecision = resolveJavaScriptSemanticPrecision(
|
||||
left.semanticPrecision,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { expect, it } from "vitest";
|
||||
import { ArtifactReaderFailure } from "../../artifacts/ArtifactReader.js";
|
||||
import {
|
||||
captureDecoderHeapContinuingRecovery,
|
||||
captureDecoderHeapRecovery,
|
||||
DEFAULT_CAPTURE_DECODER_HEAP,
|
||||
} from "../../config/captureDecoderHeap.js";
|
||||
@@ -74,7 +75,7 @@ it.each([
|
||||
retryable: false,
|
||||
remediation: {
|
||||
action: expect.stringContaining(
|
||||
captureDecoderHeapRecovery(DEFAULT_CAPTURE_DECODER_HEAP),
|
||||
captureDecoderHeapContinuingRecovery(DEFAULT_CAPTURE_DECODER_HEAP),
|
||||
),
|
||||
},
|
||||
details: {
|
||||
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
type InspectWebNetworkCaptureInput,
|
||||
} from "../../domain/webNetworkCapture.js";
|
||||
import {
|
||||
captureDecoderHeapContinuingRecovery,
|
||||
captureDecoderHeapRecovery,
|
||||
DEFAULT_CAPTURE_DECODER_HEAP,
|
||||
type CaptureDecoderHeap,
|
||||
@@ -299,6 +300,7 @@ export const captureMemoryFailure = (
|
||||
heap.oldGenerationMiB,
|
||||
);
|
||||
const recovery = captureDecoderHeapRecovery(heap);
|
||||
const continuingRecovery = captureDecoderHeapContinuingRecovery(heap);
|
||||
return new AnalysisResourceConstraintError(
|
||||
OPERATION,
|
||||
"memory",
|
||||
@@ -341,6 +343,13 @@ export const captureMemoryFailure = (
|
||||
}
|
||||
: {}),
|
||||
remediationAction: `Use a smaller capture exported by its producer, or another decoder with sufficient capacity for the original capture. Preserve the original for provenance; a subset does not establish complete-capture coverage.${input.format !== "mitmproxy" ? ` ${recovery}` : ""}${input.format !== "mitmproxy" && processMemoryLimitBytes !== null ? ` The Windows Job Object commit limit for this decoder is ${String(processMemoryLimitBytes)} bytes, ${String(heap.oldGenerationMiB)} MiB plus a 512 MiB Bun reserve.` : ""}`,
|
||||
...(input.format === "mitmproxy"
|
||||
? {}
|
||||
: {
|
||||
continuingCallerGuidance: {
|
||||
remediationAction: `Use a smaller capture exported by its producer, or another decoder with sufficient capacity for the original capture. Preserve the original for provenance; a subset does not establish complete-capture coverage. ${continuingRecovery}${processMemoryLimitBytes !== null ? ` The Windows Job Object commit limit for this decoder is ${String(processMemoryLimitBytes)} bytes, ${String(heap.oldGenerationMiB)} MiB plus a 512 MiB Bun reserve.` : ""}`,
|
||||
},
|
||||
}),
|
||||
...(cause instanceof OwnedCommandFailure && cause.snapshot !== null
|
||||
? {
|
||||
capturedOutput: {
|
||||
|
||||
@@ -67,11 +67,25 @@ export const resolveCaptureDecoderHeap = (
|
||||
return parsed.value;
|
||||
};
|
||||
|
||||
/** How to read and change the effective HAR/SAZ/qlog old-generation heap. */
|
||||
export const captureDecoderHeapRecovery = (heap: CaptureDecoderHeap): string =>
|
||||
const captureDecoderHeapFacts = (heap: CaptureDecoderHeap): string =>
|
||||
[
|
||||
`The effective ${CAPTURE_DECODER_OLD_GENERATION_SETTING} value is ${String(heap.oldGenerationMiB)} MiB (source ${heap.source}; default ${String(DEFAULT_CAPTURE_DECODER_OLD_GENERATION_MIB)} MiB, floor ${String(CAPTURE_DECODER_SEMI_SPACE_MIB)} MiB, maximum ${String(MAXIMUM_CAPTURE_DECODER_OLD_GENERATION_MIB)} MiB).`,
|
||||
`capture.decoder_old_generation_mib can be changed at runtime with update_analysis_settings. The launch seed is ${CAPTURE_DECODER_OLD_GENERATION_SETTING}, a decimal integer from ${String(CAPTURE_DECODER_SEMI_SPACE_MIB)} to ${String(MAXIMUM_CAPTURE_DECODER_OLD_GENERATION_MIB)}.`,
|
||||
`On Node.js the decoder passes --max-old-space-size=${String(heap.oldGenerationMiB)} and removes inherited NODE_OPTIONS, so additional host memory and NODE_OPTIONS do not raise this heap. The ${String(CAPTURE_DECODER_SEMI_SPACE_MIB)} MiB semi-space stays fixed.`,
|
||||
"On Bun the V8 flags are ignored. Windows limits the decoder's committed memory to this value plus a 512 MiB reserve. Other Bun hosts enforce no decoder heap limit.",
|
||||
].join(" ");
|
||||
|
||||
/** Recovery after this REA process has exited. */
|
||||
export const captureDecoderHeapRecovery = (heap: CaptureDecoderHeap): string =>
|
||||
[
|
||||
captureDecoderHeapFacts(heap),
|
||||
`Set ${CAPTURE_DECODER_OLD_GENERATION_SETTING} in the environment that starts the next REA invocation. It must be a decimal integer from ${String(CAPTURE_DECODER_SEMI_SPACE_MIB)} to ${String(MAXIMUM_CAPTURE_DECODER_OLD_GENERATION_MIB)}.`,
|
||||
].join(" ");
|
||||
|
||||
/** Recovery while this session can still call update_analysis_settings. */
|
||||
export const captureDecoderHeapContinuingRecovery = (
|
||||
heap: CaptureDecoderHeap,
|
||||
): string =>
|
||||
[
|
||||
captureDecoderHeapFacts(heap),
|
||||
`Call update_analysis_settings with setting capture.decoder_old_generation_mib. It applies to the next decoder in this session. The launch seed remains ${CAPTURE_DECODER_OLD_GENERATION_SETTING}.`,
|
||||
].join(" ");
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
DEFAULT_REGEX_MATCHING_DEADLINE,
|
||||
parseRegexMatchingDeadline,
|
||||
REGEX_MATCHING_DEADLINE_SETTING,
|
||||
regexMatchingDeadlineContinuingRecovery,
|
||||
regexMatchingDeadlineRecovery,
|
||||
} from "./regexMatchingDeadline.js";
|
||||
import { MAXIMUM_REGEX_MATCHING_DEADLINE_MS } from "../domain/analysisResourceLimits.js";
|
||||
@@ -32,9 +33,9 @@ it("keeps the 5000 ms deadline when the setting is unset", () => {
|
||||
const recovery = regexMatchingDeadlineRecovery(
|
||||
DEFAULT_REGEX_MATCHING_DEADLINE,
|
||||
);
|
||||
const projected = projectAnalysisError(
|
||||
regexMatchingDeadlineError("search_procedures"),
|
||||
);
|
||||
const error = regexMatchingDeadlineError("search_procedures");
|
||||
const projected = projectAnalysisError(error);
|
||||
const invocation = projectAnalysisError(error, "invocation");
|
||||
expect(projected).toMatchObject({
|
||||
code: "resource_constraint",
|
||||
details: {
|
||||
@@ -58,7 +59,13 @@ it("keeps the 5000 ms deadline when the setting is unset", () => {
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(projected.remediation.action).toContain(recovery);
|
||||
expect(projected.remediation.action).toContain(
|
||||
regexMatchingDeadlineContinuingRecovery(DEFAULT_REGEX_MATCHING_DEADLINE),
|
||||
);
|
||||
expect(invocation.remediation.action).toContain(recovery);
|
||||
expect(invocation.remediation.action).not.toContain(
|
||||
"update_analysis_settings",
|
||||
);
|
||||
expect(projected.remediation.action).toContain(
|
||||
REGEX_MATCHING_DEADLINE_SETTING,
|
||||
);
|
||||
|
||||
@@ -64,12 +64,25 @@ export const resolveRegexMatchingDeadline = (
|
||||
return parsed.value;
|
||||
};
|
||||
|
||||
/** How to read and change the effective supervised matching deadline. */
|
||||
const regexMatchingDeadlineFacts = (deadline: RegexMatchingDeadline): string =>
|
||||
`The effective ${REGEX_MATCHING_DEADLINE_SETTING} value is ${String(deadline.deadlineMs)} ms (source ${deadline.source}; default ${String(DEFAULT_REGEX_MATCHING_DEADLINE_MS)} ms, floor 1 ms, maximum ${String(MAXIMUM_REGEX_MATCHING_DEADLINE_MS)} ms).`;
|
||||
|
||||
/** Recovery after this REA process has exited. The next invocation reads the launch seed. */
|
||||
export const regexMatchingDeadlineRecovery = (
|
||||
deadline: RegexMatchingDeadline,
|
||||
): string =>
|
||||
[
|
||||
`The effective ${REGEX_MATCHING_DEADLINE_SETTING} value is ${String(deadline.deadlineMs)} ms (source ${deadline.source}; default ${String(DEFAULT_REGEX_MATCHING_DEADLINE_MS)} ms, floor 1 ms, maximum ${String(MAXIMUM_REGEX_MATCHING_DEADLINE_MS)} ms).`,
|
||||
`regex.matching_deadline_ms can be changed at runtime with update_analysis_settings. The launch seed is ${REGEX_MATCHING_DEADLINE_SETTING}, a positive decimal integer from 1 to ${String(MAXIMUM_REGEX_MATCHING_DEADLINE_MS)}.`,
|
||||
regexMatchingDeadlineFacts(deadline),
|
||||
`Set ${REGEX_MATCHING_DEADLINE_SETTING} in the environment that starts the next REA invocation. It must be a positive decimal integer from 1 to ${String(MAXIMUM_REGEX_MATCHING_DEADLINE_MS)}.`,
|
||||
"The worker old-generation heap does not raise this deadline.",
|
||||
].join(" ");
|
||||
|
||||
/** Recovery while the same REA session can still call update_analysis_settings. */
|
||||
export const regexMatchingDeadlineContinuingRecovery = (
|
||||
deadline: RegexMatchingDeadline,
|
||||
): string =>
|
||||
[
|
||||
regexMatchingDeadlineFacts(deadline),
|
||||
`Call update_analysis_settings with setting regex.matching_deadline_ms. It applies to the next match in this session. The launch seed remains ${REGEX_MATCHING_DEADLINE_SETTING}.`,
|
||||
"The worker old-generation heap does not raise this deadline.",
|
||||
].join(" ");
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
DEFAULT_REGEX_WORKER_HEAP_REQUEST,
|
||||
parseRegexWorkerHeapRequest,
|
||||
REGEX_WORKER_HEAP_REQUEST_SETTING,
|
||||
regexWorkerHeapRequestContinuingRecovery,
|
||||
regexWorkerHeapRequestRecovery,
|
||||
} from "./regexWorkerHeapRequest.js";
|
||||
import {
|
||||
@@ -32,7 +33,7 @@ it("keeps the 128 MiB request when the setting is unset", () => {
|
||||
expect(parsed.value.regexWorkerHeapRequest).toEqual(
|
||||
DEFAULT_REGEX_WORKER_HEAP_REQUEST,
|
||||
);
|
||||
const recovery = regexWorkerHeapRequestRecovery(
|
||||
const recovery = regexWorkerHeapRequestContinuingRecovery(
|
||||
DEFAULT_REGEX_WORKER_HEAP_REQUEST,
|
||||
);
|
||||
const projected = projectAnalysisError(
|
||||
|
||||
@@ -66,12 +66,25 @@ export const resolveRegexWorkerHeapRequest = (
|
||||
return parsed.value;
|
||||
};
|
||||
|
||||
/** How to read and change the regex worker old-generation request. */
|
||||
const regexWorkerHeapFacts = (request: RegexWorkerHeapRequest): string =>
|
||||
`The effective ${REGEX_WORKER_HEAP_REQUEST_SETTING} value is ${String(request.heapMiB)} MiB (source ${request.source}; default ${String(DEFAULT_REGEX_WORKER_HEAP_REQUEST_MIB)} MiB, floor ${String(MINIMUM_REGEX_WORKER_HEAP_REQUEST_MIB)} MiB, maximum ${String(MAXIMUM_REGEX_WORKER_HEAP_REQUEST_MIB)} MiB).`;
|
||||
|
||||
/** Recovery after this REA process has exited. */
|
||||
export const regexWorkerHeapRequestRecovery = (
|
||||
request: RegexWorkerHeapRequest,
|
||||
): string =>
|
||||
[
|
||||
`The effective ${REGEX_WORKER_HEAP_REQUEST_SETTING} value is ${String(request.heapMiB)} MiB (source ${request.source}; default ${String(DEFAULT_REGEX_WORKER_HEAP_REQUEST_MIB)} MiB, floor ${String(MINIMUM_REGEX_WORKER_HEAP_REQUEST_MIB)} MiB, maximum ${String(MAXIMUM_REGEX_WORKER_HEAP_REQUEST_MIB)} MiB).`,
|
||||
`regex.worker_heap_request_mib can be changed at runtime with update_analysis_settings. The launch seed is ${REGEX_WORKER_HEAP_REQUEST_SETTING}, a decimal integer from ${String(MINIMUM_REGEX_WORKER_HEAP_REQUEST_MIB)} to ${String(MAXIMUM_REGEX_WORKER_HEAP_REQUEST_MIB)}.`,
|
||||
regexWorkerHeapFacts(request),
|
||||
`Set ${REGEX_WORKER_HEAP_REQUEST_SETTING} in the environment that starts the next REA invocation. It must be a decimal integer from ${String(MINIMUM_REGEX_WORKER_HEAP_REQUEST_MIB)} to ${String(MAXIMUM_REGEX_WORKER_HEAP_REQUEST_MIB)}.`,
|
||||
"A positive Node --max-old-space-size, including NODE_OPTIONS, overrides this request and sets the effective heap. That flag is not limited by this setting's floor or maximum. Raising host memory alone does not raise the request.",
|
||||
].join(" ");
|
||||
|
||||
/** Recovery while this session can still call update_analysis_settings. */
|
||||
export const regexWorkerHeapRequestContinuingRecovery = (
|
||||
request: RegexWorkerHeapRequest,
|
||||
): string =>
|
||||
[
|
||||
regexWorkerHeapFacts(request),
|
||||
`Call update_analysis_settings with setting regex.worker_heap_request_mib. It changes the request for the next worker in this session. The launch seed remains ${REGEX_WORKER_HEAP_REQUEST_SETTING}.`,
|
||||
"A positive Node --max-old-space-size, including NODE_OPTIONS, overrides this request and sets the effective heap. That flag is not limited by this setting's floor or maximum. Raising host memory alone does not raise the request.",
|
||||
].join(" ");
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
regexMatchingDeadlineRecovery,
|
||||
} from "../config/regexMatchingDeadline.js";
|
||||
import {
|
||||
regexWorkerHeapRequestContinuingRecovery,
|
||||
regexWorkerHeapRequestRecovery,
|
||||
DEFAULT_REGEX_WORKER_HEAP_REQUEST,
|
||||
} from "../config/regexWorkerHeapRequest.js";
|
||||
@@ -476,7 +477,7 @@ it("keeps a selected heap request unless a positive Node flag overrides it", ()
|
||||
},
|
||||
});
|
||||
expect(projected.remediation.action).toContain(
|
||||
regexWorkerHeapRequestRecovery(request),
|
||||
regexWorkerHeapRequestContinuingRecovery(request),
|
||||
);
|
||||
expect(projected.details?.limit).toBeUndefined();
|
||||
});
|
||||
@@ -15,12 +15,14 @@ import { err, ok, type Result } from "../domain/result.js";
|
||||
import {
|
||||
DEFAULT_REGEX_MATCHING_DEADLINE,
|
||||
REGEX_MATCHING_DEADLINE_SETTING,
|
||||
regexMatchingDeadlineContinuingRecovery,
|
||||
regexMatchingDeadlineRecovery,
|
||||
type RegexMatchingDeadline,
|
||||
} from "../config/regexMatchingDeadline.js";
|
||||
import {
|
||||
DEFAULT_REGEX_WORKER_HEAP_REQUEST,
|
||||
REGEX_WORKER_HEAP_REQUEST_SETTING,
|
||||
regexWorkerHeapRequestContinuingRecovery,
|
||||
regexWorkerHeapRequestRecovery,
|
||||
type RegexWorkerHeapRequest,
|
||||
} from "../config/regexWorkerHeapRequest.js";
|
||||
@@ -404,8 +406,8 @@ const launchRegexWorkerHeap = resolveLaunchRegexWorkerHeap(
|
||||
const heapBound = (
|
||||
heap: RegexWorkerHeapLimit,
|
||||
request: RegexWorkerHeapRequest,
|
||||
recovery = regexWorkerHeapRequestRecovery(request),
|
||||
): string => {
|
||||
const recovery = regexWorkerHeapRequestRecovery(request);
|
||||
if (heap.kind === "unknown")
|
||||
return `The worker requests ${String(request.heapMiB)} MiB. The effective old-generation heap was not established from the launch flags. --max-old-space-size, --max-old-space-size-percentage, --max-heap-size, --env-file, --env-file-if-exists, --experimental-config-file, and --experimental-default-config-file can change that request. ${recovery}`;
|
||||
const override = `A positive Node --max-old-space-size, including NODE_OPTIONS, overrides the ${String(request.heapMiB)} MiB worker request`;
|
||||
@@ -476,7 +478,7 @@ export const regexMatchingDeadlineError = (
|
||||
// A one-shot invocation has ended its session before the caller retries.
|
||||
continuingCallerGuidance: {
|
||||
userMessage: "The service remains available.",
|
||||
remediationAction: `${regexMatchingDeadlineRecovery(deadline)} Simplify the ECMAScript Unicode regex or use literal mode, then retry in the same session.`,
|
||||
remediationAction: `${regexMatchingDeadlineContinuingRecovery(deadline)} Simplify the ECMAScript Unicode regex or use literal mode, then retry in the same session.`,
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -515,7 +517,10 @@ export const regexWorkerHeapError = (
|
||||
),
|
||||
}
|
||||
: {}),
|
||||
remediationAction: `${heapBound(heap, request)} Use update_analysis_settings with setting regex.worker_heap_request_mib to change the request for the next worker; launch heap flags can override it. Restart REA after changing --max-old-space-size or ${REGEX_WORKER_HEAP_REQUEST_SETTING} in the launch environment. A launch --max-old-space-size-percentage takes precedence over that flag in either order. Simplify the pattern or use literal mode.`,
|
||||
remediationAction: `${heapBound(heap, request)} Set ${REGEX_WORKER_HEAP_REQUEST_SETTING} in the environment that starts the next REA invocation. Restart REA after changing --max-old-space-size. A launch --max-old-space-size-percentage takes precedence over that flag in either order. Simplify the pattern or use literal mode.`,
|
||||
continuingCallerGuidance: {
|
||||
remediationAction: `${heapBound(heap, request, regexWorkerHeapRequestContinuingRecovery(request))} Call update_analysis_settings with setting regex.worker_heap_request_mib to change the request for the next worker in this session. Launch heap flags can still override it. The launch seed remains ${REGEX_WORKER_HEAP_REQUEST_SETTING}. A launch --max-old-space-size-percentage takes precedence over --max-old-space-size in either order. Simplify the pattern or use literal mode.`,
|
||||
},
|
||||
},
|
||||
);
|
||||
/**
|
||||
|
||||
Reference in new issue
Block a user