TL;DR — クイックフィックス
この警告は、単一のEventEmitterの同じイベントに10個以上のリスナーが登録されたときに発生します。Node.jsは、偶発的なメモリリークを検出するためのデフォルトの安全上限として10を設定しています。
対処方法は、なぜそれほど多くのリスナーがあるかによって異なります:
- 意図的に多くのリスナーが必要な場合 →
emitter.setMaxListeners(n)で上限を引き上げる - ループ内やリクエストごとにリスナーを追加している場合 → 本物のリークです — 上限を上げるのではなく、コードを修正してください
この警告が発生する原因
表示される正確なメッセージ:
(node:1234) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 listeners added. Use emitter.setMaxListeners() to increase limit
Node.jsは、単一のエミッターに同じイベントに対してdefaultMaxListeners(デフォルト:10)を超えるリスナーが登録されたときにこれを記録します。よくある原因:
- HTTPリクエストのたびにリスナーを登録し、一度も削除しない
- 繰り返し実行される関数内(例えば、すべてのルートで起動するミドルウェア)で
emitter.on()を呼び出す - ライブラリ(
readline、socket.io、chokidar)が内部でリスナーを登録する一方で、リクエストごとに新しいインスタンスを作成している - クリーンアップ時に
removeListener()やoff()を呼び忘れる
ステップ1 — どのエミッターがリークしているか特定する
アプリを実行してスタックトレースを確認します。Node.js 10以降は自動的に出力します:
(node:1234) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 listeners added to [EventEmitter].
at EventEmitter.addListener (node:events:596:17)
at Server.<anonymous> (/app/server.js:42:10) <-- あなたのファイル
...
そのファイルと行番号が、リスナーが追加されている場所です。開いて、コールバック、ループ、またはリクエストハンドラー内にあるかどうか確認してください。
現在何個のリスナーがあるか不明な場合は、出力して確認できます:
console.log(emitter.listenerCount('data')); // 'data'リスナーの数は?
ステップ2 — 本物のリークを修正する(ほとんどの場合)
ほとんどの場合、問題はシンプルです:何度も実行される関数内でリスナーを追加しており、それが一度も削除されていません。登録処理をその関数の外に移動するか、使い終わったらリスナーを削除してください。
悪いパターン — 呼び出しのたびにリスナーが追加される
// server.js
app.get('/stream', (req, res) => {
// バグ: リクエストのたびに新しいリスナーが追加され、削除されない
process.on('exit', () => res.end());
});
11回のリクエスト後に警告が発生します。何千回ものリクエストの後には、メモリリークになります。
修正済み — 使い終わったらリスナーを削除する
app.get('/stream', (req, res) => {
const cleanup = () => res.end();
process.once('exit', cleanup); // 'once'は発火後に自動的に自分を削除する
res.on('close', () => {
process.removeListener('exit', cleanup); // クライアントが先に切断した場合の明示的なクリーンアップ
});
});
一度だけ発生するイベントにはon()の代わりにonce()を使う
// on()はリスナーをずっと生かし続ける
emitter.on('finish', handleFinish);
// once()は発火後に自分を削除する — 手動クリーンアップ不要
emitter.once('finish', handleFinish);
この一つの変更で、ExpressやKoaアプリのリスナーリーク報告の大半が解決されます。
リクエストごとに新しいインスタンスを作成する場合(readlineやsocket.ioでよくある)
// 悪い例: リクエストごとに新しいreadlineインターフェースを作成
app.post('/process', (req, res) => {
const rl = readline.createInterface({ input: req });
rl.on('line', processLine);
// rlがクローズされない — リクエストごとにリスナーが積み重なる
});
// 良い例: 使い終わったらクローズする
app.post('/process', (req, res) => {
const rl = readline.createInterface({ input: req });
rl.on('line', processLine);
rl.on('close', () => res.json({ ok: true }));
req.on('end', () => rl.close()); // クリーンアップを確実に行う
});
ステップ3 — 本当に多くのリスナーが必要な場合のみ上限を引き上げる
すべてのリスナーが正当な場合もあります — 20人のサブスクライバーを持つpub/subチャンネルや、プラグインシステム内の共有イベントバスなど。そのような場合は、その特定のエミッターの上限を引き上げてください:
const EventEmitter = require('events');
const emitter = new EventEmitter();
// このエミッターに最大30個のリスナーを許可する
emitter.setMaxListeners(30);
emitter.on('message', handler1);
emitter.on('message', handler2);
// ... 最大30個まで安全に使用可能
グローバルオプションもありますが、慎重に使用してください:
const EventEmitter = require('events');
EventEmitter.defaultMaxListeners = 20;
**警告:**グローバルデフォルトを引き上げると、同じプロセス内の他の場所にある本物のリークが隠れてしまいます。できる限り、エミッターごとのsetMaxListeners()を使用してください。
ステップ4 — 安全が確認済みのサードパーティコードの警告を抑制する
AWS SDKや一部のsocket.io内部など、一部のライブラリは正しくクリーンアップしているにもかかわらず警告をトリガーすることがあります。上限ロジックを変更せずに、特定のエミッターでこれを抑制できます:
// 0 = リスナー数無制限、警告を抑制
emitter.setMaxListeners(0);
何がリスナーを登録していて、なぜなのかを確実に把握している場合にのみこれを使用してください。これはミュートボタンであり、修正ではありません。
確認 — 修正が機能したことを確認する
- アプリを再起動してコンソールを監視します — 警告が消えているはずです。
- 警告をトリガーしたコードパスを再現します:負荷をシミュレートし、いくつかのリクエストを送信し、ルートをストレステストします。
- 開発中にサニティチェックを追加します:
setInterval(() => {
const count = emitter.listenerCount('data');
if (count > 5) console.warn(`High listener count on 'data': ${count}`);
}, 5000);
- 本番環境では、プログラムで警告をキャッチしてモニタリングスタックに送信します:
process.on('warning', (warning) => {
if (warning.name === 'MaxListenersExceededWarning') {
console.error('Listener leak detected:', warning.message, warning.stack);
// Datadog、Sentryなどに転送する
}
});
まとめ
- この警告は、1つのエミッターの1つのイベントに11個以上のリスナーが登録されていることを意味します
- 99%の場合、本物のバグです — ループ内やリクエストごとのハンドラー内でリスナーが追加され、クリーンアップされていない
- 一度だけ発生するイベントには
once()を使い、明示的なクリーンアップにはremoveListener()/off()を使い、ストリームやインターフェースは使い終わったら必ずクローズしてください setMaxListeners(n)を呼び出すのは、意図的に多くのリスナーが本当に必要な場合のみにしてください — 警告を消すためだけに使用しないでください

