index.js 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  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.
  4. var assert = require('assert')
  5. var signals = require('./signals.js')
  6. var EE = require('events')
  7. /* istanbul ignore if */
  8. if (typeof EE !== 'function') {
  9. EE = EE.EventEmitter
  10. }
  11. var emitter
  12. if (process.__signal_exit_emitter__) {
  13. emitter = process.__signal_exit_emitter__
  14. } else {
  15. emitter = process.__signal_exit_emitter__ = new EE()
  16. emitter.count = 0
  17. emitter.emitted = {}
  18. }
  19. module.exports = function (cb, opts) {
  20. assert.equal(typeof cb, 'function', 'a callback must be provided for exit handler')
  21. if (loaded === false) {
  22. load()
  23. }
  24. var ev = 'exit'
  25. if (opts && opts.alwaysLast) {
  26. ev = 'afterexit'
  27. }
  28. var remove = function () {
  29. emitter.removeListener(ev, cb)
  30. if (emitter.listeners('exit').length === 0 &&
  31. emitter.listeners('afterexit').length === 0) {
  32. unload()
  33. }
  34. }
  35. emitter.on(ev, cb)
  36. return remove
  37. }
  38. module.exports.unload = unload
  39. function unload () {
  40. if (!loaded) {
  41. return
  42. }
  43. loaded = false
  44. signals.forEach(function (sig) {
  45. try {
  46. process.removeListener(sig, sigListeners[sig])
  47. } catch (er) {}
  48. })
  49. process.emit = originalProcessEmit
  50. process.reallyExit = originalProcessReallyExit
  51. emitter.count -= 1
  52. }
  53. function emit (event, code, signal) {
  54. if (emitter.emitted[event]) {
  55. return
  56. }
  57. emitter.emitted[event] = true
  58. emitter.emit(event, code, signal)
  59. }
  60. // { <signal>: <listener fn>, ... }
  61. var sigListeners = {}
  62. signals.forEach(function (sig) {
  63. sigListeners[sig] = function listener () {
  64. // If there are no other listeners, an exit is coming!
  65. // Simplest way: remove us and then re-send the signal.
  66. // We know that this will kill the process, so we can
  67. // safely emit now.
  68. var listeners = process.listeners(sig)
  69. if (listeners.length === emitter.count) {
  70. unload()
  71. emit('exit', null, sig)
  72. /* istanbul ignore next */
  73. emit('afterexit', null, sig)
  74. /* istanbul ignore next */
  75. process.kill(process.pid, sig)
  76. }
  77. }
  78. })
  79. module.exports.signals = function () {
  80. return signals
  81. }
  82. module.exports.load = load
  83. var loaded = false
  84. function load () {
  85. if (loaded) {
  86. return
  87. }
  88. loaded = true
  89. // This is the number of onSignalExit's that are in play.
  90. // It's important so that we can count the correct number of
  91. // listeners on signals, and don't wait for the other one to
  92. // handle it instead of us.
  93. emitter.count += 1
  94. signals = signals.filter(function (sig) {
  95. try {
  96. process.on(sig, sigListeners[sig])
  97. return true
  98. } catch (er) {
  99. return false
  100. }
  101. })
  102. process.emit = processEmit
  103. process.reallyExit = processReallyExit
  104. }
  105. var originalProcessReallyExit = process.reallyExit
  106. function processReallyExit (code) {
  107. process.exitCode = code || 0
  108. emit('exit', process.exitCode, null)
  109. /* istanbul ignore next */
  110. emit('afterexit', process.exitCode, null)
  111. /* istanbul ignore next */
  112. originalProcessReallyExit.call(process, process.exitCode)
  113. }
  114. var originalProcessEmit = process.emit
  115. function processEmit (ev, arg) {
  116. if (ev === 'exit') {
  117. if (arg !== undefined) {
  118. process.exitCode = arg
  119. }
  120. var ret = originalProcessEmit.apply(this, arguments)
  121. emit('exit', process.exitCode, null)
  122. /* istanbul ignore next */
  123. emit('afterexit', process.exitCode, null)
  124. return ret
  125. } else {
  126. return originalProcessEmit.apply(this, arguments)
  127. }
  128. }