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 PtyExitEvent = { exitCode: number; signal?: number };
|
||||||
type PtyListener<T> = (event: T) => void;
|
type PtyListener<T> = (event: T) => void;
|
||||||
|
type PtyDisposable = { dispose: () => void };
|
||||||
type PtyHandle = {
|
type PtyHandle = {
|
||||||
pid: number;
|
pid: number;
|
||||||
write: (data: string | Buffer) => void;
|
write: (data: string | Buffer) => void;
|
||||||
onData: (listener: PtyListener<string>) => void;
|
onData: (listener: PtyListener<string>) => PtyDisposable;
|
||||||
onExit: (listener: PtyListener<PtyExitEvent>) => void;
|
onExit: (listener: PtyListener<PtyExitEvent>) => PtyDisposable;
|
||||||
|
kill: (signal?: string) => void;
|
||||||
};
|
};
|
||||||
type PtySpawn = (
|
type PtySpawn = (
|
||||||
file: string,
|
file: string,
|
||||||
@ -361,6 +363,8 @@ async function runExecProcess(opts: {
|
|||||||
let child: ChildProcessWithoutNullStreams | null = null;
|
let child: ChildProcessWithoutNullStreams | null = null;
|
||||||
let pty: PtyHandle | null = null;
|
let pty: PtyHandle | null = null;
|
||||||
let stdin: SessionStdin | undefined;
|
let stdin: SessionStdin | undefined;
|
||||||
|
let ptyDataDisposable: PtyDisposable | null = null;
|
||||||
|
let ptyExitDisposable: PtyDisposable | null = null;
|
||||||
|
|
||||||
if (opts.sandbox) {
|
if (opts.sandbox) {
|
||||||
const { child: spawned } = await spawnWithFallback({
|
const { child: spawned } = await spawnWithFallback({
|
||||||
@ -601,7 +605,7 @@ async function runExecProcess(opts: {
|
|||||||
|
|
||||||
if (pty) {
|
if (pty) {
|
||||||
const cursorResponse = buildCursorPositionResponse();
|
const cursorResponse = buildCursorPositionResponse();
|
||||||
pty.onData((data) => {
|
ptyDataDisposable = pty.onData((data) => {
|
||||||
const raw = data.toString();
|
const raw = data.toString();
|
||||||
const { cleaned, requests } = stripDsrRequests(raw);
|
const { cleaned, requests } = stripDsrRequests(raw);
|
||||||
if (requests > 0) {
|
if (requests > 0) {
|
||||||
@ -664,10 +668,18 @@ async function runExecProcess(opts: {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (pty) {
|
if (pty) {
|
||||||
pty.onExit((event) => {
|
ptyExitDisposable = pty.onExit((event) => {
|
||||||
const rawSignal = event.signal ?? null;
|
const rawSignal = event.signal ?? null;
|
||||||
const normalizedSignal = rawSignal === 0 ? null : rawSignal;
|
const normalizedSignal = rawSignal === 0 ? null : rawSignal;
|
||||||
handleExit(event.exitCode ?? null, normalizedSignal);
|
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) {
|
} else if (child) {
|
||||||
child.once("close", (code, exitSignal) => {
|
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" {
|
declare module "@lydell/node-pty" {
|
||||||
export type PtyExitEvent = { exitCode: number; signal?: number };
|
export type PtyExitEvent = { exitCode: number; signal?: number };
|
||||||
export type PtyListener<T> = (event: T) => void;
|
export type PtyListener<T> = (event: T) => void;
|
||||||
|
export type PtyDisposable = { dispose: () => void };
|
||||||
export type PtyHandle = {
|
export type PtyHandle = {
|
||||||
pid: number;
|
pid: number;
|
||||||
write: (data: string | Buffer) => void;
|
write: (data: string | Buffer) => void;
|
||||||
onData: (listener: PtyListener<string>) => void;
|
onData: (listener: PtyListener<string>) => PtyDisposable;
|
||||||
onExit: (listener: PtyListener<PtyExitEvent>) => void;
|
onExit: (listener: PtyListener<PtyExitEvent>) => PtyDisposable;
|
||||||
|
kill: (signal?: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PtySpawn = (
|
export type PtySpawn = (
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user