Skip to content

Commit 97d029e

Browse files
committed
fix: prevent memory leaks from AbortController in async operations
1 parent 030e534 commit 97d029e

File tree

1 file changed

+48
-12
lines changed

1 file changed

+48
-12
lines changed

core/core.ts

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,38 @@ export class Core {
101101
this.messageAbortControllers.get(messageId)?.abort();
102102
}
103103

104+
105+
/**
106+
* Wraps async task execution with automatic AbortController cleanup.
107+
*/
108+
private runWithAbortController<T extends Promise<any> | AsyncGenerator<any>>(
109+
id: string,
110+
task: (controller: AbortController) => T
111+
): T {
112+
const controller = this.addMessageAbortController(id);
113+
const cleanup = () => this.abortById(id);
114+
115+
try {
116+
const result = task(controller);
117+
118+
if (result instanceof Promise) {
119+
return result.finally(cleanup) as T;
120+
}
121+
122+
// AsyncGenerator handling (intentionally skipping return/throw as caller only consumes via next())
123+
return (async function* () {
124+
try {
125+
yield* result;
126+
} finally {
127+
cleanup();
128+
}
129+
})() as T;
130+
} catch (error) {
131+
cleanup();
132+
throw error;
133+
}
134+
}
135+
104136
invoke<T extends keyof ToCoreProtocol>(
105137
messageType: T,
106138
data: ToCoreProtocol[T][0],
@@ -430,13 +462,14 @@ export class Core {
430462
});
431463

432464
on("llm/streamChat", (msg) => {
433-
const abortController = this.addMessageAbortController(msg.messageId);
434-
return llmStreamChat(
435-
this.configHandler,
436-
abortController,
437-
msg,
438-
this.ide,
439-
this.messenger,
465+
return this.runWithAbortController(msg.messageId, (abortController) =>
466+
llmStreamChat(
467+
this.configHandler,
468+
abortController,
469+
msg,
470+
this.ide,
471+
this.messenger,
472+
),
440473
);
441474
});
442475

@@ -446,12 +479,15 @@ export class Core {
446479
if (!model) {
447480
throw new Error("No chat model selected");
448481
}
449-
const abortController = this.addMessageAbortController(msg.messageId);
450482

451-
const completion = await model.complete(
452-
msg.data.prompt,
453-
abortController.signal,
454-
msg.data.completionOptions,
483+
const completion = await this.runWithAbortController(
484+
msg.messageId,
485+
(abortController) =>
486+
model.complete(
487+
msg.data.prompt,
488+
abortController.signal,
489+
msg.data.completionOptions,
490+
),
455491
);
456492
return completion;
457493
});

0 commit comments

Comments
 (0)