@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed a crash when undici emits an internal client error while terminating a mid-stream HTTP response ([#6133](https://github.com/earendil-works/pi/issues/6133)).
|
||||||
- Fixed the compaction event regression test to cover status indicator cleanup and keep CI passing.
|
- Fixed the compaction event regression test to cover status indicator cleanup and keep CI passing.
|
||||||
- Fixed interactive status indicators so ending work, retry, compaction, or branch-summary indicators no longer shrink the TUI when clear-on-shrink is enabled ([#6026](https://github.com/earendil-works/pi/pull/6026)).
|
- Fixed interactive status indicators so ending work, retry, compaction, or branch-summary indicators no longer shrink the TUI when clear-on-shrink is enabled ([#6026](https://github.com/earendil-works/pi/pull/6026)).
|
||||||
- Fixed `--session` and `SessionManager.open()` to reject non-empty invalid session files without overwriting them ([#6002](https://github.com/earendil-works/pi/issues/6002)).
|
- Fixed `--session` and `SessionManager.open()` to reject non-empty invalid session files without overwriting them ([#6002](https://github.com/earendil-works/pi/issues/6002)).
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { EventEmitter } from "node:events";
|
||||||
import * as undici from "undici";
|
import * as undici from "undici";
|
||||||
|
|
||||||
export const DEFAULT_HTTP_IDLE_TIMEOUT_MS = 300_000;
|
export const DEFAULT_HTTP_IDLE_TIMEOUT_MS = 300_000;
|
||||||
@@ -46,18 +47,50 @@ export function applyHttpProxySettings(httpProxy: string | undefined): void {
|
|||||||
process.env.HTTPS_PROXY ??= proxy;
|
process.env.HTTPS_PROXY ??= proxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ignoreUndiciDispatcherError = (_error: unknown): void => {};
|
||||||
|
|
||||||
|
// Undici can emit an internal Client "error" while terminating a mid-stream
|
||||||
|
// fetch body. The body stream still rejects through reader.read(); this listener
|
||||||
|
// only prevents EventEmitter's unhandled "error" special case from crashing pi.
|
||||||
|
function withUndiciErrorListener<T extends undici.Dispatcher>(dispatcher: T): T {
|
||||||
|
if (dispatcher instanceof EventEmitter) {
|
||||||
|
EventEmitter.prototype.on.call(dispatcher, "error", ignoreUndiciDispatcherError);
|
||||||
|
}
|
||||||
|
return dispatcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createUndiciClient(origin: string | URL, options: object): undici.Dispatcher {
|
||||||
|
return withUndiciErrorListener(new undici.Client(origin, options as undici.Client.Options));
|
||||||
|
}
|
||||||
|
|
||||||
|
function createUndiciOriginDispatcher(origin: string | URL, options: object): undici.Dispatcher {
|
||||||
|
const dispatcherOptions = options as undici.Pool.Options;
|
||||||
|
if (dispatcherOptions.connections === 1) {
|
||||||
|
return createUndiciClient(origin, dispatcherOptions);
|
||||||
|
}
|
||||||
|
return withUndiciErrorListener(
|
||||||
|
new undici.Pool(origin, {
|
||||||
|
...dispatcherOptions,
|
||||||
|
factory: createUndiciClient,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function configureHttpDispatcher(timeoutMs: number = DEFAULT_HTTP_IDLE_TIMEOUT_MS): void {
|
export function configureHttpDispatcher(timeoutMs: number = DEFAULT_HTTP_IDLE_TIMEOUT_MS): void {
|
||||||
const normalizedTimeoutMs = parseHttpIdleTimeoutMs(timeoutMs);
|
const normalizedTimeoutMs = parseHttpIdleTimeoutMs(timeoutMs);
|
||||||
if (normalizedTimeoutMs === undefined) {
|
if (normalizedTimeoutMs === undefined) {
|
||||||
throw new Error(`Invalid HTTP idle timeout: ${String(timeoutMs)}`);
|
throw new Error(`Invalid HTTP idle timeout: ${String(timeoutMs)}`);
|
||||||
}
|
}
|
||||||
undici.setGlobalDispatcher(
|
const dispatcher = withUndiciErrorListener(
|
||||||
new undici.EnvHttpProxyAgent({
|
new undici.EnvHttpProxyAgent({
|
||||||
allowH2: false,
|
allowH2: false,
|
||||||
bodyTimeout: normalizedTimeoutMs,
|
bodyTimeout: normalizedTimeoutMs,
|
||||||
headersTimeout: normalizedTimeoutMs,
|
headersTimeout: normalizedTimeoutMs,
|
||||||
|
clientFactory: createUndiciClient,
|
||||||
|
factory: createUndiciOriginDispatcher,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
undici.setGlobalDispatcher(dispatcher);
|
||||||
// Keep fetch and the dispatcher on the same undici implementation. Node 26.0's
|
// Keep fetch and the dispatcher on the same undici implementation. Node 26.0's
|
||||||
// bundled fetch can otherwise consume compressed responses through npm undici's
|
// bundled fetch can otherwise consume compressed responses through npm undici's
|
||||||
// dispatcher without decompressing them, causing response.json() failures.
|
// dispatcher without decompressing them, causing response.json() failures.
|
||||||
|
|||||||
Reference in New Issue
Block a user