fix: restore no-proxy dispatcher boundary

This commit is contained in:
Shakker
2026-05-06 03:17:57 +01:00
committed by Shakker
parent 6807da544b
commit c9c66d7a1d
2 changed files with 24 additions and 43 deletions

View File

@@ -106,32 +106,24 @@ describe("ensureGlobalUndiciStreamTimeouts", () => {
vi.mocked(resolveEnvHttpProxyAgentOptions).mockReturnValue(undefined);
});
it("replaces direct Agent dispatcher with extended stream timeouts when no env proxy is configured", () => {
it("records timeout bridge without importing undici when no env proxy is configured", () => {
getDefaultAutoSelectFamily.mockReturnValue(true);
ensureGlobalUndiciStreamTimeouts();
expect(loadUndiciGlobalDispatcherDeps).toHaveBeenCalledTimes(1);
expect(setGlobalDispatcher).toHaveBeenCalledTimes(1);
const next = getCurrentDispatcher() as { options?: Record<string, unknown> };
expect(next).toBeInstanceOf(Agent);
expect(next.options?.bodyTimeout).toBe(DEFAULT_UNDICI_STREAM_TIMEOUT_MS);
expect(next.options?.headersTimeout).toBe(DEFAULT_UNDICI_STREAM_TIMEOUT_MS);
expect(next.options?.connect).toEqual({
autoSelectFamily: true,
autoSelectFamilyAttemptTimeout: 300,
});
expect(loadUndiciGlobalDispatcherDeps).not.toHaveBeenCalled();
expect(setGlobalDispatcher).not.toHaveBeenCalled();
expect(undiciGlobalDispatcherModule._globalUndiciStreamTimeoutMs).toBe(
DEFAULT_UNDICI_STREAM_TIMEOUT_MS,
);
});
it("does not initialize the undici global dispatcher during no-proxy bootstrap", () => {
it("does not initialize the undici global dispatcher in a no-proxy subprocess", () => {
const moduleUrl = pathToFileURL(path.resolve("src/infra/net/undici-global-dispatcher.ts")).href;
const source = `
const dispatcherKey = Symbol.for("undici.globalDispatcher.1");
const mod = await import(${JSON.stringify(moduleUrl)});
mod.ensureGlobalUndiciEnvProxyDispatcher();
mod.ensureGlobalUndiciStreamTimeouts({ timeoutMs: 1_900_000 });
if (globalThis[dispatcherKey] !== undefined) {
throw new Error("undici global dispatcher was initialized");
}
@@ -222,12 +214,8 @@ describe("ensureGlobalUndiciStreamTimeouts", () => {
it("does not lower global stream timeouts below the default floor", () => {
ensureGlobalUndiciStreamTimeouts({ timeoutMs: 15_000 });
expect(loadUndiciGlobalDispatcherDeps).toHaveBeenCalledTimes(1);
expect(setGlobalDispatcher).toHaveBeenCalledTimes(1);
const next = getCurrentDispatcher() as { options?: Record<string, unknown> };
expect(next).toBeInstanceOf(Agent);
expect(next.options?.bodyTimeout).toBe(DEFAULT_UNDICI_STREAM_TIMEOUT_MS);
expect(next.options?.headersTimeout).toBe(DEFAULT_UNDICI_STREAM_TIMEOUT_MS);
expect(loadUndiciGlobalDispatcherDeps).not.toHaveBeenCalled();
expect(setGlobalDispatcher).not.toHaveBeenCalled();
expect(undiciGlobalDispatcherModule._globalUndiciStreamTimeoutMs).toBe(
DEFAULT_UNDICI_STREAM_TIMEOUT_MS,
);
@@ -238,12 +226,8 @@ describe("ensureGlobalUndiciStreamTimeouts", () => {
ensureGlobalUndiciStreamTimeouts({ timeoutMs });
expect(loadUndiciGlobalDispatcherDeps).toHaveBeenCalledTimes(1);
expect(setGlobalDispatcher).toHaveBeenCalledTimes(1);
const next = getCurrentDispatcher() as { options?: Record<string, unknown> };
expect(next).toBeInstanceOf(Agent);
expect(next.options?.bodyTimeout).toBe(timeoutMs);
expect(next.options?.headersTimeout).toBe(timeoutMs);
expect(loadUndiciGlobalDispatcherDeps).not.toHaveBeenCalled();
expect(setGlobalDispatcher).not.toHaveBeenCalled();
expect(undiciGlobalDispatcherModule._globalUndiciStreamTimeoutMs).toBe(timeoutMs);
});

View File

@@ -99,12 +99,19 @@ export function ensureGlobalUndiciStreamTimeouts(opts?: { timeoutMs?: number }):
}
const timeoutMs = Math.max(DEFAULT_UNDICI_STREAM_TIMEOUT_MS, Math.floor(timeoutMsRaw));
_globalUndiciStreamTimeoutMs = timeoutMs;
if (!hasEnvHttpProxyAgentConfigured()) {
lastAppliedTimeoutKey = null;
return;
}
const runtime = loadUndiciGlobalDispatcherDeps();
const { Agent, EnvHttpProxyAgent, setGlobalDispatcher } = runtime;
const { EnvHttpProxyAgent, setGlobalDispatcher } = runtime;
const kind = resolveCurrentDispatcherKind(runtime);
if (kind === null) {
return;
}
if (kind !== "env-proxy") {
return;
}
const autoSelectFamily = resolveUndiciAutoSelectFamily();
const nextKey = resolveDispatcherKey({ kind, timeoutMs, autoSelectFamily });
@@ -114,23 +121,13 @@ export function ensureGlobalUndiciStreamTimeouts(opts?: { timeoutMs?: number }):
const connect = createUndiciAutoSelectFamilyConnectOptions(autoSelectFamily);
try {
if (kind === "env-proxy") {
const proxyOptions = {
...resolveEnvHttpProxyAgentOptions(),
bodyTimeout: timeoutMs,
headersTimeout: timeoutMs,
...(connect ? { connect } : {}),
} as ConstructorParameters<UndiciGlobalDispatcherDeps["EnvHttpProxyAgent"]>[0];
setGlobalDispatcher(new EnvHttpProxyAgent(proxyOptions));
} else {
setGlobalDispatcher(
new Agent({
bodyTimeout: timeoutMs,
headersTimeout: timeoutMs,
...(connect ? { connect } : {}),
}),
);
}
const proxyOptions = {
...resolveEnvHttpProxyAgentOptions(),
bodyTimeout: timeoutMs,
headersTimeout: timeoutMs,
...(connect ? { connect } : {}),
} as ConstructorParameters<UndiciGlobalDispatcherDeps["EnvHttpProxyAgent"]>[0];
setGlobalDispatcher(new EnvHttpProxyAgent(proxyOptions));
lastAppliedTimeoutKey = nextKey;
} catch {
// Best-effort hardening only.