perf(serve): keep the ACP spawn channel out of Managed Runtime workers (#16235)

The worker sizes media responses with DAEMON_ACP_NDJSON_LIMITS, which
managed-tool-media imported from acp-bridge's spawnChannel. That module
pulls in the ACP SDK schemas, the acp-bridge status module and, through
it, core's package root, so every resident worker loaded about 1.5 MiB
of modules for one constant.

Define the constant in the dependency-free ndJsonStream module,
re-export it from spawnChannel, and import it from there. On Linux
x86_64 an idle worker drops from 91.0 to 79.6 MiB of private memory and
from 215 to 175 loaded modules.
This commit is contained in:
Shaojin Wen authored and GitHub committed 2026-11-05 14:42:05 +00:00
1 parent c8eaa04e81
commit cc78e35d5f
4 files changed
+28 -8

No files matched your search

+12
View File
@@ -62,6 +62,18 @@ export interface NdJsonStreamLimits {
queueSaturationGraceMs?: number;
}
/**
* The daemon's ACP channel limits. Kept in this dependency-free module so a
* Managed Runtime worker can size media responses without loading the whole
* spawn channel; `spawnChannel` re-exports it.
*/
export const DAEMON_ACP_NDJSON_LIMITS: Readonly<NdJsonStreamLimits> =
Object.freeze({
maxFrameBytes: 64 * 1024 * 1024,
maxQueuedMessages: 256,
maxQueuedBytes: 64 * 1024 * 1024,
});
export type NdJsonInboundMessageValidator = (message: AnyMessage) => boolean;
export class NdJsonFrameTooLargeError extends Error {
@@ -66,6 +66,7 @@ import {
getAcpMemoryArgs,
scrubChildEnv,
} from './spawnChannel.js';
import { DAEMON_ACP_NDJSON_LIMITS as LEAF_DAEMON_ACP_NDJSON_LIMITS } from './ndJsonStream.js';
function createFakeChildProcess(): ChildProcess {
const child = Object.assign(new EventEmitter(), {
@@ -597,6 +598,13 @@ describe('createSpawnChannelFactory env policy', () => {
});
});
it('re-exports the daemon limits that Managed Runtime workers read from the leaf module', () => {
// Workers import the limits from ndJsonStream so they do not load this
// module; both names must stay one object.
expect(DAEMON_ACP_NDJSON_LIMITS).toBe(LEAF_DAEMON_ACP_NDJSON_LIMITS);
expect(Object.isFrozen(LEAF_DAEMON_ACP_NDJSON_LIMITS)).toBe(true);
});
it('keeps the default factory unbounded and validates opt-in limits early', () => {
expect(DAEMON_ACP_NDJSON_LIMITS).toEqual({
maxFrameBytes: 64 * 1024 * 1024,
+2 -6
View File
@@ -26,6 +26,7 @@ import type { ChannelFactory } from './channel.js';
import { markChannelFactoryForwardsChildEnv } from './child-env-forwarding.js';
import { redactLogCredentials } from './logRedaction.js';
import {
DAEMON_ACP_NDJSON_LIMITS,
NdJsonQueueLimitError,
ndJsonStream,
type NdJsonMessageObservation,
@@ -43,12 +44,7 @@ import { estimateJsonStringBytes } from './json-string-bytes.js';
import { detectAvailableMemoryMb } from './daemon-memory-budget.js';
let cachedMemoryArgs: string[] | undefined;
export const DAEMON_ACP_NDJSON_LIMITS: Readonly<NdJsonStreamLimits> =
Object.freeze({
maxFrameBytes: 64 * 1024 * 1024,
maxQueuedMessages: 256,
maxQueuedBytes: 64 * 1024 * 1024,
});
export { DAEMON_ACP_NDJSON_LIMITS };
const daemonClientParamValidators = new Map<
string,
@@ -4,8 +4,12 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { hasBoundedJsonStructure } from '@qwen-code/acp-bridge/ndJsonStream';
import { DAEMON_ACP_NDJSON_LIMITS } from '@qwen-code/acp-bridge/spawnChannel';
// The leaf module, not spawnChannel: that one loads about 1.5 MiB of modules
// into every Managed Runtime worker for this one constant.
import {
DAEMON_ACP_NDJSON_LIMITS,
hasBoundedJsonStructure,
} from '@qwen-code/acp-bridge/ndJsonStream';
// Leave room for the JSON-RPC/HTTP envelopes without changing ACP capacity.
export const MAX_MANAGED_MEDIA_RESPONSE_BYTES =