UNPKG

3.39 kBJavaScriptView Raw
1'use strict';
2
3var hooks = [];
4var errHooks = [];
5var called = false;
6var waitingFor = 0;
7var asyncTimeoutMs = 10000;
8
9var events = [];
10var filters = [];
11
12function exit(exit, code, err) {
13 // Only execute hooks once
14 if (called) {
15 return;
16 }
17
18 called = true;
19
20 // Run hooks
21 if (err) {
22 // Uncaught exception, run error hooks
23 errHooks.map(runHook.bind(null, 1, err));
24 }
25 hooks.map(runHook.bind(null, 0, null));
26
27 if (!waitingFor) {
28 // No asynchronous hooks, exit immediately
29 doExit();
30 } else {
31 // Force exit after x ms (10000 by default), even if async hooks in progress
32 setTimeout(function() {
33 doExit();
34 }, asyncTimeoutMs);
35 }
36
37 // Runs a single hook
38 function runHook(syncArgCount, err, hook) {
39 // cannot perform async hooks in `exit` event
40 if (exit && hook.length > syncArgCount) {
41 // hook is async, expects a finish callback
42 waitingFor++;
43
44 if (err) {
45 // Pass error, calling uncaught exception handlers
46 return hook(err, stepTowardExit);
47 }
48 return hook(stepTowardExit);
49 }
50
51 // hook is synchronous
52 if (err) {
53 // Pass error, calling uncaught exception handlers
54 return hook(err);
55 }
56 return hook();
57 }
58
59 // Async hook callback, decrements waiting counter
60 function stepTowardExit() {
61 process.nextTick(function() {
62 if (--waitingFor === 0) {
63 doExit();
64 }
65 });
66 }
67
68 var doExitDone = false;
69 function doExit() {
70 if (doExitDone) {
71 return;
72 }
73 doExitDone = true;
74
75 if (exit === true) {
76 // All handlers should be called even if the exit-hook handler was registered first
77 process.nextTick(process.exit.bind(null, code));
78 }
79 }
80}
81
82// Add a hook
83function add(hook) {
84 hooks.push(hook);
85
86 if (hooks.length === 1) {
87 add.hookEvent('exit');
88 add.hookEvent('beforeExit', 0);
89 add.hookEvent('SIGHUP', 128 + 1);
90 add.hookEvent('SIGINT', 128 + 2);
91 add.hookEvent('SIGTERM', 128 + 15);
92 add.hookEvent('SIGBREAK', 128 + 21);
93
94 // PM2 Cluster shutdown message. Caught to support async handlers with pm2, needed because
95 // explicitly calling process.exit() doesn't trigger the beforeExit event, and the exit
96 // event cannot support async handlers, since the event loop is never called after it.
97 add.hookEvent('message', 0, function(msg) {
98 if (msg !== 'shutdown') {
99 return true;
100 }
101 });
102 }
103}
104
105// New signal / event to hook
106add.hookEvent = function(event, code, filter) {
107 events[event] = function() {
108 for (var i = 0; i < filters.length; i++) {
109 if (filters[i].apply(this, arguments)) {
110 return;
111 }
112 }
113 exit(code != null, code);
114 };
115
116 if (!filters[event]) {
117 filters[event] = [];
118 }
119
120 if (filter) {
121 filters[event].push(filter);
122 }
123 process.on(event, events[event]);
124};
125
126// Unhook signal / event
127add.unhookEvent = function(event) {
128 console.log('unhookevent: ' + event);
129 process.removeListener(event, events[event]);
130 delete events[event];
131 delete filters[event];
132};
133
134// List hooked events
135add.hookedEvents = function() {
136 var ret = [];
137 for (var name in events) {
138 if (events.hasOwnProperty(name)) {
139 ret.push(name);
140 }
141 }
142 return ret;
143};
144
145// Add an uncaught exception handler
146add.uncaughtExceptionHandler = function(hook) {
147 errHooks.push(hook);
148
149 if (errHooks.length === 1) {
150 process.once('uncaughtException', exit.bind(null, true, 1));
151 }
152};
153
154// Configure async force exit timeout
155add.forceExitTimeout = function(ms) {
156 asyncTimeoutMs = ms;
157};
158
159// Export
160module.exports = add;