Search code examples
node.jspipeforkbroken-pipe

Kill fork on windows throws "write EPIPE"


I would like to kill forks after a specific amount of time. However, in my codebase, I sometimes get the following error (only on windows) :

events.js:85
      throw er; // Unhandled 'error' event
            ^
Error: write EPIPE
    at exports._errnoException (util.js:746:11)
    at ChildProcess.target._send (child_process.js:484:28)
    at ChildProcess.target.send (child_process.js:416:12)
    at sendHelper (cluster.js:676:8)
    at send (cluster.js:512:5)
    at cluster.js:488:7
    at SharedHandle.add (cluster.js:99:3)
    at queryServer (cluster.js:480:12)
    at Worker.onmessage (cluster.js:438:7)
    at ChildProcess.<anonymous> (cluster.js:692:8)

This error seems to happen whenever a worker is not yet completely started and is killed (eg takes 1 second to start and is killed before having started)

Here's a minimal example so that you can reproduce.

var cluster = require('cluster');
var http = require('http');
var numCPUs = require('os').cpus().length;

var workers=[];

if (cluster.isMaster) {
    // Fork workers.
    for (var i = 0; i < numCPUs; i++) {
        workers[i] = cluster.fork();
        console.log('forking');
    }

    cluster.on('exit', function(worker, code, signal) {
        console.log('worker ' + worker.process.pid + ' died');
    });

    setTimeout(function(){
        workers.forEach(function(worker){
            worker.kill();
        })
    },1)

} else {
    // Workers can share any TCP connection
    // In this case its a HTTP server
    http.createServer(function(req, res) {
        res.writeHead(200);
        res.end("hello world\n");
    }).listen(8000);
}

If I change the http.createServer to something like console.log, I don't have the problem, so I suspect it is because my worker hasn't finished "starting".

Strangely enough, I also get sometimes an AssertionError instead (they doesn't seem to be any kind of pattern, I have sometimes had 10s in a row of the same error, sometimes it toggles between the two errors : it seems random between EPIPE and ASSERTION error).

assert.js:86
  throw new assert.AssertionError({
        ^
AssertionError: Resource leak detected.
    at removeWorker (cluster.js:346:9)
    at ChildProcess.<anonymous> (cluster.js:366:34)
    at ChildProcess.g (events.js:199:16)
    at ChildProcess.emit (events.js:110:17)
    at Process.ChildProcess._handle.onexit (child_process.js:1074:12)

Solution

  • The reason for the error is that the daemon is not yet listening when we send it the SIGTERM signal:

    The solution is to wait for the listening event before killing the fork.

    var cluster = require('cluster');
    var http = require('http');
    var numCPUs = require('os').cpus().length;
    
    var workers=[];
    
    if (cluster.isMaster) {
        // Fork workers.
        for (var i = 0; i < numCPUs; i++) {
            workers[i] = cluster.fork();
            console.log('forking');
        }
    
        cluster.on('listening', function(worker, code, signal) {
            setTimeout(function(){
                worker.kill();
            },1)
        });
    
        cluster.on('exit', function(worker, code, signal) {
            console.log('worker ' + worker.process.pid + ' died');
        });
    
    
    
    } else {
        // Workers can share any TCP connection
        // In this case its a HTTP server
        http.createServer(function(req, res) {
            res.writeHead(200);
            res.end("hello world\n");
        }).listen(8000);
    }