Fix macOS daemon PTY kqueue descriptor leak (#16870)

* fix(daemon): release node-pty kqueue descriptors

* test: remove redundant wait from kqueue regression
This commit is contained in:
Brennan Benson
2026-10-30 11:42:31 -07:00
committed by GitHub
parent b83e76f01e
commit eb3379ff99
3 changed files with 39 additions and 12 deletions
+17 -9
View File
@@ -367,7 +367,15 @@ index 7b4b9e1f990fbf95b51528bb56dc9717f5b87532..381d59e11bf158d269335c8231b407fc
#endif
/* NSIG - macro for highest signal + 1, should be defined */
@@ -228,6 +267,9 @@ Napi::Value PtyGetProc(const Napi::CallbackInfo& info);
@@ -169,6 +208,7 @@ void SetupExitCallback(Napi::Env env, Napi::Function cb, pid_t pid) {
}
}
}
+ close(kq);
#else
while (true) {
errno = 0;
@@ -228,6 +268,9 @@ Napi::Value PtyGetProc(const Napi::CallbackInfo& info);
static int
pty_nonblock(int);
@@ -377,7 +385,7 @@ index 7b4b9e1f990fbf95b51528bb56dc9717f5b87532..381d59e11bf158d269335c8231b407fc
#if defined(__APPLE__)
static char *
pty_getproc(int);
@@ -237,13 +279,23 @@ pty_getproc(int, char *);
@@ -237,13 +280,23 @@ pty_getproc(int, char *);
#endif
#if defined(__APPLE__) || defined(__OpenBSD__)
@@ -402,7 +410,7 @@ index 7b4b9e1f990fbf95b51528bb56dc9717f5b87532..381d59e11bf158d269335c8231b407fc
#endif
struct DelBuf {
@@ -345,8 +397,8 @@ Napi::Value PtyFork(const Napi::CallbackInfo& info) {
@@ -345,8 +398,8 @@ Napi::Value PtyFork(const Napi::CallbackInfo& info) {
term->c_cc[VSTATUS] = 20;
#endif
@@ -413,7 +421,7 @@ index 7b4b9e1f990fbf95b51528bb56dc9717f5b87532..381d59e11bf158d269335c8231b407fc
// helperPath
std::string helper_path = info[9].As<Napi::String>();
@@ -367,14 +419,18 @@ Napi::Value PtyFork(const Napi::CallbackInfo& info) {
@@ -367,14 +420,18 @@ Napi::Value PtyFork(const Napi::CallbackInfo& info) {
argv[i + 3] = strdup(arg.c_str());
}
@@ -436,7 +444,7 @@ index 7b4b9e1f990fbf95b51528bb56dc9717f5b87532..381d59e11bf158d269335c8231b407fc
#else
int argc = argv_.Length();
int argl = argc + 2;
@@ -445,6 +501,9 @@ Napi::Value PtyFork(const Napi::CallbackInfo& info) {
@@ -445,6 +502,9 @@ Napi::Value PtyFork(const Napi::CallbackInfo& info) {
if (pty_nonblock(master) == -1) {
throw Napi::Error::New(napiEnv, "Could not set master fd to nonblocking.");
}
@@ -446,7 +454,7 @@ index 7b4b9e1f990fbf95b51528bb56dc9717f5b87532..381d59e11bf158d269335c8231b407fc
}
#endif
@@ -586,6 +645,23 @@ pty_nonblock(int fd) {
@@ -586,6 +646,23 @@ pty_nonblock(int fd) {
return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}
@@ -470,7 +478,7 @@ index 7b4b9e1f990fbf95b51528bb56dc9717f5b87532..381d59e11bf158d269335c8231b407fc
/**
* pty_getproc
* Taken from tmux.
@@ -684,15 +760,73 @@ pty_getproc(int fd, char *tty) {
@@ -684,15 +761,73 @@ pty_getproc(int fd, char *tty) {
#endif
#if defined(__APPLE__)
@@ -546,7 +554,7 @@ index 7b4b9e1f990fbf95b51528bb56dc9717f5b87532..381d59e11bf158d269335c8231b407fc
for (; count < 3; count++) {
low_fds[count] = posix_openpt(O_RDWR);
@@ -706,84 +840,499 @@ pty_posix_spawn(char** argv, char** env,
@@ -706,84 +841,499 @@ pty_posix_spawn(char** argv, char** env,
POSIX_SPAWN_SETSID;
*master = posix_openpt(O_RDWR);
if (*master == -1) {
@@ -1070,7 +1078,7 @@ index 7b4b9e1f990fbf95b51528bb56dc9717f5b87532..381d59e11bf158d269335c8231b407fc
/**
* Init
*/
@@ -793,6 +1342,11 @@ Napi::Object init(Napi::Env env, Napi::Object exports) {
@@ -793,6 +1343,11 @@ Napi::Object init(Napi::Env env, Napi::Object exports) {
exports.Set("open", Napi::Function::New(env, PtyOpen));
exports.Set("resize", Napi::Function::New(env, PtyResize));
exports.Set("process", Napi::Function::New(env, PtyGetProc));
+3 -3
View File
@@ -185,7 +185,7 @@ patchedDependencies:
i18next-cli@1.74.2: 2ee32eef742615cd133e1a6940f027deb3723a8430bb7953bba3a6769b0aac47
lint-staged@16.4.0: 7333b3837f80a7fbd045964db6d76ba4fc118e49134bdbabb00585b6b7b60673
monaco-editor@0.55.1: 53b71f7d8a7c00b76ffdb73ce83d18e31692f193f5fafa6a390e48f5b3fdd636
node-pty@1.1.0: f81acb44ac52265a6053af59e60128471f4655de39e64f8c47c916e0dc8c9416
node-pty@1.1.0: 98dadd30788d9235a1a28c4596078918f5936bd92804ca529ab1958f1a0fc278
oxlint-plugin-anti-slop@0.1.2: c10c871702365a0c2c59e9f1c5fb92df97cae5755d534c0a3d83c6368f6feafe
pdfjs-dist@6.3.289: 424fde648e16c965ce45c55bdda49ecb3a4aa992c856900714c841c3f13f8415
sprintf-js@1.1.3: 05316a8b01f155fe19a1e0cb4042c81d43d798a76755c407684e1199a18b1e08
@@ -234,7 +234,7 @@ importers:
version: 3.3.1
node-pty:
specifier: ^1.1.0
version: 1.1.0(patch_hash=f81acb44ac52265a6053af59e60128471f4655de39e64f8c47c916e0dc8c9416)
version: 1.1.0(patch_hash=98dadd30788d9235a1a28c4596078918f5936bd92804ca529ab1958f1a0fc278)
posthog-node:
specifier: ^5.52.5
version: 5.52.5
@@ -13756,7 +13756,7 @@ snapshots:
node-int64@0.4.0: {}
node-pty@1.1.0(patch_hash=f81acb44ac52265a6053af59e60128471f4655de39e64f8c47c916e0dc8c9416):
node-pty@1.1.0(patch_hash=98dadd30788d9235a1a28c4596078918f5936bd92804ca529ab1958f1a0fc278):
dependencies:
node-addon-api: 7.1.1
+19
View File
@@ -18,6 +18,12 @@ function currentOpenFdCount(): number {
.filter((line) => line.trim().length > 0).length
}
function currentKqueueFdCount(): number {
return execFileSync('lsof', ['-p', String(process.pid)], { encoding: 'utf8' })
.split('\n')
.filter((line) => line.includes(' KQUEUE ')).length
}
function getExistingSpawnHelper(): string {
const helperPath = getNodePtySpawnHelperCandidates().find((candidate) => existsSync(candidate))
expect(helperPath).toBeTruthy()
@@ -42,6 +48,19 @@ async function spawnExitingPty(index: number): Promise<void> {
const describeOnDarwin = process.platform === 'darwin' ? describe : describe.skip
describeOnDarwin('node-pty macOS spawn fd handling', () => {
// Forty serial real shell launches need a 15-second budget.
it('releases the child-exit kqueue fd across repeated pty churn', async () => {
const before = currentKqueueFdCount()
for (let i = 0; i < 40; i++) {
await spawnExitingPty(i)
}
const after = currentKqueueFdCount()
expect(after - before).toBe(0)
}, 15000)
it('does not leak revoked slave tty fds across exited pty spawns', async () => {
const before = currentRevokedFdCount()