Node.js - child process timeouts

October 27, 2015

One of the options when execing a child process is timeout. Setting this means that a kill signal will automatically be sent after the given time period.

var cp = require('child_process');

cp.execFile('/usr/bin/env', ['sleep', '10'], {
  timeout: 1000
}, function (err, stdout, stderr) {
  console.log(err);
});

Running this gives the following:

$ node timeout.js
{ [Error: Command failed: ] killed: true, code: null, signal: 'SIGTERM' }

Note that the parameter timeout takes a value in milliseconds. Setting the timeout to 1 (plenty of time if the unit was in seconds) can lead to unpredictable results, as the process is signaled at creation.

When creating a child process from a setuid binary, node is unable to send a kill signal.

var cp = require('child_process');

cp.execFile('/usr/bin/sudo', ['sleep', '5'], {
  timeout: 10
}, function (err, stdout, stderr) {
  console.log(err);
});
$ node timeout2.js
{ [Error: kill EPERM] code: 'EPERM', errno: 'EPERM', syscall: 'kill' }
$ man 2 kill | grep -A 4 EPERM
[EPERM]     The sending process is not the super-user and its
            effective user id does not match the effective user-id
            of the receiving process.  When signaling a process
            group, this error is returned if any members of the
            group could not be signaled.
$

The callback executes when the timeout kicks in, with the error message that the kill syscall failed with errno EPERM. However, the node process stays alive until the sleep ends.

Strangely, if the console.log is swapped for a throw, no output is ever seen.

var cp = require('child_process');

cp.execFile('/usr/bin/sudo', ['sleep', '5'], {
  timeout: 10
}, function (err, stdout, stderr) {
  throw(err);
});
$ time node timeout3.js

real	0m5.122s
user	0m0.079s
sys	0m0.038s
$