index.js 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  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. // grab a reference to node's real process object right away
  5. import { signals } from './signals.js';
  6. export { signals };
  7. const processOk = (process) => !!process &&
  8. typeof process === 'object' &&
  9. typeof process.removeListener === 'function' &&
  10. typeof process.emit === 'function' &&
  11. typeof process.reallyExit === 'function' &&
  12. typeof process.listeners === 'function' &&
  13. typeof process.kill === 'function' &&
  14. typeof process.pid === 'number' &&
  15. typeof process.on === 'function';
  16. const kExitEmitter = Symbol.for('signal-exit emitter');
  17. const global = globalThis;
  18. const ObjectDefineProperty = Object.defineProperty.bind(Object);
  19. // teeny special purpose ee
  20. class Emitter {
  21. emitted = {
  22. afterExit: false,
  23. exit: false,
  24. };
  25. listeners = {
  26. afterExit: [],
  27. exit: [],
  28. };
  29. count = 0;
  30. id = Math.random();
  31. constructor() {
  32. if (global[kExitEmitter]) {
  33. return global[kExitEmitter];
  34. }
  35. ObjectDefineProperty(global, kExitEmitter, {
  36. value: this,
  37. writable: false,
  38. enumerable: false,
  39. configurable: false,
  40. });
  41. }
  42. on(ev, fn) {
  43. this.listeners[ev].push(fn);
  44. }
  45. removeListener(ev, fn) {
  46. const list = this.listeners[ev];
  47. const i = list.indexOf(fn);
  48. /* c8 ignore start */
  49. if (i === -1) {
  50. return;
  51. }
  52. /* c8 ignore stop */
  53. if (i === 0 && list.length === 1) {
  54. list.length = 0;
  55. }
  56. else {
  57. list.splice(i, 1);
  58. }
  59. }
  60. emit(ev, code, signal) {
  61. if (this.emitted[ev]) {
  62. return false;
  63. }
  64. this.emitted[ev] = true;
  65. let ret = false;
  66. for (const fn of this.listeners[ev]) {
  67. ret = fn(code, signal) === true || ret;
  68. }
  69. if (ev === 'exit') {
  70. ret = this.emit('afterExit', code, signal) || ret;
  71. }
  72. return ret;
  73. }
  74. }
  75. class SignalExitBase {
  76. }
  77. const signalExitWrap = (handler) => {
  78. return {
  79. onExit(cb, opts) {
  80. return handler.onExit(cb, opts);
  81. },
  82. load() {
  83. return handler.load();
  84. },
  85. unload() {
  86. return handler.unload();
  87. },
  88. };
  89. };
  90. class SignalExitFallback extends SignalExitBase {
  91. onExit() {
  92. return () => { };
  93. }
  94. load() { }
  95. unload() { }
  96. }
  97. class SignalExit extends SignalExitBase {
  98. // "SIGHUP" throws an `ENOSYS` error on Windows,
  99. // so use a supported signal instead
  100. /* c8 ignore start */
  101. #hupSig = process.platform === 'win32' ? 'SIGINT' : 'SIGHUP';
  102. /* c8 ignore stop */
  103. #emitter = new Emitter();
  104. #process;
  105. #originalProcessEmit;
  106. #originalProcessReallyExit;
  107. #sigListeners = {};
  108. #loaded = false;
  109. constructor(process) {
  110. super();
  111. this.#process = process;
  112. // { <signal>: <listener fn>, ... }
  113. this.#sigListeners = {};
  114. for (const sig of signals) {
  115. this.#sigListeners[sig] = () => {
  116. // If there are no other listeners, an exit is coming!
  117. // Simplest way: remove us and then re-send the signal.
  118. // We know that this will kill the process, so we can
  119. // safely emit now.
  120. const listeners = this.#process.listeners(sig);
  121. let { count } = this.#emitter;
  122. // This is a workaround for the fact that signal-exit v3 and signal
  123. // exit v4 are not aware of each other, and each will attempt to let
  124. // the other handle it, so neither of them do. To correct this, we
  125. // detect if we're the only handler *except* for previous versions
  126. // of signal-exit, and increment by the count of listeners it has
  127. // created.
  128. /* c8 ignore start */
  129. const p = process;
  130. if (typeof p.__signal_exit_emitter__ === 'object' &&
  131. typeof p.__signal_exit_emitter__.count === 'number') {
  132. count += p.__signal_exit_emitter__.count;
  133. }
  134. /* c8 ignore stop */
  135. if (listeners.length === count) {
  136. this.unload();
  137. const ret = this.#emitter.emit('exit', null, sig);
  138. /* c8 ignore start */
  139. const s = sig === 'SIGHUP' ? this.#hupSig : sig;
  140. if (!ret)
  141. process.kill(process.pid, s);
  142. /* c8 ignore stop */
  143. }
  144. };
  145. }
  146. this.#originalProcessReallyExit = process.reallyExit;
  147. this.#originalProcessEmit = process.emit;
  148. }
  149. onExit(cb, opts) {
  150. /* c8 ignore start */
  151. if (!processOk(this.#process)) {
  152. return () => { };
  153. }
  154. /* c8 ignore stop */
  155. if (this.#loaded === false) {
  156. this.load();
  157. }
  158. const ev = opts?.alwaysLast ? 'afterExit' : 'exit';
  159. this.#emitter.on(ev, cb);
  160. return () => {
  161. this.#emitter.removeListener(ev, cb);
  162. if (this.#emitter.listeners['exit'].length === 0 &&
  163. this.#emitter.listeners['afterExit'].length === 0) {
  164. this.unload();
  165. }
  166. };
  167. }
  168. load() {
  169. if (this.#loaded) {
  170. return;
  171. }
  172. this.#loaded = true;
  173. // This is the number of onSignalExit's that are in play.
  174. // It's important so that we can count the correct number of
  175. // listeners on signals, and don't wait for the other one to
  176. // handle it instead of us.
  177. this.#emitter.count += 1;
  178. for (const sig of signals) {
  179. try {
  180. const fn = this.#sigListeners[sig];
  181. if (fn)
  182. this.#process.on(sig, fn);
  183. }
  184. catch (_) { }
  185. }
  186. this.#process.emit = (ev, ...a) => {
  187. return this.#processEmit(ev, ...a);
  188. };
  189. this.#process.reallyExit = (code) => {
  190. return this.#processReallyExit(code);
  191. };
  192. }
  193. unload() {
  194. if (!this.#loaded) {
  195. return;
  196. }
  197. this.#loaded = false;
  198. signals.forEach(sig => {
  199. const listener = this.#sigListeners[sig];
  200. /* c8 ignore start */
  201. if (!listener) {
  202. throw new Error('Listener not defined for signal: ' + sig);
  203. }
  204. /* c8 ignore stop */
  205. try {
  206. this.#process.removeListener(sig, listener);
  207. /* c8 ignore start */
  208. }
  209. catch (_) { }
  210. /* c8 ignore stop */
  211. });
  212. this.#process.emit = this.#originalProcessEmit;
  213. this.#process.reallyExit = this.#originalProcessReallyExit;
  214. this.#emitter.count -= 1;
  215. }
  216. #processReallyExit(code) {
  217. /* c8 ignore start */
  218. if (!processOk(this.#process)) {
  219. return 0;
  220. }
  221. this.#process.exitCode = code || 0;
  222. /* c8 ignore stop */
  223. this.#emitter.emit('exit', this.#process.exitCode, null);
  224. return this.#originalProcessReallyExit.call(this.#process, this.#process.exitCode);
  225. }
  226. #processEmit(ev, ...args) {
  227. const og = this.#originalProcessEmit;
  228. if (ev === 'exit' && processOk(this.#process)) {
  229. if (typeof args[0] === 'number') {
  230. this.#process.exitCode = args[0];
  231. /* c8 ignore start */
  232. }
  233. /* c8 ignore start */
  234. const ret = og.call(this.#process, ev, ...args);
  235. /* c8 ignore start */
  236. this.#emitter.emit('exit', this.#process.exitCode, null);
  237. /* c8 ignore stop */
  238. return ret;
  239. }
  240. else {
  241. return og.call(this.#process, ev, ...args);
  242. }
  243. }
  244. }
  245. const process = globalThis.process;
  246. // wrap so that we call the method on the actual handler, without
  247. // exporting it directly.
  248. export const {
  249. /**
  250. * Called when the process is exiting, whether via signal, explicit
  251. * exit, or running out of stuff to do.
  252. *
  253. * If the global process object is not suitable for instrumentation,
  254. * then this will be a no-op.
  255. *
  256. * Returns a function that may be used to unload signal-exit.
  257. */
  258. onExit,
  259. /**
  260. * Load the listeners. Likely you never need to call this, unless
  261. * doing a rather deep integration with signal-exit functionality.
  262. * Mostly exposed for the benefit of testing.
  263. *
  264. * @internal
  265. */
  266. load,
  267. /**
  268. * Unload the listeners. Likely you never need to call this, unless
  269. * doing a rather deep integration with signal-exit functionality.
  270. * Mostly exposed for the benefit of testing.
  271. *
  272. * @internal
  273. */
  274. unload, } = signalExitWrap(processOk(process) ? new SignalExit(process) : new SignalExitFallback());
  275. //# sourceMappingURL=index.js.map