mirror of
https://github.com/morluto/rea.git
synced 2026-11-14 10:43:30 +00:00
* feat(captures): make the HAR/SAZ/qlog decoder heap configurable REA_CAPTURE_DECODER_OLD_GENERATION_MIB keeps the 192 MiB default and refuses values outside 8 to 16384 MiB instead of falling back. Co-authored-by: morluto <morluto@users.noreply.github.com> * fix(captures): defer decoder startup and share one parsed heap Invalid REA_CAPTURE_DECODER_OLD_GENERATION_MIB threw while CLI commands were registered, so doctor and help never ran. Build the capture service only when inspect-web-network-capture runs. Resolve the heap once in composition and pass that fact to each Node adapter. Limitation and memory-failure reports use the selected adapter's heap, so a decoder built with a different environment does not describe another limit. Mock captureDecoderProcessMemoryLimitBytes in the Node adapter test so a Windows Bun host still exercises the unavailable-bound refusal. Co-authored-by: morluto <morluto@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: morluto <morluto@users.noreply.github.com>
106 lines
5.1 KiB
TypeScript
106 lines
5.1 KiB
TypeScript
import { z } from "zod";
|
|
|
|
import { ConfigurationError } from "../domain/configurationErrors.js";
|
|
import { err, ok, type Result } from "../domain/result.js";
|
|
|
|
export const CAPTURE_DECODER_OLD_GENERATION_SETTING =
|
|
"REA_CAPTURE_DECODER_OLD_GENERATION_MIB";
|
|
|
|
/** Old-generation mebibytes used when the setting is unset. Today's constant. */
|
|
export const DEFAULT_CAPTURE_DECODER_OLD_GENERATION_MIB = 192;
|
|
|
|
/**
|
|
* Semi-space the owned decoder always passes. It is not a setting. An old
|
|
* generation smaller than this semi-space aborts Node during isolate startup
|
|
* instead of enforcing a heap limit, so the setting's floor is this value.
|
|
*/
|
|
export const CAPTURE_DECODER_SEMI_SPACE_MIB = 8;
|
|
|
|
/** Smallest old generation that still starts the decoder with that semi-space. */
|
|
export const MINIMUM_CAPTURE_DECODER_OLD_GENERATION_MIB =
|
|
CAPTURE_DECODER_SEMI_SPACE_MIB;
|
|
|
|
/**
|
|
* Largest old generation one owned decoder may request. 16 GiB is the same
|
|
* ceiling REA already uses for an isolated JavaScript analysis worker. Node
|
|
* will start with a larger flag; this maximum is REA's refusal, so one
|
|
* decoder cannot ask for an unbounded old generation.
|
|
*/
|
|
export const MAXIMUM_CAPTURE_DECODER_OLD_GENERATION_MIB = 16_384;
|
|
|
|
/** Effective HAR/SAZ/qlog old-generation heap and where its value came from. */
|
|
export interface CaptureDecoderHeap {
|
|
readonly oldGenerationMiB: number;
|
|
readonly source: "default" | "setting";
|
|
}
|
|
|
|
export const DEFAULT_CAPTURE_DECODER_HEAP: CaptureDecoderHeap = {
|
|
oldGenerationMiB: DEFAULT_CAPTURE_DECODER_OLD_GENERATION_MIB,
|
|
source: "default",
|
|
};
|
|
|
|
export const captureDecoderHeapConstraint = `${CAPTURE_DECODER_OLD_GENERATION_SETTING} must be a decimal integer from ${String(MINIMUM_CAPTURE_DECODER_OLD_GENERATION_MIB)} to ${String(MAXIMUM_CAPTURE_DECODER_OLD_GENERATION_MIB)} MiB; the default is ${String(DEFAULT_CAPTURE_DECODER_OLD_GENERATION_MIB)} MiB. An old generation below the decoder's ${String(CAPTURE_DECODER_SEMI_SPACE_MIB)} MiB semi-space aborts Node during startup instead of enforcing a limit, and a larger value is refused.`;
|
|
|
|
const decimalInteger = /^[1-9]\d*$/u;
|
|
|
|
const withinHeapRange = (value: string): boolean => {
|
|
// The pattern check reports a non-decimal value. Refine only bounds integers.
|
|
if (!decimalInteger.test(value)) return true;
|
|
const parsed = BigInt(value);
|
|
return (
|
|
parsed >= BigInt(MINIMUM_CAPTURE_DECODER_OLD_GENERATION_MIB) &&
|
|
parsed <= BigInt(MAXIMUM_CAPTURE_DECODER_OLD_GENERATION_MIB)
|
|
);
|
|
};
|
|
|
|
/** Optional decimal mebibyte count for the HAR/SAZ/qlog old-generation heap. */
|
|
export const captureDecoderOldGenerationMiBSchema = z
|
|
.string()
|
|
.regex(decimalInteger, { error: captureDecoderHeapConstraint })
|
|
.refine(withinHeapRange, { error: captureDecoderHeapConstraint })
|
|
.transform(Number);
|
|
|
|
/** Resolve the effective heap from one environment value. */
|
|
export const parseCaptureDecoderHeap = (
|
|
value: string | undefined,
|
|
): Result<CaptureDecoderHeap, ConfigurationError> => {
|
|
if (value === undefined) return ok(DEFAULT_CAPTURE_DECODER_HEAP);
|
|
const parsed = captureDecoderOldGenerationMiBSchema.safeParse(value);
|
|
return parsed.success
|
|
? ok({ oldGenerationMiB: parsed.data, source: "setting" })
|
|
: err(
|
|
new ConfigurationError(captureDecoderHeapConstraint, {
|
|
settings: [
|
|
{
|
|
setting: CAPTURE_DECODER_OLD_GENERATION_SETTING,
|
|
constraint: captureDecoderHeapConstraint,
|
|
},
|
|
],
|
|
}),
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Resolve the heap from a composition root's selected environment. An invalid
|
|
* setting is refused, not ignored. Callers that already resolved it at startup
|
|
* pass that value through instead of reading the environment again.
|
|
*/
|
|
export const resolveCaptureDecoderHeap = (
|
|
environment: Readonly<Record<string, string | undefined>>,
|
|
): CaptureDecoderHeap => {
|
|
const parsed = parseCaptureDecoderHeap(
|
|
environment[CAPTURE_DECODER_OLD_GENERATION_SETTING],
|
|
);
|
|
if (!parsed.ok) throw parsed.error;
|
|
return parsed.value;
|
|
};
|
|
|
|
/** How to read and change the effective HAR/SAZ/qlog old-generation heap. */
|
|
export const captureDecoderHeapRecovery = (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(MINIMUM_CAPTURE_DECODER_OLD_GENERATION_MIB)} MiB, maximum ${String(MAXIMUM_CAPTURE_DECODER_OLD_GENERATION_MIB)} MiB).`,
|
|
`Set ${CAPTURE_DECODER_OLD_GENERATION_SETTING} in the environment that starts REA to a decimal integer from ${String(MINIMUM_CAPTURE_DECODER_OLD_GENERATION_MIB)} to ${String(MAXIMUM_CAPTURE_DECODER_OLD_GENERATION_MIB)}, then restart REA.`,
|
|
`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(" ");
|