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

IT技术 javascript mysql node.js reactjs
2021-04-12 07:12:30

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

问题

(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 代码是我在 Redis 中使用 socket.io 的问题。这是错误

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) });
      });
    }
  });
};
4个回答

事件发射的默认限制是 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));
      });
    }
  });
};

中的内置eventsmodulenode.js(如果您使用webpack编译,则其版本捆绑到您的前端应用程序中browserify)对您的代码做出一些假设。有时,在某个地方,有人认为如果您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

如果你决定你已经完成了你的功课,并仔细检查了所有的事情,并且(按照设计!)注册了大量的事件监听器,那么最简单的解决方案是切换到 using eventemitter3,这是一个直接替代node 的eventsmodule,除了速度更快且与浏览器兼容,并且不会为您设置最大侦听器限制。

用法就像内置eventsmodule:

const EventEmitter = require('eventemitter3')
const emitter = new EventEmitter()
这是一个简短的例子 谢谢。但我用我收到此警告的确切行更新了我的问题。
2021-05-27 07:12:30

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

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

这是在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是在全局执行上下文中。因此,每次添加事件侦听器时,您都是在要求全局范围

  1. 实例化另一个监听器。
  2. 通过引用使用全局内存跟踪该侦听器 - 在这种情况下 resize
  3. 继续跟踪听众,直到被告知不要这样做。

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

如果您想了解(如果你不这样做的话)为什么在生命周期方法作出react组件的工作,我会强烈建议你看看这里的阵营的和解生命周期。人们不能准确地称自己为“React 开发人员”,也不能对Reconciliation非常熟悉

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

祝你好运。

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