Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't my Node.js process terminate once all listeners have been removed?

In the following code, I assign a listener to the data event of process.stdin with the once method.

console.log('Press Enter to allow process to terminate')
process.stdin.once('data', callback)

function callback (data) {
    console.log('Process can terminate now')
}

In theory, when the callback has fired, the event listener should be automatically removed (because I attached it with once), allowing the process to terminate. Surprisingly, in this case, the process never terminates (The code you see is the whole thing, try it!). I also tried manually removing the listener, but that changes nothing.

Is there something else going on here that I don't realise perhaps?

like image 852
Shawn Avatar asked Sep 23 '14 20:09

Shawn


People also ask

How do you end a NodeJS process?

Method 1: Using ctrl+C key: When running a program of NodeJS in the console, you can close it with ctrl+C directly from the console with changing the code shown below: Method 2: Using process. exit() Function: This function tells Node. js to end the process which is running at the same time with an exit code.

How do I stop NodeJS from listening?

close() prevents new connections and waits until all the clients are closed. To forcibly kill a node server you need to call server. close() and then close all the open connections from the server end.

How many listeners can be attached to node js?

The default number of listeners is 10.


2 Answers

Adding the data event listener to process.stdin add a reference to it that keeps the process open. That reference stays in place even after removing all event listeners. What you can do is manually unref() it in your callback, like so:

console.log('Press Enter to allow process to terminate')
process.stdin.once('data', callback)

function callback (data) {
    console.log('Process can terminate now')
    process.stdin.unref()
}

Also, as a general debugging tool for stuff like this, there are two (undocumented) functions that you can call to get a list of things keeping your process open:

process._getActiveHandles()
process._getActiveRequests()

See this pull request in the node project for background.


Update: You asked about attaching event listeners after you've unref()'d process.stdin. Here's a quick example showing that the listener does attach itself and function:

console.log('Press Enter to allow process to terminate')
process.stdin.once('data', callback)

function callback (data) {
    console.log('Unreferencing stdin. Exiting in 5 seconds.')
    process.stdin.unref()

    process.stdin.once('data', function(data) {
        console.log('More data')
    })

    setTimeout(function() {
        console.log('Timeout, Exiting.')
    }, 5000);
}

With that code, if you press another key before the setTimeout fires (5 seconds), then you'll see More data output to the console. Once the setTimeout's callback fires, the process will exit. The trick is that setTimeout is creating a timer which the process also keeps a reference too. Since the process still has a reference to something, it won't exit right away. Once the timer fires, the reference it released and the process exits. This also shows that references are added (and removed) to things that need them automatically (the timer created by setTimeout in this case).

like image 143
Mike S Avatar answered Sep 19 '22 17:09

Mike S


Just call .end on the process.stdin stream

To me, this is a more straightforward (and documented) way of ending the stream.

console.log('Press Enter to allow process to terminate');
process.stdin.once('data', callback);

function callback (data) {
  console.log('Process can terminate now');
  process.stdin.end();
}

It's also worth noting that node sets the stream as the context for the callback function, so you can just call this.end

console.log('Press Enter to allow process to terminate');
process.stdin.once('data', callback);

function callback (data) {
  // `this` refers to process.stdin here
  console.log('Process can terminate now');
  this.end();
}

You could also emit an end event which has additional benefits like being able to call a function when the stream is finished.

console.log('Press Enter to allow process to terminate');

process.stdin.once('data', function(data) {
  console.log('Process can terminate now');
  this.emit("end");
});

process.stdin.on('end', function() {
  console.log("all done now");
});

This would output

Press Enter to allow process to terminate

Process can terminate now
all done now

A final solution would be to use process.exit. This allows you to terminate a program whenver you want.

for (var i=0; i<10; i++) {
  process.stdout.write( i.toString() );
  if (i > 3) process.exit();
}

Output

01234

This would work inside of a stream callback, as part of a child process, or any other bit of code.

like image 22
Mulan Avatar answered Sep 17 '22 17:09

Mulan