fix(cli): cleanup AbortSignal event listeners to avoid MaxListenersExceededWarning
This commit is contained in:
parent
ae9e8b1b04
commit
57a099169e
2 changed files with 40 additions and 16 deletions
5
.changeset/blue-candles-boil.md
Normal file
5
.changeset/blue-candles-boil.md
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'@emigrate/cli': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Cleanup AbortSignal listeners when they are not needed to avoid MaxListenersExceededWarning when migrating many migrations at once
|
||||||
|
|
@ -28,6 +28,8 @@ export const exec = async <Return extends Promise<any>>(
|
||||||
const aborter = options.abortSignal ? getAborter(options.abortSignal, options.abortRespite) : undefined;
|
const aborter = options.abortSignal ? getAborter(options.abortSignal, options.abortRespite) : undefined;
|
||||||
const result = await Promise.race(aborter ? [aborter, fn()] : [fn()]);
|
const result = await Promise.race(aborter ? [aborter, fn()] : [fn()]);
|
||||||
|
|
||||||
|
aborter?.cancel();
|
||||||
|
|
||||||
return [result, undefined];
|
return [result, undefined];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return [undefined, toError(error)];
|
return [undefined, toError(error)];
|
||||||
|
|
@ -40,27 +42,44 @@ export const exec = async <Return extends Promise<any>>(
|
||||||
* @param signal The abort signal to listen to
|
* @param signal The abort signal to listen to
|
||||||
* @param respite The time in milliseconds to wait before rejecting
|
* @param respite The time in milliseconds to wait before rejecting
|
||||||
*/
|
*/
|
||||||
const getAborter = async (signal: AbortSignal, respite = DEFAULT_RESPITE_SECONDS * 1000): Promise<never> => {
|
const getAborter = (
|
||||||
return new Promise((_, reject) => {
|
signal: AbortSignal,
|
||||||
if (signal.aborted) {
|
respite = DEFAULT_RESPITE_SECONDS * 1000,
|
||||||
setTimeout(
|
): PromiseLike<never> & { cancel: () => void } => {
|
||||||
|
const cleanups: Array<() => void> = [];
|
||||||
|
|
||||||
|
const aborter = new Promise<never>((_, reject) => {
|
||||||
|
const abortListener = () => {
|
||||||
|
const timer = setTimeout(
|
||||||
reject,
|
reject,
|
||||||
respite,
|
respite,
|
||||||
ExecutionDesertedError.fromReason(`Deserted after ${prettyMs(respite)}`, toError(signal.reason)),
|
ExecutionDesertedError.fromReason(`Deserted after ${prettyMs(respite)}`, toError(signal.reason)),
|
||||||
).unref();
|
);
|
||||||
|
timer.unref();
|
||||||
|
cleanups.push(() => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (signal.aborted) {
|
||||||
|
abortListener();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
signal.addEventListener(
|
signal.addEventListener('abort', abortListener, { once: true });
|
||||||
'abort',
|
|
||||||
() => {
|
cleanups.push(() => {
|
||||||
setTimeout(
|
signal.removeEventListener('abort', abortListener);
|
||||||
reject,
|
});
|
||||||
respite,
|
|
||||||
ExecutionDesertedError.fromReason(`Deserted after ${prettyMs(respite)}`, toError(signal.reason)),
|
|
||||||
).unref();
|
|
||||||
},
|
|
||||||
{ once: true },
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const cancel = () => {
|
||||||
|
for (const cleanup of cleanups) {
|
||||||
|
cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanups.length = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
return Object.assign(aborter, { cancel });
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue