Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

web3 websocket connection prevents node process from exiting

I have a node js process that creates a web3 websocket connection, like so:

web3 = new Web3('ws://localhost:7545')

When the process completes (I send it a SIGTERM), it does not exit, but rather hangs forever with no console output.

I registered a listener on SIGINT and SIGTERM to observe at what handles the process has outstanding with process._getActiveRequests() and process._getActiveHandles(), I see this:

 Socket {
    connecting: false,
    _hadError: false,
    _handle: 
     TCP {
       reading: true,
       owner: [Circular],
       onread: [Function: onread],
       onconnection: null,
       writeQueueSize: 0 },
    <snip>
    _peername: { address: '127.0.0.1', family: 'IPv4', port: 7545 },
    <snip>
}

For completeness, here is the code that's listening for the signals:

async function stop() {
  console.log('Shutting down...')

  if (process.env.DEBUG) console.log(process._getActiveHandles())

  process.exit(0)
}

process.on('SIGTERM', async () => {
  console.log('Received SIGTERM')
  await stop()
})

process.on('SIGINT', async () => {
  console.log('Received SIGINT')
  await stop()
})

Looks like web3 is holding a socket open, which makes sense since I never told it to close the connection. Looking through the documentation and googling, it doesn't look like there's a close or end method for the web3 object.

Manually closing the socket in stop above allows the process to successfully exit:

web3.currentProvider.connection.close()

Anyone have a more elegant or officially sanctioned solution? It feels funny to me that you have to manually do this rather than have the object destroy itself on process end. Other clients seem to do this automatically without explicitly telling them to close their connections. Perhaps it is cleaner to tell all the clients created by your node process to close their handles/connections on shutdown anyway, but to me, this was unexpected.

like image 466
Andy Pang Avatar asked May 31 '18 20:05

Andy Pang


3 Answers

It feels funny to me that you have to manually do this rather than have the object destroy itself on process end

It feels funny because you have probably been exposed to more synchronous programming compared to asynchronous. Consider the below code

fs = require('fs')
data = fs.readFileSync('file.txt', 'utf-8');
console.log("Read data", data)

When you run above you get the output

$ node sync.js
Read data Hello World

This is a synchronous code. Now consider the asynchronous version of the same

fs = require('fs')
data = fs.readFile('file.txt', 'utf-8', function(err, data) {
    console.log("Got data back from file", data)
});
console.log("Read data", data);

When you run you get the below output

$ node async.js
Read data undefined
Got data back from file Hello World

Now if you think as a synchronous programmer, the program should have ended at the last console.log("Read data", data);, but what you get is another statement printed afterwards. Now this feels funny? Let's add a exit statement to the process

fs = require('fs')
data = fs.readFile('file.txt', 'utf-8', function(err, data) {
    console.log("Got data back from file", data)
});
console.log("Read data", data);
process.exit(0)

Now when you run the program, it ends at the last statement.

$ node async.js
Read data undefined

But the file is not actually read. Why? because you never gave time for JavaScript engine to execute the pending callbacks. Ideally a process automatically finishes when there is no work left for it to do (no pending callbacks, function calls etc...). This is the way asynchronous world works. There are some good SO threads and articles you should look into

https://medium.freecodecamp.org/walking-inside-nodejs-event-loop-85caeca391a9

https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/

How to exit in Node.js

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

How does a node.js process know when to stop?

So in the async world you need to either tell the process to exit or it will automatically exit when there are no pending tasks (which you know how to check - process._getActiveRequests() and process._getActiveHandles())

like image 168
Tarun Lalwani Avatar answered Oct 17 '22 12:10

Tarun Lalwani


The provider API for the JavaScript web3 module has gone through some substantial change recently due to the implementation of EIP-1193 and the impending release of Web3 1.0.0.

Per the code, it looks like web3.currentProvider.disconnect() should work. This method also accepts optional code and reason arguments, as described in the MDN reference docs for WebSocket.close(...).

Important: you'll notice that I referenced the source code above and not the documentation. That's because at present the disconnect method is not considered part of the public API. If you use it in your code, you should be sure to add a test case for it, as it could break at any time! From what I can see, WebSocketProvider.disconnect was introduced in [email protected] and is still present in the latest release as of today, which is [email protected]. Given that the stable 1.0.0 release is due to drop very soon, I don't think it's likely that this will change much between now and [email protected], but there's no holds barred when it comes to the structure of internal APIs.

I've discussed making the internal providers public at length with the current maintainer, Samuel Furter, aka nividia on GitHub. I don't fully agree with his decision to keep it internal here, but in his defense he's the only maintainer at present and he's had his hands very full with stabilizing the long-standing work in progress on the 1.0 branch.

As a result of these discussions, my opinion at the moment is that those who need a stable API for their WebSocket provider should write an EIP-1193 compatible provider of their own, and publish it on NPM for others to use. Please follow semver for this, and include a similar disconnect method in your own public API. Bonus points if you write it in TypeScript, as this gives you the ability to explicitly declare class members as public, protected, or private.

If you do this, be aware that EIP-1193 is still in draft status, so you'll need to keep an eye on the EIP-1193 discussions on EthereumMagicians and in the Provider Ring Discord to stay on top of any changes that might occur.

like image 2
Ben Burns Avatar answered Oct 17 '22 14:10

Ben Burns


At the end of your node js process, simply call:

web3.currentProvider.connection.close()
like image 1
goodvibration Avatar answered Oct 17 '22 13:10

goodvibration