Skip to content

ConPTY kill path leaks the conin socket, one handle per pty #947

Description

Summary

On Windows with ConPTY (useConpty: true, useConptyDll: false), each pty leaks one PipeWrap handle for its lifetime of the host process. The conin socket is never destroyed.

The ConPTY DLL path a few lines away does destroy it, so this looks like an asymmetry rather than a deliberate design.

Where

lib/windowsPtyAgent.js, in kill().

The useConpty && !useConptyDll branch marks both sockets unreadable and disposes the conout worker:

this._inSocket.readable = false;
this._outSocket.readable = false;
this._getConsoleProcessList().then(...);
this._ptyNative.kill(this._pty, this._useConptyDll);
this._conoutSocketWorker.dispose();

The useConptyDll branch does destroy it:

this._inSocket.destroy();
this._ptyNative.kill(this._pty, this._useConptyDll);

_inSocket.destroy() is never called on the first path.

Reproduction

node-pty 1.1.0, Node 26.0.0, Windows 11 Pro 26200 x64.

Spawn N ptys, wait for each to be ready, kill it, wait for exit, then count PipeWrap entries from process.getActiveResourcesInfo() after things settle.

sessions: 3    baseline PipeWrap=1    8s after: PipeWrap=5
sessions: 30   baseline PipeWrap=1    8s after: PipeWrap=32

Linear, roughly one handle per pty. Identical whether the pty is killed or exits on its own.

Destroying the socket from outside, with everything else unchanged:

leave    30 sessions:  PipeWrap=32
destroy  30 sessions:  PipeWrap=2

The remaining two are the last session still settling.

Impact

For a short-lived process this is invisible. For a long-running host that spawns and disposes ptys repeatedly, which is our case, it accumulates without bound.

Workaround

Reaching into the agent from outside after exit:

const socket = term._agent && term._agent.inSocket;
if (socket && typeof socket.destroy === 'function' && !socket.destroyed) {
  socket.destroy();
}

Happy to open a PR adding this._inSocket.destroy() to the ConPTY branch if that is the fix you would want.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions