Skip to content

Commit c36d6fc

Browse files
committed
fix: harden HTTP backend error handling
1 parent 14c954d commit c36d6fc

5 files changed

Lines changed: 753 additions & 16 deletions

File tree

src/connection.ts

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -443,8 +443,16 @@ export class InMemoryAcpHttpBackend implements AcpHttpBackend {
443443
});
444444

445445
try {
446-
await connection.writeInbound(message);
447-
const response = await connection.recvInitial(message.id);
446+
const discard = (): void => {
447+
this.registry.discard(connection.connectionId);
448+
};
449+
450+
await raceAbort(connection.writeInbound(message), signal, discard);
451+
const response = await raceAbort(
452+
connection.recvInitial(message.id),
453+
signal,
454+
discard,
455+
);
448456

449457
if (signal.aborted) {
450458
throw new Error("Request aborted");
@@ -677,3 +685,35 @@ function isMatchingResponse(
677685
): msg is AnyResponse {
678686
return "id" in msg && !("method" in msg) && msg.id === id;
679687
}
688+
689+
async function raceAbort<T>(
690+
promise: Promise<T>,
691+
signal: AbortSignal,
692+
onAbort: () => void,
693+
): Promise<T> {
694+
promise.catch(() => undefined);
695+
696+
if (signal.aborted) {
697+
onAbort();
698+
throw new Error("Request aborted");
699+
}
700+
701+
let removeAbortListener: () => void = () => {};
702+
const abortPromise = new Promise<never>((_resolve, reject) => {
703+
const abort = (): void => {
704+
onAbort();
705+
reject(new Error("Request aborted"));
706+
};
707+
708+
signal.addEventListener("abort", abort, { once: true });
709+
removeAbortListener = () => {
710+
signal.removeEventListener("abort", abort);
711+
};
712+
});
713+
714+
try {
715+
return await Promise.race([promise, abortPromise]);
716+
} finally {
717+
removeAbortListener();
718+
}
719+
}

0 commit comments

Comments
 (0)