1import { guardAsync } from './fn'; 2 3const debug = require('debug')('expo:utils:exit') as typeof console.log; 4 5type AsyncExitHook = (signal: NodeJS.Signals) => void | Promise<void>; 6 7const PRE_EXIT_SIGNALS: NodeJS.Signals[] = ['SIGHUP', 'SIGINT', 'SIGTERM', 'SIGBREAK']; 8 9// We create a queue since Node.js throws an error if we try to append too many listeners: 10// (node:4405) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 SIGINT listeners added to [process]. Use emitter.setMaxListeners() to increase limit 11const queue: AsyncExitHook[] = []; 12 13let unsubscribe: (() => void) | null = null; 14 15/** Add functions that run before the process exits. Returns a function for removing the listeners. */ 16export function installExitHooks(asyncExitHook: AsyncExitHook): () => void { 17 // We need to instantiate the master listener the first time the queue is used. 18 if (!queue.length) { 19 // Track the master listener so we can remove it later. 20 unsubscribe = attachMasterListener(); 21 } 22 23 queue.push(asyncExitHook); 24 25 return () => { 26 const index = queue.indexOf(asyncExitHook); 27 if (index >= 0) { 28 queue.splice(index, 1); 29 } 30 // Clean up the master listener if we don't need it anymore. 31 if (!queue.length) { 32 unsubscribe?.(); 33 } 34 }; 35} 36 37// Create a function that runs before the process exits and guards against running multiple times. 38function createExitHook(signal: NodeJS.Signals) { 39 return guardAsync(async () => { 40 debug(`pre-exit (signal: ${signal}, queue length: ${queue.length})`); 41 42 for (const [index, hookAsync] of Object.entries(queue)) { 43 try { 44 await hookAsync(signal); 45 } catch (error: any) { 46 debug(`Error in exit hook: %O (queue: ${index})`, error); 47 } 48 } 49 50 debug(`post-exit (code: ${process.exitCode ?? 0})`); 51 52 process.exit(); 53 }); 54} 55 56function attachMasterListener() { 57 const hooks: [NodeJS.Signals, () => any][] = []; 58 for (const signal of PRE_EXIT_SIGNALS) { 59 const hook = createExitHook(signal); 60 hooks.push([signal, hook]); 61 process.on(signal, hook); 62 } 63 return () => { 64 for (const [signal, hook] of hooks) { 65 process.removeListener(signal, hook); 66 } 67 }; 68} 69