From 9cf2a02d8ba49244779257969d0307c96b2d777b Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Wed, 14 Apr 2010 11:59:24 -0700 Subject: [PATCH] Add timeout and maxBuffer options to child_process.exec --- doc/api.markdown | 16 ++++++++++++- lib/child_process.js | 52 ++++++++++++++++++++++++++++++++++++---- test/simple/test-exec.js | 11 +++++++++ 3 files changed, 73 insertions(+), 6 deletions(-) diff --git a/doc/api.markdown b/doc/api.markdown index c231af282f..f3d79d816c 100644 --- a/doc/api.markdown +++ b/doc/api.markdown @@ -907,7 +907,7 @@ Example: grep.stdin.end(); -### child_process.exec(command, callback) +### child_process.exec(command, [options, ] callback) High-level way to execute a command as a child process, buffer the output, and return it all in a callback. @@ -928,6 +928,20 @@ The callback gets the arguments `(error, stdout, stderr)`. On success, `error` will be `null`. On error, `error` will be an instance of `Error` and `err.code` will be the exit code of the child process. +There is a second optional argument to specify several options. The default options are + + { encoding: 'utf8' + , timeout: 0 + , maxBuffer: 200*1024 + , killSignal: 'SIGKILL' + } + +If `timeout` is greater than 0, then it will kill the child process +if it runs longer than `timeout` milliseconds. The child process is killed with +`killSignal` (default: `'SIGKILL'`). `maxBuffer` specifies the largest +amount of data allowed on stdout or stderr - if this value is exceeded then +the child process is killed. + ## File System diff --git a/lib/child_process.js b/lib/child_process.js index 5a90442261..8066c0fa31 100644 --- a/lib/child_process.js +++ b/lib/child_process.js @@ -11,22 +11,64 @@ var spawn = exports.spawn = function (path, args, env) { }; -exports.exec = function (command, callback) { +exports.exec = function (command /* , options, callback */) { + var options = { encoding: 'utf8' + , timeout: 0 + , maxBuffer: 200*1024 + , killSignal: 'SIGKILL' + }; + + var callback = arguments[arguments.length-1]; + if (typeof arguments[1] == 'object') { + var keys = Object.keys(options); + for (var i = 0; i < keys.length; i++) { + var k = keys[i]; + if (arguments[1][k] !== undefined) options[k] = arguments[1][k]; + } + } + var child = spawn("/bin/sh", ["-c", command]); var stdout = ""; var stderr = ""; + var killed = false; + + var timeoutId; + if (options.timeout > 0) { + timeoutId = setTimeout(function () { + if (!killed) { + child.kill(options.killSignal); + killed = true; + timeoutId = null; + } + }, options.timeout); + } - child.stdout.setEncoding('utf8'); - child.stdout.addListener("data", function (chunk) { stdout += chunk; }); + child.stdout.setEncoding(options.encoding); + child.stderr.setEncoding(options.encoding); - child.stderr.setEncoding('utf8'); - child.stderr.addListener("data", function (chunk) { stderr += chunk; }); + child.stdout.addListener("data", function (chunk) { + stdout += chunk; + if (!killed && stdout.length > options.maxBuffer) { + child.kill(options.killSignal); + killed = true; + } + }); + + child.stderr.addListener("data", function (chunk) { + stderr += chunk; + if (!killed && stderr.length > options.maxBuffer) { + child.kill(options.killSignal); + killed = true + } + }); child.addListener("exit", function (code) { + if (timeoutId) clearTimeout(timeoutId); if (code == 0) { if (callback) callback(null, stdout, stderr); } else { var e = new Error("Command failed: " + stderr); + e.killed = killed; e.code = code; if (callback) callback(e, stdout, stderr); } diff --git a/test/simple/test-exec.js b/test/simple/test-exec.js index 10f537b826..c0cfa5c2d1 100644 --- a/test/simple/test-exec.js +++ b/test/simple/test-exec.js @@ -9,6 +9,7 @@ exec("ls /", function (err, stdout, stderr) { puts("error!: " + err.code); puts("stdout: " + JSON.stringify(stdout)); puts("stderr: " + JSON.stringify(stderr)); + assert.equal(false, err.killed); } else { success_count++; p(stdout); @@ -21,6 +22,7 @@ exec("ls /DOES_NOT_EXIST", function (err, stdout, stderr) { error_count++; assert.equal("", stdout); assert.equal(true, err.code != 0); + assert.equal(false, err.killed); puts("error code: " + err.code); puts("stdout: " + JSON.stringify(stdout)); puts("stderr: " + JSON.stringify(stderr)); @@ -31,6 +33,15 @@ exec("ls /DOES_NOT_EXIST", function (err, stdout, stderr) { } }); +exec("sleep 10", { timeout: 50 }, function (err, stdout, stderr) { + assert.ok(err); + assert.ok(err.killed); +}); + +exec('python -c "print 200000*\'C\'"', { maxBuffer: 1000 }, function (err, stdout, stderr) { + assert.ok(err); + assert.ok(err.killed); +}); process.addListener("exit", function () { assert.equal(1, success_count);