Skip to content

Commit ef3d3d7

Browse files
committed
disposalfixe
1 parent 47401f4 commit ef3d3d7

File tree

3 files changed

+128
-1
lines changed

3 files changed

+128
-1
lines changed

src/cleanup.ts

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// It's this package but without default console log / error https://github.com/trevorr/async-cleanup
2+
3+
/** A possibly asynchronous function invoked with the process is about to exit. */
4+
export type CleanupListener = () => void | Promise<void>;
5+
6+
let cleanupListeners: Set<CleanupListener> | undefined;
7+
8+
/** Registers a new cleanup listener. Adding the same listener more than once has no effect. */
9+
export function addCleanupListener(listener: CleanupListener): void {
10+
// Install exit listeners on initial cleanup listener
11+
if (!cleanupListeners) {
12+
installExitListeners();
13+
cleanupListeners = new Set();
14+
}
15+
16+
cleanupListeners.add(listener);
17+
}
18+
19+
/** Removes an existing cleanup listener, and returns whether the listener was registered. */
20+
export function removeCleanupListener(listener: CleanupListener): boolean {
21+
return cleanupListeners != null && cleanupListeners.delete(listener);
22+
}
23+
24+
/** Executes all cleanup listeners and then exits the process. Call this instead of `process.exit` to ensure all listeners are fully executed. */
25+
export async function exitAfterCleanup(code = 0): Promise<never> {
26+
await executeCleanupListeners(code);
27+
process.exit(code);
28+
}
29+
30+
/** Executes all cleanup listeners and then kills the process with the given signal. */
31+
export async function killAfterCleanup(signal: ExitSignal): Promise<void> {
32+
await executeCleanupListeners();
33+
process.kill(process.pid, signal);
34+
}
35+
36+
async function executeCleanupListeners(): Promise<void> {
37+
if (cleanupListeners) {
38+
// Remove exit listeners to restore normal event handling
39+
uninstallExitListeners();
40+
41+
// Clear cleanup listeners to reset state for testing
42+
const listeners = cleanupListeners;
43+
cleanupListeners = undefined;
44+
45+
// Call listeners in order added with async listeners running concurrently
46+
const promises: Promise<void>[] = [];
47+
for (const listener of listeners) {
48+
try {
49+
const promise = listener();
50+
if (promise) promises.push(promise);
51+
} catch (err) {
52+
// console.error("Uncaught exception during cleanup", err);
53+
}
54+
}
55+
56+
// Wait for all listeners to complete and log any rejections
57+
const results = await Promise.allSettled(promises);
58+
for (const result of results) {
59+
if (result.status === "rejected") {
60+
console.error("Unhandled rejection during cleanup", result.reason);
61+
}
62+
}
63+
}
64+
}
65+
66+
function beforeExitListener(code: number): void {
67+
// console.log(`Exiting with code ${code} due to empty event loop`);
68+
void exitAfterCleanup(code);
69+
}
70+
71+
function uncaughtExceptionListener(error: Error): void {
72+
// console.error("Exiting with code 1 due to uncaught exception", error);
73+
void exitAfterCleanup(1);
74+
}
75+
76+
function signalListener(signal: ExitSignal): void {
77+
// console.log(`Exiting due to signal ${signal}`);
78+
void killAfterCleanup(signal);
79+
}
80+
81+
// Listenable signals that terminate the process by default
82+
// (except SIGQUIT, which generates a core dump and should not trigger cleanup)
83+
// See https://nodejs.org/api/process.html#signal-events
84+
const listenedSignals = [
85+
"SIGBREAK", // Ctrl-Break on Windows
86+
"SIGHUP", // Parent terminal closed
87+
"SIGINT", // Terminal interrupt, usually by Ctrl-C
88+
"SIGTERM", // Graceful termination
89+
"SIGUSR2", // Used by Nodemon
90+
] as const;
91+
92+
/** Signals that can terminate the process. */
93+
export type ExitSignal =
94+
| typeof listenedSignals[number]
95+
| "SIGKILL"
96+
| "SIGQUIT"
97+
| "SIGSTOP";
98+
99+
function installExitListeners(): void {
100+
process.on("beforeExit", beforeExitListener);
101+
process.on("uncaughtException", uncaughtExceptionListener);
102+
listenedSignals.forEach((signal) => process.on(signal, signalListener));
103+
}
104+
105+
function uninstallExitListeners(): void {
106+
process.removeListener("beforeExit", beforeExitListener);
107+
process.removeListener("uncaughtException", uncaughtExceptionListener);
108+
listenedSignals.forEach((signal) =>
109+
process.removeListener(signal, signalListener)
110+
);
111+
}

src/sern.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { presenceHandler } from './handlers/presence';
1414
import type { Payload, UnpackedDependencies, Wrapper } from './types/utility';
1515
import type { Presence} from './core/presences';
1616
import { registerTasks } from './handlers/tasks';
17+
import { addCleanupListener } from './cleanup';
1718

1819

1920
/**
@@ -76,5 +77,12 @@ export function init(maybeWrapper: Wrapper = { commands: "./dist/commands" }) {
7677
})
7778
.catch(err => { throw err });
7879
interactionHandler(deps, maybeWrapper.defaultPrefix);
79-
messageHandler(deps, maybeWrapper.defaultPrefix)
80+
messageHandler(deps, maybeWrapper.defaultPrefix);
81+
82+
addCleanupListener(async () => {
83+
const duration = ((performance.now() - startTime) / 1000).toFixed(2)
84+
deps['@sern/logger']?.info({ 'message': 'sern is shutting down after '+duration +" seconds" })
85+
await useContainerRaw().disposeAll();
86+
});
87+
8088
}

yarn.lock

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -588,6 +588,7 @@ __metadata:
588588
"@types/node-cron": ^3.0.11
589589
"@typescript-eslint/eslint-plugin": 5.58.0
590590
"@typescript-eslint/parser": 5.59.1
591+
async-cleanup: ^1.0.0
591592
callsites: ^3.1.0
592593
cron: ^3.1.7
593594
deepmerge: ^4.3.1
@@ -1012,6 +1013,13 @@ __metadata:
10121013
languageName: node
10131014
linkType: hard
10141015

1016+
"async-cleanup@npm:^1.0.0":
1017+
version: 1.0.0
1018+
resolution: "async-cleanup@npm:1.0.0"
1019+
checksum: d2dea124db5f546716f9c5237714d78978de39e78f0bf9d9e884497249543e073203b1949c78262b2485e9ed827dc9a26d2392954be6e4025e9f06552c5164dc
1020+
languageName: node
1021+
linkType: hard
1022+
10151023
"balanced-match@npm:^1.0.0":
10161024
version: 1.0.2
10171025
resolution: "balanced-match@npm:1.0.2"

0 commit comments

Comments
 (0)