fix(exec): clean up PTY resources to prevent FD leaks
Add proper cleanup of node-pty resources when PTY process exits: - Dispose onData and onExit listeners - Call pty.kill() to release file descriptors This prevents gradual FD accumulation that leads to EBADF errors after extended gateway uptime. Closes #4102
This commit is contained in:
parent
6372242da7
commit
a0d1af93e4
@ -79,11 +79,13 @@ const APPROVAL_SLUG_LENGTH = 8;
|
||||
|
||||
type PtyExitEvent = { exitCode: number; signal?: number };
|
||||
type PtyListener<T> = (event: T) => void;
|
||||
type PtyDisposable = { dispose: () => void };
|
||||
type PtyHandle = {
|
||||
pid: number;
|
||||
write: (data: string | Buffer) => void;
|
||||
onData: (listener: PtyListener<string>) => void;
|
||||
onExit: (listener: PtyListener<PtyExitEvent>) => void;
|
||||
onData: (listener: PtyListener<string>) => PtyDisposable;
|
||||
onExit: (listener: PtyListener<PtyExitEvent>) => PtyDisposable;
|
||||
kill: (signal?: string) => void;
|
||||
};
|
||||
type PtySpawn = (
|
||||
file: string,
|
||||
@ -361,6 +363,8 @@ async function runExecProcess(opts: {
|
||||
let child: ChildProcessWithoutNullStreams | null = null;
|
||||
let pty: PtyHandle | null = null;
|
||||
let stdin: SessionStdin | undefined;
|
||||
let ptyDataDisposable: PtyDisposable | null = null;
|
||||
let ptyExitDisposable: PtyDisposable | null = null;
|
||||
|
||||
if (opts.sandbox) {
|
||||
const { child: spawned } = await spawnWithFallback({
|
||||
@ -601,7 +605,7 @@ async function runExecProcess(opts: {
|
||||
|
||||
if (pty) {
|
||||
const cursorResponse = buildCursorPositionResponse();
|
||||
pty.onData((data) => {
|
||||
ptyDataDisposable = pty.onData((data) => {
|
||||
const raw = data.toString();
|
||||
const { cleaned, requests } = stripDsrRequests(raw);
|
||||
if (requests > 0) {
|
||||
@ -664,10 +668,18 @@ async function runExecProcess(opts: {
|
||||
};
|
||||
|
||||
if (pty) {
|
||||
pty.onExit((event) => {
|
||||
ptyExitDisposable = pty.onExit((event) => {
|
||||
const rawSignal = event.signal ?? null;
|
||||
const normalizedSignal = rawSignal === 0 ? null : rawSignal;
|
||||
handleExit(event.exitCode ?? null, normalizedSignal);
|
||||
// Clean up PTY resources to prevent FD leaks
|
||||
try {
|
||||
ptyDataDisposable?.dispose();
|
||||
ptyExitDisposable?.dispose();
|
||||
pty.kill();
|
||||
} catch {
|
||||
// Ignore cleanup errors
|
||||
}
|
||||
});
|
||||
} else if (child) {
|
||||
child.once("close", (code, exitSignal) => {
|
||||
|
||||
6
src/types/lydell-node-pty.d.ts
vendored
6
src/types/lydell-node-pty.d.ts
vendored
@ -1,11 +1,13 @@
|
||||
declare module "@lydell/node-pty" {
|
||||
export type PtyExitEvent = { exitCode: number; signal?: number };
|
||||
export type PtyListener<T> = (event: T) => void;
|
||||
export type PtyDisposable = { dispose: () => void };
|
||||
export type PtyHandle = {
|
||||
pid: number;
|
||||
write: (data: string | Buffer) => void;
|
||||
onData: (listener: PtyListener<string>) => void;
|
||||
onExit: (listener: PtyListener<PtyExitEvent>) => void;
|
||||
onData: (listener: PtyListener<string>) => PtyDisposable;
|
||||
onExit: (listener: PtyListener<PtyExitEvent>) => PtyDisposable;
|
||||
kill: (signal?: string) => void;
|
||||
};
|
||||
|
||||
export type PtySpawn = (
|
||||
|
||||
Loading…
Reference in New Issue
Block a user