Skip to content

fix(server): remove lingering stdin listeners in _writeWithBackpressure#172

Open
kainstar wants to merge 1 commit into
dmtrKovalenko:mainfrom
kainstar:fix/server-stdin-listener-leak
Open

fix(server): remove lingering stdin listeners in _writeWithBackpressure#172
kainstar wants to merge 1 commit into
dmtrKovalenko:mainfrom
kainstar:fix/server-stdin-listener-leak

Conversation

@kainstar
Copy link
Copy Markdown

@kainstar kainstar commented Apr 30, 2026

Problem

When _writeWithBackpressure needs to wait for drain, it registers three once listeners on stdin:

stdin.once("drain", () => { ... resolve() });
stdin.once("error", (err) => { ... reject() });
stdin.once("close", () => { ... reject() });

When drain fires first, only the drain handler is auto-removed (by once). The error and close handlers are never removed — they linger on the stdin Socket indefinitely.

All writes to ODiffServer are serialized by writeLock, so listeners never pile up concurrently. Instead, they accumulate sequentially: every write that encounters backpressure and resolves via drain leaves behind one orphaned error listener and one orphaned close listener. After 11 such writes over the server's lifetime, the warning fires:

MaxListenersExceededWarning: Possible EventEmitter memory leak detected.
11 error listeners added to [Socket]. MaxListeners is 10.

Fix

Convert the anonymous callbacks to named functions so each handler can explicitly remove the other two when it settles:

const onDrain = () => {
  if (!settled) {
    settled = true;
    stdin.removeListener("error", onError);
    stdin.removeListener("close", onClose);
    resolve(undefined);
  }
};
// same pattern for onError and onClose
stdin.once("drain", onDrain);
stdin.once("error", onError);
stdin.once("close", onClose);

This ensures stdin's listener count stays bounded regardless of how many comparisons are run.

When `drain` fires first, the `once('error')` and `once('close')`
handlers were never removed from stdin, causing them to accumulate
across multiple `compareBuffers` calls and triggering Node.js
MaxListenersExceededWarning on the stdin Socket.

Fix by converting anonymous callbacks to named functions so that each
handler can explicitly remove the other two when it fires.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant