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

Shawn picture Shawn · Sep 23, 2014 · Viewed 10.3k times · Source

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?

Answer

Mike S picture Mike S · Sep 23, 2014

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).