Skip to content

Commit 30026af

Browse files
committed
fix: possible EventEmitter memory leak detected
1 parent 5ecdb9d commit 30026af

1 file changed

Lines changed: 44 additions & 15 deletions

File tree

packages/executor/src/execution/execute.ts

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,35 @@ export function execute<TData = any, TVariables = any, TContext = any>(
287287
return executeImpl(exeContext);
288288
}
289289

290+
// AbortSignal handler cache to avoid the "possible EventEmitter memory leak detected"
291+
// on Node.js
292+
const abortSignalHandlers = new WeakMap<AbortSignal, Set<VoidFunction>>();
293+
294+
/**
295+
* Register an AbortSignal handler for a signal.
296+
* This helper function mainly exists to work around the
297+
* "possible EventEmitter memory leak detected. 11 listeners added. Use emitter.setMaxListeners() to increase limit."
298+
* warning occuring on Node.js
299+
*/
300+
function registerAbortSignalHandler(signal: AbortSignal, handler: VoidFunction): void {
301+
let handlers = abortSignalHandlers.get(signal);
302+
303+
if (!Array.isArray(handlers)) {
304+
handlers = new Set();
305+
abortSignalHandlers.set(signal, handlers);
306+
307+
function abortSignalHandler() {
308+
for (const handler of handlers!) {
309+
handler();
310+
}
311+
}
312+
313+
signal.addEventListener('abort', abortSignalHandler, { once: true });
314+
}
315+
316+
handlers.add(handler);
317+
}
318+
290319
function executeImpl<TData = any, TVariables = any, TContext = any>(
291320
exeContext: ExecutionContext<TVariables, TContext>,
292321
): MaybePromise<SingularExecutionResult<TData> | IncrementalExecutionResults<TData>> {
@@ -958,13 +987,12 @@ async function completeAsyncIteratorValue(
958987
iterator: AsyncIterator<unknown>,
959988
asyncPayloadRecord?: AsyncPayloadRecord,
960989
): Promise<ReadonlyArray<unknown>> {
961-
exeContext.signal?.addEventListener(
962-
'abort',
963-
() => {
990+
if (exeContext.signal) {
991+
registerAbortSignalHandler(exeContext.signal, () => {
964992
iterator.return?.();
965-
},
966-
{ once: true },
967-
);
993+
});
994+
}
995+
968996
const errors = asyncPayloadRecord?.errors ?? exeContext.errors;
969997
const stream = getStreamValues(exeContext, fieldNodes, path);
970998
let containsPromise = false;
@@ -1761,9 +1789,12 @@ function assertEventStream(result: unknown, signal?: AbortSignal): AsyncIterable
17611789
return {
17621790
[Symbol.asyncIterator]() {
17631791
const asyncIterator = result[Symbol.asyncIterator]();
1764-
signal?.addEventListener('abort', () => {
1765-
asyncIterator.return?.();
1766-
});
1792+
if (signal) {
1793+
registerAbortSignalHandler(signal, () => {
1794+
asyncIterator.return?.();
1795+
});
1796+
}
1797+
17671798
return asyncIterator;
17681799
},
17691800
};
@@ -2085,14 +2116,12 @@ function yieldSubsequentPayloads(
20852116
let isDone = false;
20862117

20872118
const abortPromise = new Promise<void>((_, reject) => {
2088-
exeContext.signal?.addEventListener(
2089-
'abort',
2090-
() => {
2119+
if (exeContext.signal) {
2120+
registerAbortSignalHandler(exeContext.signal, () => {
20912121
isDone = true;
20922122
reject(exeContext.signal?.reason);
2093-
},
2094-
{ once: true },
2095-
);
2123+
});
2124+
}
20962125
});
20972126

20982127
async function next(): Promise<IteratorResult<SubsequentIncrementalExecutionResult, void>> {

0 commit comments

Comments
 (0)