Files
rea/tests/support/cli/cliCommandSessionFrames.mjs
T
ccfaf520b7 perf(test): reuse one compiled CLI process per JavaScript application file (#5676)
* perf(test): reuse one compiled CLI process per JavaScript application file

analyzeJavaScriptDirectory CLI cases were still starting a fresh process
after stdio MCP reuse. Keep one compiled process for the same child
environment so later cases skip Node and CLI startup.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(test): bound shared CLI worker lifetime

Retire a worker that reports a fatal failure before resolving that command,
and wait for it to exit before the next command starts. Join a session frame
once its bytes are complete instead of copying the unfinished buffer on every
chunk. Restart the shared process after 32 commands or a reported 1 GiB
resident set so one file does not keep a single heap high-water mark.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(test): retire a shared CLI worker after a transport failure

A malformed session frame rejected the command and sent SIGTERM without
clearing the worker. signalCode stays unset until the process exits, so the
next queued command could write to that same process. Transport failures now
use the fatal-reply retirement path and the queue waits for that exit.

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>
2026-10-21 12:02:15 +08:00

78 lines
2.6 KiB
JavaScript

/** Largest accepted session message, including the encoded command result. */
export const MAX_CLI_SESSION_FRAME_BYTES = 64 * 1024 * 1024;
/** Length-prefixed JSON frame shared by the session parent and worker. */
export const encodeCliSessionFrame = (value) => {
const body = Buffer.from(JSON.stringify(value));
if (body.length > MAX_CLI_SESSION_FRAME_BYTES) {
throw new Error(
`CLI session frame is ${body.length} bytes; the limit is ${MAX_CLI_SESSION_FRAME_BYTES}`,
);
}
const frame = Buffer.allocUnsafe(4 + body.length);
frame.writeUInt32BE(body.length, 0);
body.copy(frame, 4);
return frame;
};
const frameLimitError = (length) =>
new Error(
`CLI session frame is ${length} bytes; the limit is ${MAX_CLI_SESSION_FRAME_BYTES}`,
);
/** Read a big-endian length from the start of a chunk list without joining it. */
const readFrameLength = (chunks) => {
const first = chunks[0];
if (first.length >= 4) return first.readUInt32BE(0);
const header = Buffer.allocUnsafe(4);
let offset = 0;
for (const chunk of chunks) {
const take = Math.min(chunk.length, 4 - offset);
chunk.copy(header, offset, 0, take);
offset += take;
if (offset === 4) break;
}
return header.readUInt32BE(0);
};
/**
* Parse complete frames from a byte stream. An oversized or malformed frame
* fails the reader instead of resynchronizing on a later length. Incomplete
* chunks stay separate until one whole frame is buffered, then that frame is
* joined once.
*/
export const createCliSessionFrameReader = (onFrame) => {
/** @type {Buffer[]} */
const chunks = [];
let buffered = 0;
return (chunk) => {
const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
chunks.push(bytes);
buffered += bytes.length;
if (buffered < 4) return;
const length = readFrameLength(chunks);
if (length > MAX_CLI_SESSION_FRAME_BYTES) throw frameLimitError(length);
if (buffered < 4 + length) return;
const pending = Buffer.concat(chunks);
let offset = 0;
try {
while (offset + 4 <= pending.length) {
const frameLength = pending.readUInt32BE(offset);
if (frameLength > MAX_CLI_SESSION_FRAME_BYTES) {
throw frameLimitError(frameLength);
}
const frameEnd = offset + 4 + frameLength;
if (frameEnd > pending.length) break;
const body = pending.toString("utf8", offset + 4, frameEnd);
offset = frameEnd;
onFrame(JSON.parse(body));
}
} finally {
const rest = pending.subarray(offset);
chunks.length = 0;
buffered = rest.length;
if (rest.length > 0) chunks.push(Buffer.from(rest));
}
};
};