├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── app.js ├── lib └── death.js ├── package.json └── test ├── death.test.js ├── mocha.opts └── resources ├── default ├── disable ├── sighup ├── uncaughtException-false └── uncaughtException-true /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 1.1.0 / 2017-01-18 2 | ------------------ 3 | - ability to remove listeners 4 | 5 | 1.0.0 / 2015-03-17 6 | ------------------ 7 | - removed `homepage` field in `package.json` 8 | - small formatting changes 9 | 10 | 0.1.0 / 2013-02-18 11 | ------------------ 12 | * Fixed bugs due to setting of `uncaughtException`. Closes #1 13 | * Changed default of `uncaughtException` from `true` to `false`. 14 | * Removed aliases for `uncaughtException`. 15 | * Fixed bug that when a key is set to false, it's still caught. 16 | * Passed signal to callback. 17 | 18 | 19 | 0.0.1 / 2012-12-01 20 | ------------------ 21 | * Initial release. 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2012, JP Richardson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files 6 | (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, 7 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | 12 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 13 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 14 | OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 15 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Node.js - death 2 | ================ 3 | 4 | Gracefully cleanup when termination signals are sent to your process. 5 | 6 | 7 | Why? 8 | ---- 9 | 10 | Because adding clean up callbacks for `uncaughtException`, `SIGINT`, and `SIGTERM` is annoying. Ideally, you can 11 | use this package to put your cleanup code in one place and exit gracefully if you need to. 12 | 13 | 14 | Operating System Compatibility 15 | ------------------------------ 16 | 17 | It's only been tested on POSIX compatible systems. [Here's a nice discussion](https://github.com/joyent/node/issues/1553) on Windows signals, apparently, this has been fixed/mapped. 18 | 19 | 20 | Installation 21 | ------------ 22 | 23 | npm install death 24 | 25 | 26 | 27 | Example 28 | ------ 29 | 30 | ```js 31 | var ON_DEATH = require('death'); //this is intentionally ugly 32 | 33 | ON_DEATH(function(signal, err) { 34 | //clean up code here 35 | }) 36 | ``` 37 | 38 | 39 | Usage 40 | ----- 41 | 42 | By default, it sets the callback on `SIGINT`, `SIGQUIT`, and `SIGTERM`. 43 | 44 | ### Signals 45 | - **SIGINT**: Sent from CTRL-C 46 | - **SIGQUIT**: Sent from keyboard quit action. 47 | - **SIGTERM**: Sent from operating system `kill`. 48 | 49 | More discussion and detail: http://www.gnu.org/software/libc/manual/html_node/Termination-Signals.html and http://pubs.opengroup.org/onlinepubs/009695399/basedefs/signal.h.html and http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap11.html. 50 | 51 | AS they pertain to Node.js: http://dailyjs.com/2012/03/15/unix-node-signals/ 52 | 53 | 54 | #### Want to catch uncaughtException? 55 | 56 | No problem, do this: 57 | 58 | ```js 59 | var ON_DEATH = require('death')({uncaughtException: true}) 60 | ``` 61 | 62 | #### Want to know which signals are being caught? 63 | 64 | Do this: 65 | 66 | ```js 67 | var ON_DEATH = require('death')({debug: true}) 68 | ``` 69 | 70 | Your process will then log anytime it catches these signals. 71 | 72 | #### Want to catch SIGHUP? 73 | 74 | Be careful with this one though. Typically this is fired if your SSH connection dies, but can 75 | also be fired if the program is made a daemon. 76 | 77 | Do this: 78 | 79 | ```js 80 | var ON_DEATH = require('death')({SIGHUP: true}) 81 | ``` 82 | 83 | #### Why choose the ugly "ON_DEATH"? 84 | 85 | Name it whatever you want. I like `ON_DEATH` because it stands out like a sore thumb in my code. 86 | 87 | 88 | #### Want to remove event handlers? 89 | 90 | If you want to remove event handlers `ON_DEATH` returns a function for cleaning 91 | up after itself: 92 | 93 | ```js 94 | var ON_DEATH = require('death') 95 | var OFF_DEATH = ON_DEATH(function(signal, err) { 96 | //clean up code here 97 | }) 98 | 99 | // later on... 100 | OFF_DEATH(); 101 | ``` 102 | 103 | License 104 | ------- 105 | 106 | (MIT License) 107 | 108 | Copyright 2012, JP Richardson 109 | 110 | 111 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | var ON_DEATH = require('./lib/death')({debug: true}) 2 | 3 | //to kill this, call `kill -9 pid` 4 | 5 | process.stdin.resume() 6 | 7 | ON_DEATH(function(err) { 8 | if (!err) 9 | console.log('Caught foo') 10 | else 11 | console.error('We got an uncaught exception! ' + err) 12 | }) 13 | 14 | setTimeout(function() { 15 | throw new Error('stupid exception') 16 | }, 5000) 17 | 18 | -------------------------------------------------------------------------------- /lib/death.js: -------------------------------------------------------------------------------- 1 | 2 | var defaultConfig = { 3 | uncaughtException: false, 4 | SIGINT: true, 5 | SIGTERM: true, 6 | SIGQUIT: true 7 | } 8 | 9 | var DEBUG = false 10 | 11 | function ON_DEATH (callback) { 12 | var handlers = []; 13 | Object.keys(defaultConfig).forEach(function(key) { 14 | var val = defaultConfig[key] 15 | var handler = null; 16 | if (val) { 17 | if (DEBUG) { 18 | handler = function() { 19 | var args = Array.prototype.slice.call(arguments, 0) 20 | args.unshift(key) 21 | console.log('Trapped ' + key) 22 | callback.apply(null, args) 23 | }; 24 | process.on(key, handler) 25 | } else { 26 | handler = function() { 27 | var args = Array.prototype.slice.call(arguments, 0) 28 | args.unshift(key) 29 | callback.apply(null, args) 30 | } 31 | process.on(key, handler) 32 | } 33 | handlers.push([key, handler]) 34 | } 35 | }) 36 | return function OFF_DEATH() { 37 | handlers.forEach(function (args) { 38 | var key = args[0]; 39 | var handler = args[1]; 40 | process.removeListener(key, handler); 41 | }) 42 | } 43 | } 44 | 45 | module.exports = function (arg) { 46 | if (typeof arg === 'object') { 47 | if (arg['debug']) 48 | DEBUG = arg.debug 49 | if (arg['DEBUG']) 50 | DEBUG = arg.DEBUG 51 | delete arg.debug; delete arg.DEBUG; 52 | 53 | Object.keys(arg).forEach(function(key) { 54 | defaultConfig[key] = arg[key] 55 | }) 56 | 57 | if (DEBUG) 58 | console.log('ON_DEATH: debug mode enabled for pid [%d]', process.pid) 59 | 60 | return ON_DEATH 61 | } else if (typeof arg === 'function') { 62 | return ON_DEATH(arg) 63 | } 64 | } 65 | 66 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "death", 3 | "version": "1.1.0", 4 | "description": "Gracefully cleanup when termination signals are sent to your process.", 5 | "repository": { 6 | "type": "git", 7 | "url": "git@github.com:jprichardson/node-death.git" 8 | }, 9 | "keywords": [ 10 | "sigint", 11 | "sigterm", 12 | "sigkill", 13 | "sigquit", 14 | "exception", 15 | "kill", 16 | "terminate", 17 | "process", 18 | "clean" 19 | ], 20 | "author": "JP Richardson ", 21 | "licenses": [ 22 | { 23 | "type": "MIT", 24 | "url": "" 25 | } 26 | ], 27 | "dependencies": {}, 28 | "devDependencies": { 29 | "win-spawn": "~1.1.1", 30 | "autoresolve": "0.0.3", 31 | "testutil": "~0.4.0", 32 | "colors": "~0.6.0-1" 33 | }, 34 | "main": "./lib/death.js", 35 | "scripts": { 36 | "test": "mocha test" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /test/death.test.js: -------------------------------------------------------------------------------- 1 | var spawn = require('win-spawn') 2 | var P = require('autoresolve') 3 | var testutil = require('testutil') 4 | var colors = require('colors') 5 | 6 | /* global describe, it, T, EQ */ 7 | 8 | describe('death', function() { 9 | describe('default behavior', function() { 10 | it('should catch SIGINT, SIGTERM, and SIGQUIT and return 3', function(done) { 11 | var signals = [] 12 | var progPath = P('test/resources/default') 13 | var prog = spawn(progPath, []) 14 | //console.dir(prog) 15 | 16 | prog.stdout.on('data', function(data) { 17 | //console.log(colors.cyan(data.toString())) 18 | }) 19 | 20 | prog.stderr.on('data', function(data) { 21 | //console.error(colors.red(data.toString())) 22 | signals = signals.concat(data.toString().trim().split('\n')) 23 | }) 24 | 25 | prog.on('exit', function(code) { 26 | EQ (code, 3) 27 | //console.dir(signals) 28 | T (signals.indexOf('SIGQUIT') >= 0) 29 | T (signals.indexOf('SIGTERM') >= 0) 30 | T (signals.indexOf('SIGINT') >= 0) 31 | done() 32 | }) 33 | 34 | setTimeout(function() { 35 | prog.kill('SIGINT') 36 | process.kill(prog.pid, 'SIGTERM') 37 | prog.kill('SIGQUIT') 38 | }, 100) 39 | 40 | }) 41 | }) 42 | 43 | describe('other signal', function() { 44 | it('should catch SIGINT, SIGTERM, SIGQUIT, and SIGHUP and return 4', function(done) { 45 | var signals = [] 46 | var progPath = P('test/resources/sighup') 47 | var prog = spawn(progPath, []) 48 | //console.dir(prog) 49 | 50 | prog.stdout.on('data', function(data) { 51 | //console.log(colors.cyan(data.toString())) 52 | }) 53 | 54 | prog.stderr.on('data', function(data) { 55 | //console.error(colors.red(data.toString())) 56 | signals = signals.concat(data.toString().trim().split('\n')) 57 | }) 58 | 59 | prog.on('exit', function(code) { 60 | EQ (code, 4) 61 | //console.dir(signals) 62 | T (signals.indexOf('SIGQUIT') >= 0) 63 | T (signals.indexOf('SIGTERM') >= 0) 64 | T (signals.indexOf('SIGINT') >= 0) 65 | T (signals.indexOf('SIGHUP') >= 0) 66 | done() 67 | }) 68 | 69 | setTimeout(function() { 70 | prog.kill('SIGINT') 71 | process.kill(prog.pid, 'SIGTERM') 72 | prog.kill('SIGQUIT') 73 | prog.kill('SIGHUP') 74 | }, 100) 75 | 76 | }) 77 | }) 78 | 79 | describe('disable signal', function() { 80 | it('should catch SIGINT and SIGTERM', function(done) { 81 | var signals = [] 82 | var progPath = P('test/resources/disable') 83 | var prog = spawn(progPath, []) 84 | //console.dir(prog) 85 | 86 | prog.stdout.on('data', function(data) { 87 | //console.log(colors.cyan(data.toString())) 88 | }) 89 | 90 | prog.stderr.on('data', function(data) { 91 | //console.error(colors.red(data.toString())) 92 | signals = signals.concat(data.toString().trim().split('\n')) 93 | }) 94 | 95 | prog.on('exit', function(code) { 96 | T (signals.indexOf('SIGQUIT') < 0) 97 | T (signals.indexOf('SIGTERM') >= 0) 98 | T (signals.indexOf('SIGINT') >= 0) 99 | done() 100 | }) 101 | 102 | setTimeout(function() { 103 | prog.kill('SIGINT') 104 | prog.kill('SIGTERM') 105 | setTimeout(function() { 106 | prog.kill('SIGQUIT') //this actually kills it since we disabled it 107 | },10) 108 | }, 100) 109 | 110 | }) 111 | }) 112 | 113 | describe('uncaughException', function() { 114 | describe('> when set to true', function() { 115 | it('should catch uncaughtException', function(done) { 116 | var errData = '' 117 | var progPath = P('test/resources/uncaughtException-true') 118 | var prog = spawn(progPath, []) 119 | //console.dir(prog) 120 | 121 | prog.stdout.on('data', function(data) { 122 | //console.log(colors.cyan(data.toString())) 123 | }) 124 | 125 | prog.stderr.on('data', function(data) { 126 | //console.error(colors.red(data.toString())) 127 | errData += data.toString().trim() 128 | }) 129 | 130 | prog.on('exit', function(code) { 131 | EQ (code, 70) 132 | T (errData.indexOf('uncaughtException') >= 0) 133 | T (errData.indexOf('UNCAUGHT SELF') >= 0) 134 | done() 135 | }) 136 | }) 137 | }) 138 | 139 | describe('> when set to false', function() { 140 | it('should catch uncaughtException', function(done) { 141 | var errData = '' 142 | var progPath = P('test/resources/uncaughtException-false') 143 | var prog = spawn(progPath, []) 144 | //console.dir(prog) 145 | 146 | prog.stdout.on('data', function(data) { 147 | //console.log(colors.cyan(data.toString())) 148 | }) 149 | 150 | prog.stderr.on('data', function(data) { 151 | //console.error(colors.red(data.toString())) 152 | errData += data.toString().trim() 153 | }) 154 | 155 | prog.on('exit', function(code) { 156 | EQ (code, 1) 157 | T (errData.indexOf('CAUGHT: uncaughtException') < 0) 158 | T (errData.indexOf('UNCAUGHT SELF') >= 0) 159 | done() 160 | }) 161 | }) 162 | }) 163 | }) 164 | }) 165 | 166 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --reporter spec 2 | --ui bdd 3 | --timeout 2000 -------------------------------------------------------------------------------- /test/resources/default: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var P = require('autoresolve') 4 | , ON_DEATH = require(P('lib/death')) 5 | 6 | var ret = 0x0 7 | console.log('HI FROM PROGRAM') 8 | 9 | ON_DEATH(function(signal, err) { 10 | console.error(signal) 11 | ret += 1 12 | }) 13 | 14 | setTimeout(function() { 15 | //twiddle thumbs 16 | process.exit(ret) 17 | }, 500) -------------------------------------------------------------------------------- /test/resources/disable: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var P = require('autoresolve') 4 | , ON_DEATH = require(P('lib/death'))({SIGQUIT: false}) 5 | 6 | var ret = 0x0 7 | console.log('HI FROM PROGRAM') 8 | 9 | ON_DEATH(function(signal, err) { 10 | console.error(signal) 11 | ret += 1 12 | }) 13 | 14 | setTimeout(function() { 15 | //twiddle thumbls 16 | process.exit(ret) 17 | }, 30000) 18 | 19 | -------------------------------------------------------------------------------- /test/resources/sighup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var P = require('autoresolve') 4 | , ON_DEATH = require(P('lib/death'))({SIGHUP: true}) 5 | 6 | var ret = 0x0 7 | console.log('HI FROM PROGRAM') 8 | 9 | ON_DEATH(function(signal, err) { 10 | console.error(signal) 11 | ret += 1 12 | }) 13 | 14 | setTimeout(function() { 15 | //twiddle thumbs 16 | process.exit(ret) 17 | }, 500) -------------------------------------------------------------------------------- /test/resources/uncaughtException-false: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var P = require('autoresolve') 4 | , ON_DEATH = require(P('lib/death'))({uncaughtException: false}) 5 | 6 | var ret = 0x0 7 | console.log('HI FROM PROGRAM') 8 | 9 | ON_DEATH(function(signal, err) { 10 | console.error('CAUGHT: ' + signal) 11 | console.error(err.message) 12 | process.exit(70) 13 | }) 14 | 15 | throw new Error('UNCAUGHT SELF') -------------------------------------------------------------------------------- /test/resources/uncaughtException-true: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var P = require('autoresolve') 4 | , ON_DEATH = require(P('lib/death'))({uncaughtException: true}) 5 | 6 | var ret = 0x0 7 | console.log('HI FROM PROGRAM') 8 | 9 | ON_DEATH(function(signal, err) { 10 | console.error(signal) 11 | console.error(err.message) 12 | process.exit(70) 13 | }) 14 | 15 | throw new Error('UNCAUGHT SELF') --------------------------------------------------------------------------------