Skip to content

_connect() 和 disconnect() 存在 WebSocket连接泄漏,长时间运行会导致系统端口耗尽 #18

@yilin-zhouyang

Description

@yilin-zhouyang

我在使用 dingtalk-stream v2.1.5的过程中遇到了一个比较严重的问题:跑了两天的机器人直接把我电脑的网络端口全部耗尽了,整台电脑断网,所有程序都报Can't assign requested address。

排查下来发现是 SDK 里有两个 bug 导致 TCP 连接泄漏:

Bug 1:_connect() 不销毁旧的 WebSocket
_connect() 里直接 this.socket = new WebSocket(...) 覆盖了旧的引用,但旧的socket 对应的 TCP 连接并没有被关闭,每次重连就泄漏一个连接。

// client.mjs L134
this.socket = new WebSocket(this.dw_url, this.sslopts);
// 旧的 socket 就这么丢了,TCP 连接永远不释放

Bug 2:disconnect() 无法阻止已排队的 autoReconnect
close 事件里 setTimeout(this.connect.bind(this), this.reconnectInterval) 一旦排进队列,之后再调 disconnect() 设 userDisconnect = true已经来不及了,因为 connect() 方法开头不检查 userDisconnect,只在 catch分支才检查。

另外 disconnect() 用的是 socket.close(),如果对端不响应关闭握手,TCP连接就永远不会释放。

影响:这两个问题叠加起来,心跳超时一触发重连就不断泄漏 socket。我这边一个 node进程最后积累了 18000+ 个 ESTABLISHED 连接,直接把系统临时端口用完了。

建议修复
1. _connect() 开头加一行销毁旧 socket:

if (this.socket) {
try { this.socket.removeAllListeners(); this.socket.terminate(); } catch (e)
{}
this.socket = null;
}

2. connect() 开头检查 userDisconnect,阻止已排队的定时器继续执行:

async connect() {
if (this.userDisconnect) return;
// ...
}

3. disconnect() 用 terminate() 替代 close(),并清除事件监听器:

disconnect() {
this.userDisconnect = true;
if (this.config.keepAlive && this.heartbeatIntervallId !== void 0) {
clearInterval(this.heartbeatIntervallId);
}
if (this.socket) {
try { this.socket.removeAllListeners(); this.socket.terminate(); } catch
(e) {}
this.socket = null;
}
}

我这边已经用 patch-package打了补丁在跑了,目前稳定运行没有再泄漏。希望官方能在后续版本里修复一下,不然其他开发者也可能踩到这个坑。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions