fix: prevent AbortSignal listener memory leak

- Add abort listener cleanup in Query.close() to prevent memory leak
- Add abort listener cleanup in ControlDispatcher.shutdown()
- Remove AbortController recreation in Session.handleInterrupt()

This fixes the MaxListenersExceededWarning that occurred when:
- Creating 11+ Query instances in SDK/non-interactive mode
- Multiple user interrupts (Ctrl+C) in interactive mode
- Intensive control request scenarios
This commit is contained in:
LaZzyMan 2026-02-12 10:39:19 +08:00
parent 66f754e203
commit 3f04217458
3 changed files with 26 additions and 5 deletions

View file

@ -80,6 +80,8 @@ export class ControlDispatcher implements IPendingRequestRegistry {
private pendingOutgoingRequests: Map<string, PendingOutgoingRequest> =
new Map();
private abortHandler: (() => void) | null = null;
constructor(context: IControlContext) {
this.context = context;
@ -102,9 +104,10 @@ export class ControlDispatcher implements IPendingRequestRegistry {
// this.hookController = new HookController(context, this, 'HookController');
// Listen for main abort signal
this.context.abortSignal.addEventListener('abort', () => {
this.abortHandler = () => {
this.shutdown();
});
};
this.context.abortSignal.addEventListener('abort', this.abortHandler);
}
/**
@ -240,6 +243,12 @@ export class ControlDispatcher implements IPendingRequestRegistry {
shutdown(): void {
debugLogger.debug('[ControlDispatcher] Shutting down');
// Remove abort listener to prevent memory leak
if (this.abortHandler) {
this.context.abortSignal.removeEventListener('abort', this.abortHandler);
this.abortHandler = null;
}
// Cancel all incoming requests
for (const [
_requestId,

View file

@ -408,7 +408,8 @@ class Session {
private handleInterrupt(): void {
debugLogger.info('[Session] Interrupt requested');
this.abortController.abort();
this.abortController = new AbortController();
// Do not create a new AbortController to prevent listener leaks.
// Subsequent queries will check signal.aborted and fail immediately.
}
private setupSignalHandlers(): void {