Skip to content

Commit

Permalink
fix(cli): cleanup AbortSignal event listeners to avoid MaxListenersEx…
Browse files Browse the repository at this point in the history
…ceededWarning
  • Loading branch information
joakimbeng committed Feb 12, 2024
1 parent ae9e8b1 commit 57a0991
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 16 deletions.
5 changes: 5 additions & 0 deletions .changeset/blue-candles-boil.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@emigrate/cli': patch
---

Cleanup AbortSignal listeners when they are not needed to avoid MaxListenersExceededWarning when migrating many migrations at once
51 changes: 35 additions & 16 deletions packages/cli/src/exec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ export const exec = async <Return extends Promise<any>>(
const aborter = options.abortSignal ? getAborter(options.abortSignal, options.abortRespite) : undefined;
const result = await Promise.race(aborter ? [aborter, fn()] : [fn()]);

aborter?.cancel();

return [result, undefined];
} catch (error) {
return [undefined, toError(error)];
Expand All @@ -40,27 +42,44 @@ export const exec = async <Return extends Promise<any>>(
* @param signal The abort signal to listen to
* @param respite The time in milliseconds to wait before rejecting
*/
const getAborter = async (signal: AbortSignal, respite = DEFAULT_RESPITE_SECONDS * 1000): Promise<never> => {
return new Promise((_, reject) => {
if (signal.aborted) {
setTimeout(
const getAborter = (
signal: AbortSignal,
respite = DEFAULT_RESPITE_SECONDS * 1000,
): PromiseLike<never> & { cancel: () => void } => {
const cleanups: Array<() => void> = [];

const aborter = new Promise<never>((_, reject) => {
const abortListener = () => {
const timer = setTimeout(
reject,
respite,
ExecutionDesertedError.fromReason(`Deserted after ${prettyMs(respite)}`, toError(signal.reason)),
).unref();
);
timer.unref();
cleanups.push(() => {
clearTimeout(timer);
});
};

if (signal.aborted) {
abortListener();
return;
}

signal.addEventListener(
'abort',
() => {
setTimeout(
reject,
respite,
ExecutionDesertedError.fromReason(`Deserted after ${prettyMs(respite)}`, toError(signal.reason)),
).unref();
},
{ once: true },
);
signal.addEventListener('abort', abortListener, { once: true });

cleanups.push(() => {
signal.removeEventListener('abort', abortListener);
});
});

const cancel = () => {
for (const cleanup of cleanups) {
cleanup();
}

cleanups.length = 0;
};

return Object.assign(aborter, { cancel });
};

0 comments on commit 57a0991

Please sign in to comment.