私はこれが重複したソリューションとしてフラグを立てる可能性があることを知っていますが、スタックオーバーフローのソリューションは私のために働いていません。
問題:
(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)})
})
}
});
};
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))
});
}
});
};
組み込みの events
_node.js
_ のモジュール( でコンパイルすると、そのバージョンがフロントエンドアプリにバンドルされますwebpack
または browserify
)は、コードについていくつかの仮定を行います。いつか、どこかで、誰かがX
個のリスナーを登録していたら、確かにメモリリークがあると判断しました。そして時々それは正しく、漏れを見つけに行くように正しく思い出させます。
私はこの警告を何度も受け取りましたが、通常は2つの特定の理由のみで、どちらにも簡単な解決策があります
コンポーネントは、イベントリスナとしてコンポーネントメソッドを使用し、登録するときにバインドしているように見える場合があります。
_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は、追加した関数とは異なります。その結果、追加された関数は追加され続け(悪口)、実際に実際のメモリリークが発生します。
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
}
}
_
時々、宿題を本当にし、必要に応じてリスナーを早めにバインドしたことを再確認し、適切な場所でそれらをすべて削除しました。次に、よく見ると、次のようなことをしていることがわかります。
_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制限を吹き飛ばします。
eventemitter3
_ を使用するように切り替えます宿題をして、すべてのことを再確認し、(設計上!)多くのイベントリスナーを登録していると判断した場合、最も簡単な解決策は _eventemitter3
_ 、これはノードのevents
モジュールのドロップイン置換です。ただし、より高速で、ブラウザと互換性があり、は最大リスナー制限を設定しません。
使用方法は、組み込みのevents
モジュールと同じです。
_const EventEmitter = require('eventemitter3')
const emitter = new EventEmitter()
_
これは、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コンポーネントの動作)のライフサイクルメソッドについて、Reactで here を確認することを強くお勧めしますReconciliationライフサイクル。 "React Developer"を正確に呼び出すことはできず、Reconciliationに精通していない。
注このコンポーネントはbabelを使用してコードの一部をトランスパイルします:import
、および割り当てられたカスタムメソッドhandleResize
のみ矢印関数を使用します。環境のセットアップに支援が必要な場合は、 このブログ投稿 を参照してください。
がんばろう。