fix(cursor): close ReadableStream controller on abort to prevent consumer hangs

When abort fires after streaming begins, onAbort closed HTTP/2 handles
but never closed the ReadableStream controller. Consumers waiting on
reader.read() could hang indefinitely since end/error handlers were
gated by streamClosed flag.

Hoist controller reference to outer scope so abort handler can close it.
This commit is contained in:
Tam Nhu Tran
2026-02-13 05:33:56 +07:00
parent ca7bb23970
commit 36a67c4837
+11
View File
@@ -416,9 +416,18 @@ export class CursorExecutor {
// Register abort before response headers arrive so cancellation works at all stages
let streamClosed = false;
let streamController: ReadableStreamDefaultController<Uint8Array> | null = null;
if (signal) {
const onAbort = () => {
streamClosed = true;
// Close the ReadableStream controller so consumers don't hang on reader.read()
if (streamController) {
try {
streamController.close();
} catch {
/* already closed */
}
}
req.close();
client.close();
};
@@ -471,6 +480,8 @@ export class CursorExecutor {
const readable = new ReadableStream<Uint8Array>({
start(controller) {
streamController = controller;
const emitSSE = (data: string) => {
if (streamClosed) return;
controller.enqueue(enc.encode(`data: ${data}\n\n`));