main
ts 60 lines 2.21 KB
Raw
1 // should not import other sources that themselves import this file, to avoid circular dependencies
2 import { EventEmitter } from 'events'
3 import _ from 'lodash'
4 import assert from 'assert'
5
6 type ProcessExitHandler = (signal: string) => any
7 const cbsOnExit = new Set<{ cb: ProcessExitHandler, order: number }>()
8 export function onProcessExit(cb: ProcessExitHandler, order=10) {
9 assert(Number.isInteger(order) && order >= 0, 'order must be an integer >= 0')
10 const rec = { cb, order }
11 cbsOnExit.add(rec)
12 return () => cbsOnExit.delete(rec)
13 }
14
15 export let quitting = false
16 export let exitCode = 0
17 // 'exit' event is handled as the last resort, but it's not compatible with async callbacks
18 onFirstEvent(process, ['exit', 'SIGQUIT', 'SIGTERM', 'SIGINT', 'SIGHUP', 'beforeExit'], async signal => {
19 console.log('Quitting with signal:', signal || 'unknown')
20 quitting = true
21 const byOrder = _.groupBy(Array.from(cbsOnExit), 'order') // this will be inherently ordered because keys are positive integers
22 for (const recs of Object.values(byOrder)) {
23 const ret = Promise.allSettled(recs.map(({ cb }) => {
24 try { return cb(signal) }
25 // keep exit moving even when a synchronous cleanup fails after partially shutting down
26 catch (e) {
27 console.error("Error while quitting", e)
28 return Promise.reject(e)
29 }
30 }))
31 if (signal !== 'exit') // exit is sync
32 await ret
33 }
34 cbsOnExit.clear()
35 console.debug('Process exit')
36 process.exit(exitCode)
37 })
38
39 export function quit(code=0) {
40 exitCode = code
41 process.emit('SIGINT')
42 }
43
44 // keep calling cb in a sync fashion – returning a promise instead would break the code for argv.updating (update.ts)
45 export function onFirstEvent(emitter:EventEmitter, events: string[], cb: (...args:any[]) => void) {
46 let already = false
47 const cleanup = () => {
48 events.forEach((e, i) => emitter.off(e, handlers[i]!))
49 }
50 const handlers = events.map(e => {
51 const handler = (...args: any[]) => {
52 if (already) return
53 already = true
54 cleanup()
55 cb(e, ...args)
56 }
57 emitter.on(e, handler)
58 return handler
59 })
60 }