MaxListenersExceededWarning:检测到可能的 EventEmitter 内存泄漏。添加了 11 个消息列表。使用emitter.setMaxListeners() 增加限制

Posted

技术标签:

【中文标题】MaxListenersExceededWarning:检测到可能的 EventEmitter 内存泄漏。添加了 11 个消息列表。使用emitter.setMaxListeners() 增加限制【英文标题】:MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 message lis teners added. Use emitter.setMaxListeners() to increase limit 【发布时间】:2018-11-15 11:16:49 【问题描述】:

我知道这可能会标记为重复的解决方案,但堆栈溢出的解决方案对我不起作用。

问题

(node:5716) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 message lis
teners added. Use emitter.setMaxListeners() to increase limit.

我的代码库很大,有时我会遇到这个错误,我不知道为什么会这样。我试图增加听众限制,但不幸的是,它不起作用。

const EventEmitter = require('events');
const emitter = new EventEmitter()
emitter.setMaxListeners(50)

更新

浏览一番后,我运行此命令来跟踪警告

node --trace-warnings index.babel.js

原来是我的 socket.io 代码是我将 socket.io 与 Redis 一起使用的问题。这是错误

node:14212) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 message li
steners added. Use emitter.setMaxListeners() to increase limit
    at _addListener (events.js:281:19)
    at RedisClient.addListener (events.js:298:10)
    at Namespace.<anonymous> (D:/newProject/services/socket.js:21:17)
    at emitOne (events.js:115:13)
    at Namespace.emit (events.js:210:7)
    at Namespace.emit (D:\newProject\node_modules\socket.io\lib\namespace.js:213:10)
    at D:\newProject\node_modules\socket.io\lib\namespace.js:181:14
    at _combinedTickCallback (internal/process/next_tick.js:131:7)
    at process._tickCallback (internal/process/next_tick.js:180:9)

这是代码(但此代码用于更具体的任务,它不会一直执行)。

const redis = require('redis');
const config = require('../config');
const sub = redis.createClient(config.REDIS.port, config.REDIS.host);
const pub = redis.createClient(config.REDIS.port, config.REDIS.host);

sub.subscribe('spread');

module.exports = io => 
  io.on('connection', socket => 
    /* To find the User Login  */
    let passport = socket.handshake.session.passport; 

    if (typeof passport !== 'undefined') 
      socket.on('typing:send', data => 
        pub.publish('spread', JSON.stringify(data));
      );

      sub.on('message', (ch, msg) => 
        // This is the Exact line where I am getting this error
        io.emit(`$JSON.parse(msg).commonID:receive`,  ...JSON.parse(msg) );
      );
    
  );
;

【问题讨论】:

这是 99% 的时间,因为我们忘记在控制流的摘要阶段取消挂钩事件侦听器。您必须始终解开添加到全局执行上下文的每个事件侦听器。您正在构建 React 应用程序吗? @TobiahRex 您好,感谢您的评论,但我是新手,只是一步一步开始,您能举一些例子吗? 我打算在了解您的用例后给您一个示例。因此,为什么我问您是否正在构建 React 应用程序,因为我看到您在问题中添加了“reactjs”标签。那么,用例是什么? 是的,堆栈是 Node.js/React.js/mysql/Redis ,如果我使用应用程序这么快就会出现错误。 这个库是否让用户调用析构函数? 【参考方案1】:

Event Emitter 的默认限制是 10。您可以使用emitter.setMaxListeners 增加它。我的建议是除非明确要求,否则不要更改它,因为您没有取消订阅,所以听众增加了。现在到你的代码。

const redis = require('redis');
const config = require('../config');
const sub = redis.createClient(config.REDIS.port, config.REDIS.host);
const pub = redis.createClient(config.REDIS.port, config.REDIS.host);

sub.subscribe('spread');

module.exports = (io) => 
  io.on('connection', (socket) => 
    // this callback will be executed for all the socket connections.
    let passport =
      socket.handshake.session.passport; /* To find the User Login  */

    if (typeof passport !== 'undefined') 
      socket.on('typing:send', (data) => 
        pub.publish('spread', JSON.stringify(data));
      );

      // this is where you are subscribing for each and every socket connected to your server
      sub.on('message', (ch, msg) => 
        // this is the Exact line where I am getting this error

        // whereas you are emitting messages on socket manager, not on the socket.
        io.emit(`$JSON.parse(msg).commonID:receive`,  ...JSON.parse(msg) );
      );
    
  );
;

现在如果我们分析上面的代码,那么如果你打开 20 个到你的服务器的套接字连接,它将订阅 20 次,这里出错了。 现在,如果您的要求是在服务器级别侦听 Redis 上发布的消息,然后在 io 上发出它,那么您的代码应该如下所示

const redis = require('redis');
const config = require('../config');
const sub = redis.createClient(config.REDIS.port, config.REDIS.host);
const pub = redis.createClient(config.REDIS.port, config.REDIS.host);

sub.subscribe('spread');

module.exports = (io) => 
  sub.on('message', (ch, msg) => 
    // this is the Exact line where I am getting this error
    io.emit(`$JSON.parse(msg).commonID:receive`,  ...JSON.parse(msg) );
  );

  io.on('connection', (socket) => 
    let passport =
      socket.handshake.session.passport; /* To find the User Login  */

    if (typeof passport !== 'undefined') 
      socket.on('typing:send', (data) => 
        pub.publish('spread', JSON.stringify(data));
      );
    
  );
;

【讨论】:

【参考方案2】:

node.js 中的内置events 模块(如果您使用webpackbrowserify 编译,则该模块的一个版本捆绑到您的前端应用程序中)对您的代码进行了一些假设。有时,某个地方,有人决定如果您注册了X 数量的侦听器,那么肯定你有内存泄漏。有时它是正确的,并正确地提醒您去寻找泄漏点。

我多次收到此警告,但通常仅出于两个特定原因,两者都有简单的解决方案:


问题一:绑定的事件监听函数不匹配

您的组件可能看起来像这样,您使用组件方法作为事件侦听器,并且在注册时绑定它

import events from '../lib/events' // some singleton event emitter

class MyComponent extends React.Component 
  componentDidMount() 
    events.addEventListener('some-event', this.myMethod.bind(this))
  

  componentWillUnmount() 
    events.removeEventListener('some-event', this.myMethod.bind(this))
  

  myMethod() 
    // does something
  

  render() 
    // gotta have this too
  

这里的问题是function.bind 每次都会创建一个新函数,这样您尝试删除的函数与您添加的函数不同。因此,添加的函数不断累加(双关语),实际上确实存在内存泄漏。

解决方案 1:尽早绑定方法

尽早绑定你的方法,通常在constructor()。然后每次可以参考绑定的版本,保证去掉的功能和增加的功能一样。

import events from '../lib/events' // some singleton event emitter

class MyComponent extends React.Component 
  constructor() 
    // bind your method early so the function removed
    // is the same as the function added
    this.myMethod = this.myMethod.bind(this)
  

  componentDidMount() 
    events.addEventListener('some-event', this.myMethod)
  

  componentWillUnmount() 
    events.removeEventListener('some-event', this.myMethod)
  

  myMethod() 
    // does something
  

  render() 
    // gotta have this too
  


问题 2:大量的事件监听器

有时您确实已经完成了作业,并仔细检查了您是否已在需要时尽早绑定了您的听众,然后在适当的位置将它们全部删除。然后你仔细观察,发现你正在做这样的事情:

import MyComponent from './MyComponent' // same component above

class Parent extends React.Component 
  render() 
    return (
      <div>
         this.props.largeArray.map(MyComponent) 
      </div>
    )
  

假设 this.props.largeArray 有 50、100 或 250 个元素。这意味着(按设计!)您正在渲染 250 个 MyComponent 实例,每个实例都在注册另一个唯一的事件侦听器。

不要害怕!这是完全有效的代码,并且没有内存泄漏。但它确实突破了某个人在某个时候、某个地方武断地决定帮助保护您的最大侦听器限制。

解决方案2:改用eventemitter3

如果您确定自己已完成作业,并仔细检查了所有内容,并且(按设计!)注册了大量事件侦听器,那么最简单的解决方案是切换到使用 eventemitter3,即替代节点的 events 模块,但速度更快、与浏览器兼容,并且不会为您设置最大侦听器限制。

用法就像内置的events模块:

const EventEmitter = require('eventemitter3')
const emitter = new EventEmitter()

【讨论】:

这是一个简短的例子,谢谢。但我用收到此警告的确切行更新了我的问题。【参考方案3】:

MaxListenersExceededWarning:可能的 EventEmitter 内存泄漏 检测到。 11 消息列表添加。采用 emitter.setMaxListeners() 增加限制

默认情况下,最多可以为任何单个事件注册 10 个侦听器,我们得到 11 Ohno

// Change to 80 or 150 whatever and see what happens
require('events').EventEmitter.prototype._maxListeners = 70;
require('events').defaultMaxListeners = 70;

  process.on('warning', function (err) 
    if ( 'MaxListenersExceededWarning' == err.name ) 
      console.log('o kurwa');
      // write to log function
      process.exit(1); // its up to you what then in my case script was hang

    
  );

【讨论】:

初始要求对我有用!!内存泄漏会有什么问题吗? First require 也为我做了。我正在使用 material-ui DataGrid,当我添加第 11 列时,我得到了一个类似的错误,部分是:11 colResizing: start listeners added。我觉得可能有一种方法可以直接指向“colResizing:start”侦听器组并具体告诉它我的列号(超过十个时)。设置为 11 解决了我的问题。【参考方案4】:

这是在 React 组件中添加和删除事件侦听器的推荐方法 - 使用 LifeCycle 方法。

import  Component  from 'react';

class Example extends Component 
  constructor(props) 
   super(props);

   this.state = 
    windowWidth: window.innderWidth,
   ;
  

  componentDidMount() 
    window.addEventListener('resize', this.handleResize);
  

  componentWillUnmount() 
    window.removeEventListener('resize', this.handleResize);
  

  handleResize = () => 
    this.setState( windowWidth: window.innerWidth );
  

  render() 
    return (
      <div>
        Current window width: this.state.windowWidth
      </div>
    );
  

请务必记住,window 位于全局执行上下文中。因此,每次添加事件侦听器时,都会要求全局范围

    实例化另一个侦听器。 通过引用使用全局内存跟踪该侦听器 - 在本例中为 resize 继续跟踪听众,直到被告知不要这样做。

如果您从未告诉全局范围删除这些侦听器,那么由您的浏览器设置分配的全局内存将慢慢蒸发并导致您的浏览器和应用程序崩溃,或者如果已经在生产环境中,客户端的浏览器会崩溃。当他们操作全局内存时,必须非常小心和非常清楚。

如果您想了解(如果您还没有)为什么在 React 组件的工作中使用生命周期方法,我强烈建议您查看 here 在 React 的 Reconciliation 生命周期中。一个人不能准确地称自己为 “React 开发者”,也不能非常熟悉 Reconciliation

注意 该组件使用 babel 转译部分代码:import,并分配自定义方法 handleResize,仅使用箭头函数。如果您在设置环境方面需要帮助,可以参考this blog post 我写的应该可以理解。

祝你好运。

【讨论】:

您好,首先感谢您的简短回答,但我很确定,我重新检查了我已清除所有事件侦听器。但我仍然收到此错误(node:7732) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 message lis teners added. Use emitter.setMaxListeners() to increase limit ,我使用 mysql 作为我的数据库 @Nane 错误不会骗你。它们是永远不会错的合乎逻辑的论点。因此,尽管我确定您相信您已清除所有内容,但错误表明您没有。祝你好运! @Nane 此资源可用于查找内存泄漏。 developers.google.com/web/tools/chrome-devtools/memory-problems/… 我用收到此警告的确切行更新了我的问题。

以上是关于MaxListenersExceededWarning:检测到可能的 EventEmitter 内存泄漏。添加了 11 个消息列表。使用emitter.setMaxListeners() 增加限制的主要内容,如果未能解决你的问题,请参考以下文章