[darwin] pty_posix_spawn leaks the pty master+slave when posix_spawn fails, and one ptmx fd on every successful spawn
维护者通常 1 天内回复
还没有人认领这个 Issue。
评估
- 难度
- 4/5
- 预计耗时
- 3-5 天
- 新手友好度
- 68/100
- Issue 类型
- 缺陷
- 描述清晰度
- 描述清楚
- 活跃度
- 活跃
- 技术栈
- cpp, macos
调研方向
从 src/unix/pty.cc 中的 pty_posix_spawn 开始,检查其 low-fd 前导部分、错误返回、done: 路径以及 PtyFork 失败路径。使用提供的 oversized-argv 脚本复现失败和成功的 spawn,然后使用 lsof 验证 master、slave 和 low_fds 的所有描述符都已关闭,并确认现有的 spawn 行为仍然正常工作。
由索引模型根据 Issue 内容生成。
描述
Version: node-pty 1.1.0, macOS (arm64, also reproduced on macOS 26), Node 22 / Electron 42.
On darwin, pty_posix_spawn (src/unix/pty.cc) allocates a pty before spawning and never releases it on any error path — and its low-fd prologue leaks one fd even when everything succeeds.
Leak 1 — failed spawn leaks the master and the slave (2 pty devices per failure)
*master = posix_openpt(O_RDWR)(pty.cc:707) andslave = open(slave_pty_name, …)(pty.cc:725) have no matching parent-sideclose()anywhere: every earlyreturn(L709, 714, 722, 727, 733, 740) and thedone:path (L777) leave both fds open. Theposix_spawn_file_actions_addclosecalls at L749-750 apply only to the child.PtyForkthen throws (pty.cc:372-374,"posix_spawnp failed.") without cleanup, and since the JS ctor never receivesterm.fd, the caller cannot close anything either.
Leak 2 — successful spawn leaks one ptmx fd (off-by-one)
The low-fd prologue (L694-701) opens ptys until one lands at fd >= STDERR_FILENO, then breaks. Cleanup is:
for (; count > 0; count--) close(low_fds[count]);
In the common case the loop breaks with count == 0, so the body never runs and low_fds[0] leaks on every spawn. It also skips index 0 whenever count > 0, and reads low_fds[3] out of bounds if the prologue loop completes without breaking.
Reproduction
Forcing a real posix_spawn failure needs more than a nonexistent binary (on macOS argv[0] is the spawn-helper, which exists — the failure then happens in the child). E2BIG via an oversized argv works:
const pty = require('node-pty')
const huge = 'x'.repeat(3 * 1024 * 1024)
for (let i = 0; i < 25; i++) {
try { pty.spawn('/bin/echo', [huge], { cwd: '/tmp' }) } catch (e) { /* posix_spawnp failed. */ }
}
// lsof -p <pid>: 50 /dev/ptmx + 25 /dev/ttysNNN still open
Measured on macOS: 25 failed spawns → +50 /dev/ptmx fds, +25 /dev/ttys* fds, system-wide device count 84 → 134 (2 devices per failure). 25 successful spawns, each fully exited and destroy()ed, still left 25 /dev/ptmx fds open (1 device each).
Impact
macOS caps pty devices system-wide at kern.tty.ptmx_max (default 511). Because failures leak two devices each, exhaustion is self-amplifying: at the ceiling every spawn fails, every failure consumes two more devices, and the process never recovers. An Electron app of ours accumulated 479 open masters in a 31-minute session against 28 real terminals, after which every spawn on the machine failed until the process exited.
Suggested fix
- In
pty_posix_spawn, close*master(andslaveonce opened) on every error path, setting*master = -1; closeslavein the parent unconditionally afterposix_spawn, and close*mastertoo when*err != 0. - Track how many
low_fdswere actually opened and close all of them, e.g.size_t opened = count < 3 ? count + 1 : 3; for (size_t i = 0; i < opened; i++) if (low_fds[i] >= 0) close(low_fds[i]); - Ideally include the
posix_spawnerrno in the thrown message —"posix_spawnp failed."currently discards the one datum (EMFILE vs EAGAIN vs ENOENT) that would let applications diagnose this.
- 主要语言
- TypeScript
- 星标
- 2k
- 派生
- 337
- 平均合并
- 23 小时 33 分钟
- 30 天内合并 PR
- 2
环境准备
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
microsoft/node-pty 的其他 Issue
-
难度 2/5 1-3 小时 新手友好度 78/100
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 76/100
microsoft/node-pty#951 · 1 条评论 ·
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 82/100
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 76/100
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 76/100
维护者通常 1 天内回复
查看 microsoft/node-pty 的全部 Issue
相似的 Issue
-
needs:triage
难度 2/5 1-3 小时 新手友好度 84/100
维护者通常 1 天内回复
-
ai-discovered
难度 2/5 1-3 小时 新手友好度 83/100
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 88/100
jessepollak/home#1627 ·
维护者通常 1 天内回复
-
agent-canvas bug llm priority:low ready-for-dev
难度 2/5 1-3 小时 新手友好度 82/100
OpenHands/OpenHands#17806 · 3 条评论 ·
维护者通常 1 天内回复
-
bug
难度 2/5 1-3 小时 新手友好度 76/100
radius-project/ai-extensions#923 ·
维护者通常 1 天内回复