Skip to content

Commit a991173

Browse files
authored
fix: properly close PTY sessions during server shutdown (#976)
Add closeAllSessions() method to terminal and PTY services to ensure all terminal sessions are terminated during server shutdown. This prevents orphan PTY processes that nodemon and similar tools would detect as lingering sub-processes. The cleanup handler now: - Guards against duplicate cleanup with cleanupInProgress flag - Closes all PTY sessions before disposing other resources - Uses a 5-second timeout to force exit if cleanup hangs - Properly awaits all async cleanup operations
1 parent ea494db commit a991173

File tree

3 files changed

+50
-7
lines changed

3 files changed

+50
-7
lines changed

src/cli/server.ts

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -97,16 +97,41 @@ const mockWindow: BrowserWindow = {
9797
console.log(`Server is running on ${server.baseUrl}`);
9898

9999
// Cleanup on shutdown
100-
const cleanup = () => {
100+
let cleanupInProgress = false;
101+
const cleanup = async () => {
102+
if (cleanupInProgress) return;
103+
cleanupInProgress = true;
104+
101105
console.log("Shutting down server...");
102-
void lockfile
103-
.release()
104-
.then(() => server.close())
105-
.then(() => process.exit(0));
106+
107+
// Force exit after timeout if cleanup hangs
108+
const forceExitTimer = setTimeout(() => {
109+
console.log("Cleanup timed out, forcing exit...");
110+
process.exit(1);
111+
}, 5000);
112+
113+
try {
114+
// Close all PTY sessions first (these are the "sub-processes" nodemon sees)
115+
serviceContainer.terminalService.closeAllSessions();
116+
117+
// Dispose background processes
118+
await serviceContainer.dispose();
119+
120+
// Release lockfile and close server
121+
await lockfile.release();
122+
await server.close();
123+
124+
clearTimeout(forceExitTimer);
125+
process.exit(0);
126+
} catch (err) {
127+
console.error("Cleanup error:", err);
128+
clearTimeout(forceExitTimer);
129+
process.exit(1);
130+
}
106131
};
107132

108-
process.on("SIGINT", cleanup);
109-
process.on("SIGTERM", cleanup);
133+
process.on("SIGINT", () => void cleanup());
134+
process.on("SIGTERM", () => void cleanup());
110135
})().catch((error) => {
111136
console.error("Failed to initialize server:", error);
112137
process.exit(1);

src/node/services/ptyService.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,16 @@ export class PTYService {
381381
sessionIds.forEach((id) => this.closeSession(id));
382382
}
383383

384+
/**
385+
* Close all terminal sessions.
386+
* Called during server shutdown to prevent orphan PTY processes.
387+
*/
388+
closeAllSessions(): void {
389+
const sessionIds = Array.from(this.sessions.keys());
390+
log.info(`Closing all ${sessionIds.length} terminal session(s)`);
391+
sessionIds.forEach((id) => this.closeSession(id));
392+
}
393+
384394
/**
385395
* Get all sessions for debugging
386396
*/

src/node/services/terminalService.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,14 @@ export class TerminalService {
545545
this.ptyService.closeWorkspaceSessions(workspaceId);
546546
}
547547

548+
/**
549+
* Close all terminal sessions.
550+
* Called during server shutdown to prevent orphan PTY processes.
551+
*/
552+
closeAllSessions(): void {
553+
this.ptyService.closeAllSessions();
554+
}
555+
548556
private cleanup(sessionId: string) {
549557
this.outputEmitters.delete(sessionId);
550558
this.exitEmitters.delete(sessionId);

0 commit comments

Comments
 (0)