mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-13 21:32:56 -07:00
Merge branch 'main' into memory_usage3
This commit is contained in:
@@ -75,7 +75,6 @@ describe('process-utils', () => {
|
||||
|
||||
expect(mockProcessKill).toHaveBeenCalledWith(-1234, 'SIGKILL');
|
||||
});
|
||||
|
||||
it('should use escalation on Unix if requested', async () => {
|
||||
vi.mocked(os.platform).mockReturnValue('linux');
|
||||
const exited = false;
|
||||
@@ -87,6 +86,11 @@ describe('process-utils', () => {
|
||||
isExited,
|
||||
});
|
||||
|
||||
// flush microtasks
|
||||
await new Promise(process.nextTick);
|
||||
await new Promise(process.nextTick);
|
||||
await new Promise(process.nextTick);
|
||||
|
||||
// First call should be SIGTERM
|
||||
expect(mockProcessKill).toHaveBeenCalledWith(-1234, 'SIGTERM');
|
||||
|
||||
@@ -110,6 +114,11 @@ describe('process-utils', () => {
|
||||
isExited,
|
||||
});
|
||||
|
||||
// flush microtasks
|
||||
await new Promise(process.nextTick);
|
||||
await new Promise(process.nextTick);
|
||||
await new Promise(process.nextTick);
|
||||
|
||||
expect(mockProcessKill).toHaveBeenCalledWith(-1234, 'SIGTERM');
|
||||
|
||||
// Simulate process exiting
|
||||
@@ -117,10 +126,11 @@ describe('process-utils', () => {
|
||||
|
||||
await vi.advanceTimersByTimeAsync(SIGKILL_TIMEOUT_MS);
|
||||
|
||||
// Second call should NOT be SIGKILL because it exited
|
||||
expect(mockProcessKill).not.toHaveBeenCalledWith(-1234, 'SIGKILL');
|
||||
|
||||
await killPromise;
|
||||
});
|
||||
|
||||
it('should fallback to specific process kill if group kill fails', async () => {
|
||||
vi.mocked(os.platform).mockReturnValue('linux');
|
||||
mockProcessKill.mockImplementationOnce(() => {
|
||||
|
||||
@@ -32,7 +32,8 @@ export interface KillOptions {
|
||||
* or the PTY's built-in kill method.
|
||||
*
|
||||
* On Unix, it attempts to kill the process group (using -pid) with escalation
|
||||
* from SIGTERM to SIGKILL if requested.
|
||||
* from SIGTERM to SIGKILL if requested. It also walks the process tree using pgrep
|
||||
* to ensure all descendants are killed.
|
||||
*/
|
||||
export async function killProcessGroup(options: KillOptions): Promise<void> {
|
||||
const { pid, escalate = false, isExited = () => false, pty } = options;
|
||||
@@ -55,12 +56,59 @@ export async function killProcessGroup(options: KillOptions): Promise<void> {
|
||||
return;
|
||||
}
|
||||
|
||||
// Unix logic
|
||||
// Unix logic: Walk process tree to find all descendants
|
||||
const getAllDescendants = async (parentPid: number): Promise<number[]> => {
|
||||
let children: number[] = [];
|
||||
try {
|
||||
const { stdout } = await spawnAsync('pgrep', [
|
||||
'-P',
|
||||
parentPid.toString(),
|
||||
]);
|
||||
const pids = stdout
|
||||
.trim()
|
||||
.split('\n')
|
||||
.map((p: string) => parseInt(p, 10))
|
||||
.filter((p: number) => !isNaN(p));
|
||||
for (const p of pids) {
|
||||
children.push(p);
|
||||
const grandchildren = await getAllDescendants(p);
|
||||
children = children.concat(grandchildren);
|
||||
}
|
||||
} catch {
|
||||
// pgrep exits with 1 if no children are found
|
||||
}
|
||||
return children;
|
||||
};
|
||||
|
||||
const descendants = await getAllDescendants(pid);
|
||||
const allPidsToKill = [...descendants.reverse(), pid];
|
||||
|
||||
try {
|
||||
const initialSignal = options.signal || (escalate ? 'SIGTERM' : 'SIGKILL');
|
||||
|
||||
// Try killing the process group first (-pid)
|
||||
process.kill(-pid, initialSignal);
|
||||
try {
|
||||
process.kill(-pid, initialSignal);
|
||||
} catch {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
// Kill individual processes in the tree to ensure detached descendants are caught
|
||||
for (const targetPid of allPidsToKill) {
|
||||
try {
|
||||
process.kill(targetPid, initialSignal);
|
||||
} catch {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
if (pty) {
|
||||
try {
|
||||
pty.kill(typeof initialSignal === 'string' ? initialSignal : undefined);
|
||||
} catch {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
if (escalate && !isExited()) {
|
||||
await new Promise((res) => setTimeout(res, SIGKILL_TIMEOUT_MS));
|
||||
@@ -70,43 +118,30 @@ export async function killProcessGroup(options: KillOptions): Promise<void> {
|
||||
} catch {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Fallback to specific process kill if group kill fails or on error
|
||||
if (!isExited()) {
|
||||
if (pty) {
|
||||
if (escalate) {
|
||||
|
||||
for (const targetPid of allPidsToKill) {
|
||||
try {
|
||||
// Attempt the group kill BEFORE the pty session leader dies
|
||||
process.kill(-pid, 'SIGTERM');
|
||||
pty.kill('SIGTERM');
|
||||
await new Promise((res) => setTimeout(res, SIGKILL_TIMEOUT_MS));
|
||||
if (!isExited()) {
|
||||
try {
|
||||
process.kill(-pid, 'SIGKILL');
|
||||
} catch {
|
||||
// Ignore
|
||||
}
|
||||
pty.kill('SIGKILL');
|
||||
}
|
||||
process.kill(targetPid, 'SIGKILL');
|
||||
} catch {
|
||||
// Ignore
|
||||
}
|
||||
} else {
|
||||
}
|
||||
if (pty) {
|
||||
try {
|
||||
process.kill(-pid, 'SIGKILL'); // Group kill first
|
||||
pty.kill('SIGKILL');
|
||||
} catch {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
process.kill(pid, 'SIGKILL');
|
||||
} catch {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Ultimate fallback if something unexpected throws
|
||||
if (!isExited()) {
|
||||
try {
|
||||
process.kill(pid, 'SIGKILL');
|
||||
} catch {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user