关于 `socket.setTimeout` 在高压请求下,Nodejs会出现 `EventEmitter memory leak detected` 的问题讨论

关于 socket.setTimeout 在高压请求下,Nodejs会出现 EventEmitter memory leak detected 的问题讨论

最近又遇到 EventEmitter memory leak detected 问题了。

原因是超时使用了 socket.setTimeout() ,在压测的时候出现 #issues3 EventEmitter内存可能泄漏的异常。

于是我又想起了之前分享过的关于超时设置的问题。

我使用 setTimeout() 替换 socket.setTimeout() 后,此问题就不再出现了。

看了一下 net.js 的源代码,感觉是不会出问题的,但是实际输出和看的代码不一致。有些疑惑,不知道各位是否也遇到类似问题呢?


5 回复

关于 socket.setTimeout 在高压请求下,Nodejs会出现 EventEmitter memory leak detected 的问题讨论

最近又遇到 EventEmitter memory leak detected 问题了。

背景

这个问题发生在我使用 socket.setTimeout() 方法来处理客户端连接超时时。在进行压力测试(压测)的过程中,我遇到了 #issues3 中提到的 EventEmitter 内存泄漏问题。

问题描述

socket.setTimeout() 方法用于在指定的时间后触发一个超时事件。然而,在高压请求环境下,我发现当这个超时事件被频繁触发时,Node.js 会抛出 EventEmitter memory leak detected 的警告,提示存在内存泄漏。

原因分析

经过检查 net.js 源代码,我发现理论上 socket.setTimeout() 不应该导致内存泄漏。但是实际运行中发现,当超时事件频繁触发时,Node.js 仍然报告了内存泄漏。

解决方案

为了解决这个问题,我尝试将 socket.setTimeout() 替换为 setTimeout()。具体来说,我创建了一个独立的定时器来管理超时逻辑,而不是直接在 socket 上调用 setTimeout()

示例代码

const net = require('net');

// 创建一个TCP服务器
const server = net.createServer((socket) => {
    console.log('Client connected');
    
    // 使用独立的定时器来管理超时
    const timeoutId = setTimeout(() => {
        console.error('Socket timed out');
        socket.destroy(); // 销毁socket连接
    }, 5000); // 设置5秒超时

    socket.on('data', (data) => {
        clearTimeout(timeoutId); // 收到数据时清除定时器
        console.log(`Received data: ${data.toString()}`);
        socket.write('Data received');
    });

    socket.on('end', () => {
        console.log('Client disconnected');
    });
});

server.listen(8080, () => {
    console.log('Server listening on port 8080');
});

解释

  • 独立定时器:通过使用独立的 setTimeout() 定时器,避免了在 socket 对象上直接绑定超时事件,从而减少了 EventEmitter 的内存泄漏风险。
  • 清除定时器:在接收到数据时清除定时器,确保只有在没有数据接收的情况下才会触发超时事件。

这种方法不仅解决了内存泄漏问题,还提高了代码的可读性和可维护性。

结论

在高压请求环境下,直接在 socket 上使用 socket.setTimeout() 可能会导致 EventEmitter 内存泄漏。通过使用独立的 setTimeout() 定时器并适时清除定时器,可以有效避免这一问题。希望这些建议对大家有所帮助。


猜测应该是 keep-alive 模式时,此socket 被多次request 所依赖,而每次request 都设置一次所致… 纯属猜测…

一开始我也怀疑是这个问题,后来我在setTimeout之前先removeAllListener(‘timeout’),结果还是一样。

请问,这个问题,解决了否???

在Node.js中,当你在高并发请求场景下使用socket.setTimeout()时,可能会遇到EventEmitter memory leak detected警告。这是因为socket.setTimeout()内部维护了一个定时器,如果这些定时器没有被正确清理,就会导致内存泄漏。

示例代码

假设我们有一个简单的TCP客户端连接到服务器,并设置了超时时间:

const net = require('net');

const client = new net.Socket();

client.connect(12345, 'localhost', () => {
    console.log('Connected');
});

// 使用 socket.setTimeout 设置超时
client.setTimeout(5000, () => {
    console.log('Socket timeout');
    client.destroy(); // 销毁socket
});

client.on('data', (data) => {
    console.log('Received: ', data.toString());
});

client.on('end', () => {
    console.log('Disconnected');
});

在这个例子中,如果你在高并发情况下频繁创建和销毁socket,可能会导致定时器累积,从而引发内存泄漏问题。

解决方案

为了防止这个问题,你可以使用setTimeout来替代socket.setTimeout。这样可以避免Node.js内部对定时器的管理问题。

const net = require('net');
const { setTimeout: setTimeoutGlobal } = require('timers/promises');

const client = new net.Socket();

client.connect(12345, 'localhost', () => {
    console.log('Connected');
});

async function checkTimeout() {
    await setTimeoutGlobal(5000);
    console.log('Global timeout reached');
    client.destroy();
}

checkTimeout().catch(console.error);

client.on('data', (data) => {
    console.log('Received: ', data.toString());
});

client.on('end', () => {
    console.log('Disconnected');
});

总结

通过使用全局的setTimeout而非socket.setTimeout,你可以避免由于定时器管理不当导致的内存泄漏问题。确保在每个请求完成后正确清理资源,也是预防此类问题的关键。

回到顶部