mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-15 22:33:05 -07:00
fix(core): address memory leaks in oauth flow, chat history, and shell parsing
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { useEffect, useReducer, useRef } from 'react';
|
||||
import { useEffect, useReducer, useRef, useCallback } from 'react';
|
||||
import { setTimeout as setTimeoutPromise } from 'node:timers/promises';
|
||||
import * as path from 'node:path';
|
||||
import {
|
||||
@@ -224,15 +224,15 @@ export function useAtCompletion(props: UseAtCompletionProps): void {
|
||||
setIsLoadingSuggestions(state.isLoading);
|
||||
}, [state.isLoading, setIsLoadingSuggestions]);
|
||||
|
||||
const resetFileSearchState = () => {
|
||||
const resetFileSearchState = useCallback(() => {
|
||||
fileSearchMap.current.clear();
|
||||
initEpoch.current += 1;
|
||||
dispatch({ type: 'RESET' });
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
resetFileSearchState();
|
||||
}, [cwd, config]);
|
||||
}, [cwd, config, resetFileSearchState]);
|
||||
|
||||
useEffect(() => {
|
||||
const workspaceContext = config?.getWorkspaceContext?.();
|
||||
@@ -242,7 +242,7 @@ export function useAtCompletion(props: UseAtCompletionProps): void {
|
||||
workspaceContext.onDirectoriesChanged(resetFileSearchState);
|
||||
|
||||
return unsubscribe;
|
||||
}, [config]);
|
||||
}, [config, resetFileSearchState]);
|
||||
|
||||
// Reacts to user input (`pattern`) ONLY.
|
||||
useEffect(() => {
|
||||
|
||||
@@ -83,7 +83,12 @@ export function useHistory({
|
||||
return prevHistory; // Don't add the duplicate
|
||||
}
|
||||
}
|
||||
return [...prevHistory, newItem];
|
||||
const newHistory = [...prevHistory, newItem];
|
||||
// Enforce a hard limit of 1000 items to prevent unbounded memory growth
|
||||
if (newHistory.length > 1000) {
|
||||
return newHistory.slice(newHistory.length - 1000);
|
||||
}
|
||||
return newHistory;
|
||||
});
|
||||
|
||||
// Record UI-specific messages, but don't do it if we're actually loading
|
||||
|
||||
@@ -424,6 +424,7 @@ async function authWithUserCode(client: OAuth2Client): Promise<boolean> {
|
||||
'\n\n',
|
||||
);
|
||||
|
||||
let authTimeoutId: NodeJS.Timeout | undefined;
|
||||
const code = await new Promise<string>((resolve, reject) => {
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
@@ -431,7 +432,7 @@ async function authWithUserCode(client: OAuth2Client): Promise<boolean> {
|
||||
terminal: true,
|
||||
});
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
authTimeoutId = setTimeout(() => {
|
||||
rl.close();
|
||||
reject(
|
||||
new FatalAuthenticationError(
|
||||
@@ -441,10 +442,11 @@ async function authWithUserCode(client: OAuth2Client): Promise<boolean> {
|
||||
}, 300000); // 5 minute timeout
|
||||
|
||||
rl.question('Enter the authorization code: ', (code) => {
|
||||
clearTimeout(timeout);
|
||||
rl.close();
|
||||
resolve(code.trim());
|
||||
});
|
||||
}).finally(() => {
|
||||
if (authTimeoutId) clearTimeout(authTimeoutId);
|
||||
});
|
||||
|
||||
if (!code) {
|
||||
|
||||
@@ -116,6 +116,8 @@ export function startCallbackServer(
|
||||
portReject = reject;
|
||||
});
|
||||
|
||||
let timeoutId: NodeJS.Timeout | undefined;
|
||||
|
||||
const responsePromise = new Promise<OAuthAuthorizationResponse>(
|
||||
(resolve, reject) => {
|
||||
let serverPort: number;
|
||||
@@ -222,7 +224,7 @@ export function startCallbackServer(
|
||||
});
|
||||
|
||||
// Timeout after 5 minutes
|
||||
setTimeout(
|
||||
timeoutId = setTimeout(
|
||||
() => {
|
||||
server.close();
|
||||
reject(new Error('OAuth callback timeout'));
|
||||
@@ -232,7 +234,14 @@ export function startCallbackServer(
|
||||
},
|
||||
);
|
||||
|
||||
return { port: portPromise, response: responsePromise };
|
||||
return {
|
||||
port: portPromise,
|
||||
response: responsePromise.finally(() => {
|
||||
if (timeoutId) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -164,7 +164,16 @@ async function loadBashLanguage(): Promise<void> {
|
||||
|
||||
export async function initializeShellParsers(): Promise<void> {
|
||||
if (!treeSitterInitialization) {
|
||||
treeSitterInitialization = loadBashLanguage().catch((error) => {
|
||||
const timeoutPromise = new Promise<void>((_, reject) => {
|
||||
setTimeout(
|
||||
() => reject(new Error('Tree-sitter initialization timed out')),
|
||||
5000,
|
||||
);
|
||||
});
|
||||
treeSitterInitialization = Promise.race([
|
||||
loadBashLanguage(),
|
||||
timeoutPromise,
|
||||
]).catch((error) => {
|
||||
treeSitterInitialization = null;
|
||||
// Log the error but don't throw, allowing the application to fall back to safe defaults (ASK_USER)
|
||||
// or regex checks where appropriate.
|
||||
|
||||
Reference in New Issue
Block a user