web-dev-qa-db-ja.com

MaxListenersExceededWarning:可能性のあるEventEmitterメモリリークが検出されました。 11個のメッセージリストが追加されました。 emulator.setMaxListeners()を使用して制限を増やします

私はこれが重複したソリューションとしてフラグを立てる可能性があることを知っていますが、スタックオーバーフローのソリューションは私のために働いていません。

問題:

(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) => {

        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))
            });
            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)})
            })


        }
    });
};
8
Nane

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) => {
    //COMMENT : 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))
            });
            // COMMENT : This is where you are subscribing for each and every socket conected to your server
            sub.on('message',(ch,msg) => { // this is the Exact line where I am getting this error


//COMMENT : Where as you are emiting message on socket manager not on 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))
            });
            


        }
    });
};
5
Rohit Harkhani

組み込みの events_node.js_ のモジュール( でコンパイルすると、そのバージョンがフロントエンドアプリにバンドルされますwebpack または browserify )は、コードについていくつかの仮定を行います。いつか、どこかで、誰かがX個のリスナーを登録していたら、確かにメモリリークがあると判断しました。そして時々それは正しく、漏れを見つけに行くように正しく思い出させます。

私はこの警告を何度も受け取りましたが、通常は2つの特定の理由のみで、どちらにも簡単な解決策があります


問題1:バインドされたイベントリスナー関数の不一致

コンポーネントは、イベントリスナとしてコンポーネントメソッドを使用し、登録するときにバインドしているように見える場合があります

_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_ が毎回新しい関数を作成することです。 removeは、追加した関数とは異なります。その結果、追加された関数は追加され続け(悪口)、実際に実際のメモリリークが発生します。

解決策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の要素があったとします。これは、(設計により)MyComponentの250のインスタンスをレンダリングし、各インスタンスが別の一意のイベントリスナーを登録していることを意味します。

恐れるな!これは完全に有効なコードであり、メモリリークはありません。しかし、それは、誰か、いつか、どこかであなたを保護するために勝手に決定したmax-listeners制限を吹き飛ばします。

解決策2: _eventemitter3_ を使用するように切り替えます

宿題をして、すべてのことを再確認し、(設計上!)多くのイベントリスナーを登録していると判断した場合、最も簡単な解決策は _eventemitter3_ 、これはノードのeventsモジュールのドロップイン置換です。ただし、より高速で、ブラウザと互換性があり、は最大リスナー制限を設定しません。

使用方法は、組み込みのeventsモジュールと同じです。

_const EventEmitter = require('eventemitter3')
const emitter = new EventEmitter()
_
4
flintinatux

これは、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で here を確認することを強くお勧めしますReconciliationライフサイクル。 "React Developer"を正確に呼び出すことはできず、Reconciliationに精通していない

このコンポーネントはbabelを使用してコードの一部をトランスパイルします:import、および割り当てられたカスタムメソッドhandleResizeのみ矢印関数を使用します。環境のセットアップに支援が必要な場合は、 このブログ投稿 を参照してください。

がんばろう。

0
Tobiah Rex