From 7b02cdc10864a495c7ad918638cd3d264f155fcc Mon Sep 17 00:00:00 2001 From: David Von Lehman Date: Thu, 11 Aug 2016 08:17:07 -0700 Subject: [PATCH] Add timeout support --- README.md | 3 +++ index.js | 35 +++++++++++++++++++++++++++-------- package.json | 3 ++- test/fixtures/timeout.js | 3 +++ test/yexec.js | 14 ++++++++++++++ 5 files changed, 49 insertions(+), 9 deletions(-) create mode 100644 test/fixtures/timeout.js diff --git a/README.md b/README.md index fef1099..4382f4a 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ Yet another process execution wrapper. Uses `child_process.spawn` to execute an * Supports optional log filter * Protects against double callbacks from `error` and `exit` events. * Invokes callback with an Error if process exits with non-zero code +* Specify an optional timeout. If the process has not exited within the interval the process is force killed and a `TIMEOUT` error is passed in the callback. ### Usage @@ -24,12 +25,14 @@ var params = { executable: 'git', args: ['clone', 'https://github.com/nodejs/node.git'], logger: winston, + timeout: 5000, // 5 seconds logFilter: function(level, msg) { return level !== 'info'; } }; yexec(params, function(err) { + // If timeout occurred, err.code will be 'TIMEOUT' winston.error('Oops, git failed with code %s', err.code); }); ~~~ diff --git a/index.js b/index.js index deb5a95..8ac38f9 100644 --- a/index.js +++ b/index.js @@ -15,17 +15,11 @@ module.exports = function(params, callback) { debug('spawning %s %s', params.executable, params.args.join(' ')); - var process; - try { - process = spawn(params.executable, params.args, options); - } catch (err) { - return callback(err); - } - var processExited; + var processTimedOut; var filter; - // If the + // If logFilter is an array if (isArray(params.logFilter)) { filter = function(level, msg) { return !some(params.logFilter, function(pattern) { @@ -49,6 +43,13 @@ module.exports = function(params, callback) { } }; + var process; + try { + process = spawn(params.executable, params.args, options); + } catch (err) { + return callback(err); + } + // Log stdout to the log as info process.stdout.on('data', function(data) { log('info', data); @@ -70,6 +71,13 @@ module.exports = function(params, callback) { process.on('exit', function(code) { if (processExited) return; processExited = true; + + if (processTimedOut === true) { + var error = new Error('Process ' + executableBaseName + ' timed out'); + error.code = 'TIMEOUT'; + return callback(error); + } + if (isNumber(code) && code !== 0) { var error = new Error('Process ' + executableBaseName + ' failed with code ' + code); error.code = code; @@ -78,4 +86,15 @@ module.exports = function(params, callback) { callback(); } }); + + if (isNumber(params.timeout)) { + // If the process still has not exited after the timeout period has elapsed, + // force kill it. + setTimeout(function() { + if (!processExited) { + processTimedOut = true; + process.kill(); + } + }, params.timeout); + } }; diff --git a/package.json b/package.json index 896f388..490da60 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "yexec", - "version": "1.0.3", + "version": "1.1.0", "description": "Yet another cmd execution wrapper that works the way I like it", "main": "index.js", "scripts": { @@ -16,6 +16,7 @@ "lodash.isarray": "^4.0.0", "lodash.isfunction": "^3.0.8", "lodash.isnumber": "^3.0.3", + "lodash.isstring": "^4.0.1", "lodash.pick": "^4.2.0", "lodash.some": "^4.5.1" }, diff --git a/test/fixtures/timeout.js b/test/fixtures/timeout.js new file mode 100644 index 0000000..adf30c0 --- /dev/null +++ b/test/fixtures/timeout.js @@ -0,0 +1,3 @@ +setTimeout(function() { + process.exit(); +}, 100); diff --git a/test/yexec.js b/test/yexec.js index 5550ae9..d744ae0 100644 --- a/test/yexec.js +++ b/test/yexec.js @@ -71,6 +71,20 @@ describe('yexec', function() { done(); }); }); + + it('kills process if not finished within timeout period', function(done) { + var log = new Log(); + var params = { + executable: 'node', + args: [path.join(__dirname, './fixtures/timeout.js')], + logger: log, + timeout: 20 + }; + yexec(params, function(err) { + assert.equal(err.code, 'TIMEOUT'); + done(); + }); + }); }); function Log() {