mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 16:02:32 +00:00
* fix(pty): release ptmx fd on natural exit + defuse SIGHUP-to-recycled-pid Daemons accumulated ptmx fds over time because node-pty's UnixTerminal only releases the master fd when destroy() runs. On the natural-exit path (the common case — user closes a tab, shell runs `exit`) nothing ever calls destroy(), so the fd leaks until GC. On macOS this eventually hits kern.tty.ptmx_max=511 and all new terminals fail to spawn. Fix: release the fd synchronously on every teardown path (natural exit, explicit kill, stale SSH spawn, daemon shutdown) and close the concurrent SIGHUP-to-recycled-pid hazard inside node-pty's UnixTerminal.destroy(). - src/main/daemon/pty-subprocess.ts: synchronous POSIX proc.kill neutralization inside proc.onExit; dead guards on forceKill/signal so they never target a reaped-and-possibly-recycled pid - src/main/daemon/session.ts: new disposeSubprocess() for already- exited sessions (fd release only, no SIGKILL) — avoids sending SIGKILL to a recycled pid during daemon shutdown - src/main/daemon/terminal-host.ts: dispose loop routes on isAlive — live sessions get forceKillAndDisposeSubprocess (SIGKILL + fd release), exited sessions get disposeSubprocess (fd release only) - src/main/providers/local-pty-provider.ts: same POSIX kill neutralization at top of onExit for the legacy local path - src/relay/pty-handler.ts: same neutralization in wireAndStore; disposed flag guards all public entry points; dispose() uses SIGKILL (not SIGTERM) before destroy since the relay is exiting; killTimer fallback + immediate-shutdown + stale-spawn cleanup all call disposeManagedPty + ptys.delete so wedged children (D-state, bad NFS) can't leak map entries against the 50-PTY cap Windows is exempt everywhere — WindowsTerminal.destroy IS a kill() call internally (closes the ConPTY agent), so neutralizing would turn destroy into a no-op and leak the agent. See docs/fix-pty-fd-leak.md for the full design. Co-authored-by: Orca <help@stably.ai> * fix(pty): patch node-pty native off-by-one leaking /dev/ptmx per spawn node-pty 1.1.0's pty_posix_spawn on macOS walks low_fds[0..2] in an allocation loop that breaks at the first fd >= STDERR_FILENO, then cleans up via `for (; count > 0; count--) close(low_fds[count])`. In the typical case (break at count=0) the cleanup body never runs and low_fds[0] — a /dev/ptmx handle — leaks per spawn. Fixed upstream in microsoft/node-pty af053f2 (PR #882), not in any 1.1.0 release. Backport the 3-line cleanup-loop fix as a pnpm patch. E2E validated against a dev daemon: 200 spawn/kill cycles kept the daemon's ptmx fd count flat at baseline; prior runs reproduced linear 1-per-spawn growth. Also documents the native root cause as a status addendum in docs/fix-pty-fd-leak.md — the JS-side destroy() discipline previously landed is still load-bearing for the SIGHUP-to-recycled-pid hazard and for synchronous fd release on daemon shutdown. Co-authored-by: Orca <help@stably.ai> * fix(pty): capture stable kill spy ref in pty.test.ts destroyPtyProcess reassigns proc.kill = () => {} on POSIX to defuse the SIGHUP-to-recycled-pid hazard (see docs/fix-pty-fd-leak.md). After that reassignment, proc.kill.mock is undefined and the assertions crashed in CI. Capture a stable reference to the vi.fn() before it gets reassigned. Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Orca <help@stably.ai>
91 lines
3.4 KiB
Diff
91 lines
3.4 KiB
Diff
diff --git a/binding.gyp b/binding.gyp
|
|
index 5f63978b07ab50aaf7523219a2170ec737a6b5db..b3309a07ef99dea7967d7bdd04b9fc3500acacae 100644
|
|
--- a/binding.gyp
|
|
+++ b/binding.gyp
|
|
@@ -5,9 +5,6 @@
|
|
],
|
|
'conditions': [
|
|
['OS=="win"', {
|
|
- 'msvs_configuration_attributes': {
|
|
- 'SpectreMitigation': 'Spectre'
|
|
- },
|
|
'msvs_settings': {
|
|
'VCCLCompilerTool': {
|
|
'AdditionalOptions': [
|
|
diff --git a/deps/winpty/src/winpty.gyp b/deps/winpty/src/winpty.gyp
|
|
index 1ac5758bedd8cf54f32280dea4e4aeb5afdee30d..e619813759c6f14694838bdfbd0ea5f8360130ef 100644
|
|
--- a/deps/winpty/src/winpty.gyp
|
|
+++ b/deps/winpty/src/winpty.gyp
|
|
@@ -10,7 +10,7 @@
|
|
# make -j4 CXX=i686-w64-mingw32-g++ LDFLAGS="-static -static-libgcc -static-libstdc++"
|
|
|
|
'variables': {
|
|
- 'WINPTY_COMMIT_HASH%': '<!(cmd /c "cd shared && GetCommitHash.bat")',
|
|
+ 'WINPTY_COMMIT_HASH%': '<!(cmd /c "cd shared && .\\GetCommitHash.bat")',
|
|
},
|
|
'target_defaults' : {
|
|
'defines' : [
|
|
@@ -22,7 +22,7 @@
|
|
'include_dirs': [
|
|
# Add the 'src/gen' directory to the include path and force gyp to
|
|
# run the script (re)generating the version header.
|
|
- '<!(cmd /c "cd shared && UpdateGenVersion.bat <(WINPTY_COMMIT_HASH)")',
|
|
+ '<!(cmd /c "cd shared && .\\UpdateGenVersion.bat <(WINPTY_COMMIT_HASH)")',
|
|
]
|
|
},
|
|
'targets' : [
|
|
@@ -40,9 +40,6 @@
|
|
'-lshell32',
|
|
'-luser32',
|
|
],
|
|
- 'msvs_configuration_attributes': {
|
|
- 'SpectreMitigation': 'Spectre'
|
|
- },
|
|
'msvs_settings': {
|
|
# Specify this setting here to override a setting from somewhere
|
|
# else, such as node's common.gypi.
|
|
@@ -142,9 +139,6 @@
|
|
'-ladvapi32',
|
|
'-luser32',
|
|
],
|
|
- 'msvs_configuration_attributes': {
|
|
- 'SpectreMitigation': 'Spectre'
|
|
- },
|
|
'msvs_settings': {
|
|
# Specify this setting here to override a setting from somewhere
|
|
# else, such as node's common.gypi.
|
|
diff --git a/lib/unixTerminal.js b/lib/unixTerminal.js
|
|
index 1ec12f796a822c78fba9ad7f6448c3987e325c23..cec8b67aef02f8199e5606a0d257088bf1865877 100644
|
|
--- a/lib/unixTerminal.js
|
|
+++ b/lib/unixTerminal.js
|
|
@@ -28,8 +28,12 @@ var native = utils_1.loadNativeModule('pty');
|
|
var pty = native.module;
|
|
var helperPath = native.dir + '/spawn-helper';
|
|
helperPath = path.resolve(__dirname, helperPath);
|
|
-helperPath = helperPath.replace('app.asar', 'app.asar.unpacked');
|
|
-helperPath = helperPath.replace('node_modules.asar', 'node_modules.asar.unpacked');
|
|
+if (!helperPath.includes('app.asar.unpacked')) {
|
|
+ helperPath = helperPath.replace('app.asar', 'app.asar.unpacked');
|
|
+}
|
|
+if (!helperPath.includes('node_modules.asar.unpacked')) {
|
|
+ helperPath = helperPath.replace('node_modules.asar', 'node_modules.asar.unpacked');
|
|
+}
|
|
var DEFAULT_FILE = 'sh';
|
|
var DEFAULT_NAME = 'xterm';
|
|
var DESTROY_SOCKET_TIMEOUT_MS = 200;
|
|
diff --git a/src/unix/pty.cc b/src/unix/pty.cc
|
|
index 7b4b9e1f990fbf95b51528bb56dc9717f5b87532..c17decbb7bc06f10d7b5d26317bd68fdc8c4e339 100644
|
|
--- a/src/unix/pty.cc
|
|
+++ b/src/unix/pty.cc
|
|
@@ -778,8 +778,8 @@ done:
|
|
posix_spawn_file_actions_destroy(&acts);
|
|
posix_spawnattr_destroy(&attrs);
|
|
|
|
- for (; count > 0; count--) {
|
|
- close(low_fds[count]);
|
|
+ for (size_t i = 0; i <= count && i < 3; i++) {
|
|
+ close(low_fds[i]);
|
|
}
|
|
}
|
|
#endif
|