fix(coding-agent): retry RPC stdout backpressure

closes #4897
This commit is contained in:
Armin Ronacher
2026-05-24 22:59:35 +02:00
parent 9600ded922
commit ce0e801d8e
4 changed files with 76 additions and 22 deletions

View File

@@ -6,6 +6,42 @@ interface StdoutTakeoverState {
let stdoutTakeoverState: StdoutTakeoverState | undefined;
const RAW_STDOUT_RETRY_DELAY_MS = 10;
let rawStdoutWriteTail: Promise<void> = Promise.resolve();
function getRawStdoutWrite(): StdoutTakeoverState["rawStdoutWrite"] {
if (stdoutTakeoverState) {
return stdoutTakeoverState.rawStdoutWrite;
}
return process.stdout.write.bind(process.stdout) as StdoutTakeoverState["rawStdoutWrite"];
}
async function writeRawStdoutChunk(text: string): Promise<void> {
while (true) {
try {
await new Promise<void>((resolve, reject) => {
try {
getRawStdoutWrite()(text, (error) => {
if (error) reject(error);
else resolve();
});
} catch (error) {
reject(error instanceof Error ? error : new Error(String(error)));
}
});
return;
} catch (error) {
const writeError = error instanceof Error ? error : new Error(String(error));
const code = (writeError as Error & { code?: unknown }).code;
if (code !== "ENOBUFS" && code !== "EAGAIN" && code !== "EWOULDBLOCK") {
throw writeError;
}
await new Promise<void>((resolve) => setTimeout(resolve, RAW_STDOUT_RETRY_DELAY_MS));
}
}
}
export function takeOverStdout(): void {
if (stdoutTakeoverState) {
return;
@@ -47,28 +83,26 @@ export function isStdoutTakenOver(): boolean {
}
export function writeRawStdout(text: string): void {
if (stdoutTakeoverState) {
stdoutTakeoverState.rawStdoutWrite(text);
if (text.length === 0) {
return;
}
process.stdout.write(text);
rawStdoutWriteTail = rawStdoutWriteTail.then(() => writeRawStdoutChunk(text));
void rawStdoutWriteTail.catch(() => {
process.exit(1);
});
}
export async function waitForRawStdoutBackpressure(): Promise<void> {
while (true) {
const tail = rawStdoutWriteTail;
await tail;
if (tail === rawStdoutWriteTail) {
return;
}
}
}
export async function flushRawStdout(): Promise<void> {
if (stdoutTakeoverState) {
await new Promise<void>((resolve, reject) => {
stdoutTakeoverState?.rawStdoutWrite("", (err) => {
if (err) reject(err);
else resolve();
});
});
return;
}
await new Promise<void>((resolve, reject) => {
process.stdout.write("", (err) => {
if (err) reject(err);
else resolve();
});
});
await waitForRawStdoutBackpressure();
await writeRawStdoutChunk("");
}