Skip to content

Commit

Permalink
feat: handle onDeath
Browse files Browse the repository at this point in the history
  • Loading branch information
yjl9903 committed May 11, 2023
1 parent f4ddf12 commit 422db6f
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 1 deletion.
4 changes: 4 additions & 0 deletions packages/death/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ onDeath(() => {
})
```

## Note

In [Signal events](https://nodejs.org/dist/latest-v20.x/docs/api/process.html#signal-events), it says that `SIGTERM` and `SIGINT` have default handlers on non-Windows platforms that reset the terminal mode before exiting with code 128 + signal number. **If one of these signals has a listener installed, its default behavior will be removed (Node.js will no longer exit).**

## License

MIT License 漏 2023 [XLor](https://github.com/yjl9903)
16 changes: 16 additions & 0 deletions packages/death/scripts/play.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { onDeath } from '../src';

function sleep(timeout: number): Promise<void> {
return new Promise((res) => {
setTimeout(() => res(), timeout);
});
}

onDeath((sig) => {
console.log(`Receive: ${sig}`);
console.log('Process is being killed');
});

console.log('Start sleep');

await sleep(1000 * 1000);
99 changes: 99 additions & 0 deletions packages/death/src/death.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { EventEmitter } from 'node:events';

export type DeathSignals = 'SIGINT' | 'SIGTERM' | 'SIGQUIT';

export interface OnDeathContext {
// Terminate process by which method or does nothing when callbacks are done
terminate: 'exit' | 'kill' | false;

// Call process.exit when callbacks are done
exit: number | undefined;

// Call process.kill when callbacks are done
kill: NodeJS.Signals | undefined;
}

export type OnDeathCallback = (
signal: DeathSignals,
context: OnDeathContext
) => unknown | Promise<unknown>;

export interface OnDeathOptions {
SIGINT?: boolean;
SIGTERM?: boolean;
SIGQUIT?: boolean;
}

const emitter = new EventEmitter();

const handlers: Record<DeathSignals, NodeJS.SignalsListener> = {
SIGINT: makeHandler('SIGINT'),
SIGTERM: makeHandler('SIGTERM'),
SIGQUIT: makeHandler('SIGQUIT')
};

export function onDeath(
callback: OnDeathCallback,
{ SIGINT = true, SIGTERM = true, SIGQUIT = true }: OnDeathOptions = {}
): () => void {
const cleanUps: Array<() => void> = [];

if (SIGINT) {
registerCallback('SIGINT', handlers.SIGINT);
emitter.addListener('SIGINT', callback);
cleanUps.push(() => emitter.removeListener('SIGINT', callback));
}
if (SIGTERM) {
registerCallback('SIGTERM', handlers.SIGTERM);
emitter.addListener('SIGTERM', callback);
cleanUps.push(() => emitter.removeListener('SIGTERM', callback));
}
if (SIGQUIT) {
registerCallback('SIGQUIT', handlers.SIGQUIT);
emitter.addListener('SIGQUIT', callback);
cleanUps.push(() => emitter.removeListener('SIGQUIT', callback));
}

return () => {
for (const cleanUp of cleanUps) {
cleanUp();
}
};
}

function registerCallback(
signal: DeathSignals,
callback: NodeJS.SignalsListener
) {
process.on(signal, callback);
return () => {
process.off(signal, callback);
};
}

function makeHandler(signal: DeathSignals) {
return async (signal: NodeJS.Signals) => {
const listeners = emitter.listeners(signal);
const context: OnDeathContext = {
terminate: 'kill',
exit: undefined,
kill: signal
};

// Iterate all the listener by reverse
for (const listener of listeners.reverse()) {
await listener(signal, context);
}

if (context.terminate === 'kill' || context.terminate === 'exit') {
process.removeListener('SIGINT', handlers.SIGINT);
process.removeListener('SIGTERM', handlers.SIGTERM);
process.removeListener('SIGQUIT', handlers.SIGQUIT);
if (context.terminate === 'kill') {
process.kill(process.pid, context.kill);
} else {
process.exit(context.exit);
}
}
};
}
2 changes: 1 addition & 1 deletion packages/death/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export function onDeath() {}
export * from './death';

0 comments on commit 422db6f

Please sign in to comment.