1// Note: since nyc uses this module to output coverage, any lines 2// that are in the direct sync flow of nyc's outputCoverage are 3// ignored, since we can never get coverage for them. 4var assert = require('assert') 5var signals = require('./signals.js') 6 7var EE = require('events') 8/* istanbul ignore if */ 9if (typeof EE !== 'function') { 10 EE = EE.EventEmitter 11} 12 13var emitter 14if (process.__signal_exit_emitter__) { 15 emitter = process.__signal_exit_emitter__ 16} else { 17 emitter = process.__signal_exit_emitter__ = new EE() 18 emitter.count = 0 19 emitter.emitted = {} 20} 21 22// Because this emitter is a global, we have to check to see if a 23// previous version of this library failed to enable infinite listeners. 24// I know what you're about to say. But literally everything about 25// signal-exit is a compromise with evil. Get used to it. 26if (!emitter.infinite) { 27 emitter.setMaxListeners(Infinity) 28 emitter.infinite = true 29} 30 31module.exports = function (cb, opts) { 32 assert.equal(typeof cb, 'function', 'a callback must be provided for exit handler') 33 34 if (loaded === false) { 35 load() 36 } 37 38 var ev = 'exit' 39 if (opts && opts.alwaysLast) { 40 ev = 'afterexit' 41 } 42 43 var remove = function () { 44 emitter.removeListener(ev, cb) 45 if (emitter.listeners('exit').length === 0 && 46 emitter.listeners('afterexit').length === 0) { 47 unload() 48 } 49 } 50 emitter.on(ev, cb) 51 52 return remove 53} 54 55module.exports.unload = unload 56function unload () { 57 if (!loaded) { 58 return 59 } 60 loaded = false 61 62 signals.forEach(function (sig) { 63 try { 64 process.removeListener(sig, sigListeners[sig]) 65 } catch (er) {} 66 }) 67 process.emit = originalProcessEmit 68 process.reallyExit = originalProcessReallyExit 69 emitter.count -= 1 70} 71 72function emit (event, code, signal) { 73 if (emitter.emitted[event]) { 74 return 75 } 76 emitter.emitted[event] = true 77 emitter.emit(event, code, signal) 78} 79 80// { <signal>: <listener fn>, ... } 81var sigListeners = {} 82signals.forEach(function (sig) { 83 sigListeners[sig] = function listener () { 84 // If there are no other listeners, an exit is coming! 85 // Simplest way: remove us and then re-send the signal. 86 // We know that this will kill the process, so we can 87 // safely emit now. 88 var listeners = process.listeners(sig) 89 if (listeners.length === emitter.count) { 90 unload() 91 emit('exit', null, sig) 92 /* istanbul ignore next */ 93 emit('afterexit', null, sig) 94 /* istanbul ignore next */ 95 process.kill(process.pid, sig) 96 } 97 } 98}) 99 100module.exports.signals = function () { 101 return signals 102} 103 104module.exports.load = load 105 106var loaded = false 107 108function load () { 109 if (loaded) { 110 return 111 } 112 loaded = true 113 114 // This is the number of onSignalExit's that are in play. 115 // It's important so that we can count the correct number of 116 // listeners on signals, and don't wait for the other one to 117 // handle it instead of us. 118 emitter.count += 1 119 120 signals = signals.filter(function (sig) { 121 try { 122 process.on(sig, sigListeners[sig]) 123 return true 124 } catch (er) { 125 return false 126 } 127 }) 128 129 process.emit = processEmit 130 process.reallyExit = processReallyExit 131} 132 133var originalProcessReallyExit = process.reallyExit 134function processReallyExit (code) { 135 process.exitCode = code || 0 136 emit('exit', process.exitCode, null) 137 /* istanbul ignore next */ 138 emit('afterexit', process.exitCode, null) 139 /* istanbul ignore next */ 140 originalProcessReallyExit.call(process, process.exitCode) 141} 142 143var originalProcessEmit = process.emit 144function processEmit (ev, arg) { 145 if (ev === 'exit') { 146 if (arg !== undefined) { 147 process.exitCode = arg 148 } 149 var ret = originalProcessEmit.apply(this, arguments) 150 emit('exit', process.exitCode, null) 151 /* istanbul ignore next */ 152 emit('afterexit', process.exitCode, null) 153 return ret 154 } else { 155 return originalProcessEmit.apply(this, arguments) 156 } 157} 158