【问题标题】:Memory leak in while with promises承诺时内存泄漏
【发布时间】:2022-01-06 02:41:04
【问题描述】:

我有一个nodejs cluster,其主节点处理工作循环(在 while 循环中)并监听工作消息以在循环中进行。 (在我的代码中 index.js 不会在 setInterval 上发送消息,而是在其他类型的事件上发送消息,我在这里简化了代码以了解问题的本质)

Server.js

var cluster = require('cluster');
const ClusterMessages = require('cluster-messages');
const messages = new ClusterMessages();

if (cluster.isMaster){
    let worker = cluster.fork()
    console.log(cluster);
    
    (async ()=>{
    let cycle = 0
    while(true){
        console.log(cycle);
        cycle ++
        await Promise.all([
            enough(),
        ])
    }
    function enough () {
        return new Promise(resolve => {
            messages.on('enough', () => {
                console.log('enough');
                resolve()
            });
        });
    }})()
} else {
    require('./index.js')
}

Index.js

const ClusterMessages = require('cluster-messages');
const messages = new ClusterMessages();

setInterval(() => {
    messages.send('enough');
}, 1000);

代码运行良好(因此,在本示例和我的代码中),但似乎存在内存泄漏,您可以从这段代码的输出中理解:

0
enough
1
enough
enough
2
enough
enough
enough
3
enough
enough
enough
enough...

我尝试了几件事,比如交换新的 Promise 和 messages.on(),在 Promise 的回调中添加一个 return,但我不知道这里发生了什么。有什么想法吗?


解决方法是,与 cluster-messages 包的“事件监听器”相反,再制作一个可以触发一次的事件

Server.js

if (cluster.isMaster){
    let worker = cluster.fork()
    console.log(cluster);

    // Importing events
    const EventEmitter = require('events');
    const eventEmitter = new EventEmitter();
    
    messages.on('enough', () => {
        eventEmitter.emit('event');
    });
    (async ()=>{let cycle = 0
    while(true){
        console.log(cycle);
        cycle ++
        await Promise.all([
            enough(),
        ])
    }
    function enough () {
        return new Promise(resolve => {
            eventEmitter.once('event', () => {
                console.log('event');
                resolve()
            });
        });
    }})()
} else {
    require('./index.js')
}

Index.js

const ClusterMessages = require('cluster-messages');
const messages = new ClusterMessages();

setInterval(() => {
    messages.send('enough');
}, 1000);

【问题讨论】:

  • 为什么要使用while 循环并承诺处理事件?
  • 我使用 while 是因为我需要循环运行未知次数我使用 Promise 是因为我不仅有足够的函数来解决问题,而且还有 3 个不同的函数,其中一个是超时(最短时间在进入循环的下一步之前等待)

标签: javascript node.js promise eventemitter


【解决方案1】:

每次调用enough() 都会为messages 上的enough 事件安装另一个侦听器。它们永远不会被删除,内存泄漏(并导致每个事件的日志数量增加)。而是使用once method 安装监听器:

function enough () {
    return new Promise(resolve => {
        messages.once('enough', () => {
//               ^^^^
            console.log('enough');
            resolve();
        });
    });
}

或者更简单,使用once

const { once } = require('events');
function enough() {
    return once(messages, 'enough');
}

在您的特定示例中,我建议不要使用承诺来处理事件。您甚至可能会错过在删除和重新附加侦听器时触发的事件。随便写

let cycle = 0
messages.on('enough', () => {
    console.log(cycle);
    cycle++;
});

如果由于某种原因您需要一个可以中断的循环或await 其他东西,我会推荐使用on 构建的异步迭代器:

const { once } = require('events');
(async () => {
    let cycle = 0
    for await (const _ of on(messages, 'enough')) {
        console.log(cycle);
        cycle++;
    }
})();

【讨论】:

  • 当我尝试你的解决方案时,我有'曾经不是一个函数'最后两个答案对我来说不可用,循环变量在这里只是用来帮助你理解正在发生的事情但在代码中没有真正的实用性
  • @JeanJacquesGourdin 你用的是什么版本的nodejs?有关何时引入这些实用方法,请参阅文档。
  • 我在 16.13,但问题似乎来自 npm 包集群消息,因为不可能关闭事件侦听器我不知道如何解决这个问题,就像只有一条消息一样。在外部足够()并触发resolve()...
  • 哦,我明白了,ClusterMessages 不是EventEmitter,而只是名称上的事件发射器。您可能希望向库提交功能请求以使其成为子类。同时,只需创建您自己的new EventEmitter 并从messages.on 调用它的.emit() 方法。有其他选择(例如使用promise queue),但这可能是最简单的解决方法。
  • 谢谢你,它的工作,我要发布答案
猜你喜欢
  • 2018-04-28
  • 2019-02-24
  • 2013-02-08
  • 2015-01-20
  • 2013-12-02
  • 2011-10-04
  • 2020-06-23
  • 1970-01-01
相关资源
最近更新 更多