关于javascript:web3 websocket连接阻止节点进程退出

web3 websocket connection prevents node process from exiting

我有一个节点JS进程,它创建了Web3 WebSocket连接,如下所示:

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

当进程完成时(我给它发送一个sigterm),它不会退出,而是永远挂起,没有控制台输出。

我在sigint和sigterm上注册了一个监听器,观察在哪些处理过程中,process._getActiveRequests()process._getActiveHandles()有突出之处,我看到了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 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>
}

为了完整起见,下面是监听信号的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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()
})

看起来Web3正在打开一个套接字,这是有意义的,因为我从未告诉它关闭连接。浏览文档和谷歌搜索,它看起来不像Web3对象有一个关闭或结束方法。

手动关闭上述stop中的套接字,可以使进程成功退出:

1
web3.currentProvider.connection.close()

有人有更优雅或官方认可的解决方案吗?我觉得很有趣的是,您必须手动执行此操作,而不是让对象在进程结束时自行销毁。其他客户机似乎会自动执行此操作,而不会明确告诉他们关闭连接。也许告诉您的节点进程创建的所有客户机在关闭时关闭它们的句柄/连接会更干净,但对我来说,这是意外的。


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

这感觉很有趣,因为与异步编程相比,您可能接触过更多的同步编程。考虑下面的代码

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

当你在上面运行时,你得到输出

1
2
$ node sync.js
Read data Hello World

这是一个同步代码。现在考虑相同的异步版本

1
2
3
4
5
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);

当你运行时,你得到下面的输出

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

现在,如果您认为是一个同步程序员,程序应该在最后一个console.log("Read data", data);结束,但是您得到的是随后打印的另一条语句。现在感觉很有趣?让我们在进程中添加一个exit语句

1
2
3
4
5
6
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)

现在,当您运行程序时,它将在最后一条语句结束。

1
2
$ node async.js
Read data undefined

但文件实际上并没有被读取。为什么?因为您从未给javascript引擎时间来执行挂起的回调。理想情况下,当没有剩余的工作可供处理时(没有挂起的回调、函数调用等),进程会自动完成。这就是异步世界的工作方式。有一些很好的,所以线程和文章你应该研究

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

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

如何退出node.js

为什么我的node.js进程在删除所有侦听器后不终止?

node.js进程如何知道何时停止?

因此,在异步世界中,您需要告诉进程退出,否则当没有挂起的任务(您知道如何检查-process._getActiveRequests()process._getActiveHandles()时,进程将自动退出)


在节点JS过程的末尾,只需调用:

1
web3.currentProvider.connection.close()


由于EIP-1193的实现以及即将发布的Web3 1.0.0,Javascript Web3模块的提供者API最近发生了一些实质性的变化。

根据代码,似乎web3.currentProvider.disconnect()可以工作。此方法还接受可选的codereason参数,如WebSocket.close(...)的MDN参考文档中所述。

重要提示:您会注意到我引用了上面的源代码,而不是文档。这是因为目前,disconnect方法不被认为是公共API的一部分。如果您在代码中使用它,您应该确保为它添加一个测试用例,因为它可能在任何时候中断!据我所见,WebSocketProvider.disconnect是在[email protected]中引入的,至今仍在最新版本中出现,即[email protected]。考虑到稳定的1.0.0版本将很快下降,我认为这在Now和[email protected]之间不会有太大的变化,但是在内部API的结构方面没有限制。

我已经和现在的维护者,Samuel Futter,也就是Github上的Nividia讨论了如何让内部供应商公开。我不完全同意他决定将其保留在这里的决定,但在他的辩护中,他是目前唯一的维护者,而且他非常全力地稳定了在1.0分公司正在进行的长期工作。

由于这些讨论的结果,我现在的观点是,那些需要为自己的WebSocket提供商提供一个稳定的API的人应该自己编写一个兼容EIP-1193的提供商,并将其发布到NPM上供其他人使用。请遵循semver,并在您自己的公共API中包含类似的disconnect方法。如果你把它写在TypeScript中,就可以得到加分,因为这样你就可以明确地宣布类成员为publicprotectedprivate

如果您这样做,请注意,EIP-1193仍处于草稿状态,因此您需要密切关注关于以太坊制造商的EIP-1193讨论以及提供商之间的关系,以了解可能发生的任何变化。