• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1const signals = require('./signals.js')
2
3// for testing, expose the process being used
4module.exports = Object.assign(fn => setup(fn), { process })
5
6// do all of this in a setup function so that we can call it
7// multiple times for multiple reifies that might be going on.
8// Otherwise, Arborist.reify() is a global action, which is a
9// new constraint we'd be adding with this behavior.
10const setup = fn => {
11  const { process } = module.exports
12
13  const sigListeners = { loaded: false }
14
15  const unload = () => {
16    if (!sigListeners.loaded) {
17      return
18    }
19    for (const sig of signals) {
20      try {
21        process.removeListener(sig, sigListeners[sig])
22      } catch {
23        // ignore errors
24      }
25    }
26    process.removeListener('beforeExit', onBeforeExit)
27    sigListeners.loaded = false
28  }
29
30  const onBeforeExit = () => {
31    // this trick ensures that we exit with the same signal we caught
32    // Ie, if you press ^C and npm gets a SIGINT, we'll do the rollback
33    // and then exit with a SIGINT signal once we've removed the handler.
34    // The timeout is there because signals are asynchronous, so we need
35    // the process to NOT exit on its own, which means we have to have
36    // something keeping the event loop looping.  Hence this hack.
37    unload()
38    process.kill(process.pid, signalReceived)
39    setTimeout(() => {}, 500)
40  }
41
42  let signalReceived = null
43  const listener = (sig, fn) => () => {
44    signalReceived = sig
45
46    // if we exit normally, but caught a signal which would have been fatal,
47    // then re-send it once we're done with whatever cleanup we have to do.
48    unload()
49    if (process.listeners(sig).length < 1) {
50      process.once('beforeExit', onBeforeExit)
51    }
52
53    fn({ signal: sig })
54  }
55
56  // do the actual loading here
57  for (const sig of signals) {
58    sigListeners[sig] = listener(sig, fn)
59    const max = process.getMaxListeners()
60    try {
61      // if we call this a bunch of times, avoid triggering the warning
62      const { length } = process.listeners(sig)
63      if (length >= max) {
64        process.setMaxListeners(length + 1)
65      }
66      process.on(sig, sigListeners[sig])
67    } catch {
68      // ignore errors
69    }
70  }
71  sigListeners.loaded = true
72
73  return unload
74}
75