mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-14 23:31:13 -07:00
fix: prevent orphaned processes from consuming 100% CPU when terminal closes (#16965)
Co-authored-by: Tommaso Sciortino <sciortino@gmail.com>
This commit is contained in:
committed by
GitHub
parent
aa98cafca7
commit
b8d6041d42
@@ -10,12 +10,14 @@ import {
|
||||
Storage,
|
||||
shutdownTelemetry,
|
||||
isTelemetrySdkInitialized,
|
||||
ExitCodes,
|
||||
} from '@google/gemini-cli-core';
|
||||
import type { Config } from '@google/gemini-cli-core';
|
||||
|
||||
const cleanupFunctions: Array<(() => void) | (() => Promise<void>)> = [];
|
||||
const syncCleanupFunctions: Array<() => void> = [];
|
||||
let configForTelemetry: Config | null = null;
|
||||
let isShuttingDown = false;
|
||||
|
||||
export function registerCleanup(fn: (() => void) | (() => Promise<void>)) {
|
||||
cleanupFunctions.push(fn);
|
||||
@@ -33,6 +35,7 @@ export function resetCleanupForTesting() {
|
||||
cleanupFunctions.length = 0;
|
||||
syncCleanupFunctions.length = 0;
|
||||
configForTelemetry = null;
|
||||
isShuttingDown = false;
|
||||
}
|
||||
|
||||
export function runSyncCleanup() {
|
||||
@@ -100,6 +103,65 @@ async function drainStdin() {
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gracefully shuts down the process, ensuring cleanup runs exactly once.
|
||||
* Guards against concurrent shutdown from signals (SIGHUP, SIGTERM, SIGINT)
|
||||
* and TTY loss detection racing each other.
|
||||
*
|
||||
* @see https://github.com/google-gemini/gemini-cli/issues/15874
|
||||
*/
|
||||
async function gracefulShutdown(_reason: string) {
|
||||
if (isShuttingDown) {
|
||||
return;
|
||||
}
|
||||
isShuttingDown = true;
|
||||
|
||||
await runExitCleanup();
|
||||
process.exit(ExitCodes.SUCCESS);
|
||||
}
|
||||
|
||||
export function setupSignalHandlers() {
|
||||
process.on('SIGHUP', () => gracefulShutdown('SIGHUP'));
|
||||
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
|
||||
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
|
||||
}
|
||||
|
||||
export function setupTtyCheck(): () => void {
|
||||
let intervalId: ReturnType<typeof setInterval> | null = null;
|
||||
let isCheckingTty = false;
|
||||
|
||||
intervalId = setInterval(async () => {
|
||||
if (isCheckingTty || isShuttingDown) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (process.env['SANDBOX']) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!process.stdin.isTTY && !process.stdout.isTTY) {
|
||||
isCheckingTty = true;
|
||||
|
||||
if (intervalId) {
|
||||
clearInterval(intervalId);
|
||||
intervalId = null;
|
||||
}
|
||||
|
||||
await gracefulShutdown('TTY loss');
|
||||
}
|
||||
}, 5000);
|
||||
|
||||
// Don't keep the process alive just for this interval
|
||||
intervalId.unref();
|
||||
|
||||
return () => {
|
||||
if (intervalId) {
|
||||
clearInterval(intervalId);
|
||||
intervalId = null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export async function cleanupCheckpoints() {
|
||||
const storage = new Storage(process.cwd());
|
||||
await storage.initialize();
|
||||
|
||||
Reference in New Issue
Block a user