diff --git a/src/integrations/workspace/WorkspaceTracker.ts b/src/integrations/workspace/WorkspaceTracker.ts index 7de8f994c0..17c768d6d8 100644 --- a/src/integrations/workspace/WorkspaceTracker.ts +++ b/src/integrations/workspace/WorkspaceTracker.ts @@ -106,7 +106,7 @@ class WorkspaceTracker { this.prevWorkSpacePath = this.cwd this.initializeFilePaths() } - }, 300) // Debounce for 300ms + }, 500) // Increased debounce to 500ms to reduce CPU load } private workspaceDidUpdate() { @@ -125,7 +125,7 @@ class WorkspaceTracker { openedTabs: this.getOpenedTabsInfo(), }) this.updateTimer = null - }, 300) // Debounce for 300ms + }, 500) // Increased debounce to 500ms to reduce CPU load } private normalizeFilePath(filePath: string): string { diff --git a/src/integrations/workspace/__tests__/WorkspaceTracker.spec.ts b/src/integrations/workspace/__tests__/WorkspaceTracker.spec.ts index b0a617d970..90f1d6f131 100644 --- a/src/integrations/workspace/__tests__/WorkspaceTracker.spec.ts +++ b/src/integrations/workspace/__tests__/WorkspaceTracker.spec.ts @@ -225,8 +225,8 @@ describe("WorkspaceTracker", () => { // Simulate tab change event await registeredTabChangeCallback!() - // Run the debounce timer for workspaceDidReset - vitest.advanceTimersByTime(300) + // Run the debounce timer for workspaceDidReset (now 500ms) + vitest.advanceTimersByTime(500) // Should clear file paths and reset workspace expect(mockProvider.postMessageToWebview).toHaveBeenCalledWith({ @@ -314,8 +314,8 @@ describe("WorkspaceTracker", () => { // Call again before timer completes await registeredTabChangeCallback!() - // Advance timer - vitest.advanceTimersByTime(300) + // Advance timer (now 500ms) + vitest.advanceTimersByTime(500) // Should only have one call to postMessageToWebview expect(mockProvider.postMessageToWebview).toHaveBeenCalledWith({ @@ -338,8 +338,8 @@ describe("WorkspaceTracker", () => { // Dispose before timer completes workspaceTracker.dispose() - // Advance timer - vitest.advanceTimersByTime(300) + // Advance timer (now 500ms) + vitest.advanceTimersByTime(500) // Should have called dispose on all disposables expect(mockDispose).toHaveBeenCalled() diff --git a/src/services/code-index/processors/file-watcher.ts b/src/services/code-index/processors/file-watcher.ts index 1e5ebcbceb..7a0e955f21 100644 --- a/src/services/code-index/processors/file-watcher.ts +++ b/src/services/code-index/processors/file-watcher.ts @@ -37,7 +37,7 @@ export class FileWatcher implements IFileWatcher { private ignoreController: RooIgnoreController private accumulatedEvents: Map = new Map() private batchProcessDebounceTimer?: NodeJS.Timeout - private readonly BATCH_DEBOUNCE_DELAY_MS = 500 + private readonly BATCH_DEBOUNCE_DELAY_MS = 1000 // Increased from 500ms to reduce CPU load private readonly FILE_PROCESSING_CONCURRENCY_LIMIT = 10 private readonly batchSegmentThreshold: number diff --git a/src/services/glob/list-files.ts b/src/services/glob/list-files.ts index 7347515784..f6ed7a2cce 100644 --- a/src/services/glob/list-files.ts +++ b/src/services/glob/list-files.ts @@ -626,29 +626,42 @@ async function execRipgrep(rgPath: string, args: string[], limit: number): Promi const rgProcess = childProcess.spawn(rgPath, args) let output = "" let results: string[] = [] + let processKilled = false - // Set timeout to avoid hanging + // Set timeout to avoid hanging - increased timeout for slower systems const timeoutId = setTimeout(() => { - rgProcess.kill() - console.warn("ripgrep timed out, returning partial results") - resolve(results.slice(0, limit)) - }, 10_000) + if (!processKilled) { + processKilled = true + rgProcess.kill("SIGTERM") + console.warn("ripgrep timed out, returning partial results") + resolve(results.slice(0, limit)) + } + }, 15_000) // Increased timeout to 15 seconds // Process stdout data as it comes in rgProcess.stdout.on("data", (data) => { + // Early exit if we've already killed the process + if (processKilled) return + output += data.toString() processRipgrepOutput() // Kill the process if we've reached the limit - if (results.length >= limit) { - rgProcess.kill() - clearTimeout(timeoutId) // Clear the timeout when we kill the process due to reaching the limit + if (results.length >= limit && !processKilled) { + processKilled = true + clearTimeout(timeoutId) + rgProcess.kill("SIGTERM") + // Resolve immediately when limit is reached + resolve(results.slice(0, limit)) } }) // Process stderr but don't fail on non-zero exit codes rgProcess.stderr.on("data", (data) => { - console.error(`ripgrep stderr: ${data}`) + // Only log errors if not killed intentionally + if (!processKilled) { + console.error(`ripgrep stderr: ${data}`) + } }) // Handle process completion @@ -656,22 +669,27 @@ async function execRipgrep(rgPath: string, args: string[], limit: number): Promi // Clear the timeout to avoid memory leaks clearTimeout(timeoutId) - // Process any remaining output - processRipgrepOutput(true) + // Only process if not already resolved + if (!processKilled) { + // Process any remaining output + processRipgrepOutput(true) - // Log non-zero exit codes but don't fail - if (code !== 0 && code !== null && code !== 143 /* SIGTERM */) { - console.warn(`ripgrep process exited with code ${code}, returning partial results`) - } + // Log non-zero exit codes but don't fail + if (code !== 0 && code !== null && code !== 143 /* SIGTERM */) { + console.warn(`ripgrep process exited with code ${code}, returning partial results`) + } - resolve(results.slice(0, limit)) + resolve(results.slice(0, limit)) + } }) // Handle process errors rgProcess.on("error", (error) => { // Clear the timeout to avoid memory leaks clearTimeout(timeoutId) - reject(new Error(`ripgrep process error: ${error.message}`)) + if (!processKilled) { + reject(new Error(`ripgrep process error: ${error.message}`)) + } }) // Helper function to process output buffer diff --git a/src/services/mcp/McpHub.ts b/src/services/mcp/McpHub.ts index 6ec5b839e8..2bb5481e4a 100644 --- a/src/services/mcp/McpHub.ts +++ b/src/services/mcp/McpHub.ts @@ -286,11 +286,11 @@ export class McpHub { clearTimeout(existingTimer) } - // Set new timer + // Set new timer with longer debounce to reduce CPU usage const timer = setTimeout(async () => { this.configChangeDebounceTimers.delete(key) await this.handleConfigFileChange(filePath, source) - }, 500) // 500ms debounce + }, 1000) // Increased to 1000ms debounce to reduce CPU load this.configChangeDebounceTimers.set(key, timer) } @@ -1033,7 +1033,6 @@ export class McpHub { if (manageConnectingState) { this.isConnecting = true } - this.removeAllFileWatchers() // Filter connections by source const currentConnections = this.connections.filter( (conn) => conn.server.source === source || (!conn.server.source && source === "global"), @@ -1041,7 +1040,7 @@ export class McpHub { const currentNames = new Set(currentConnections.map((conn) => conn.server.name)) const newNames = new Set(Object.keys(newServers)) - // Delete removed servers + // Delete removed servers (this will also clean up their file watchers) for (const name of currentNames) { if (!newNames.has(name)) { await this.deleteConnection(name, source) @@ -1065,10 +1064,7 @@ export class McpHub { if (!currentConnection) { // New server try { - // Only setup file watcher for enabled servers - if (!validatedConfig.disabled) { - this.setupFileWatcher(name, validatedConfig, source) - } + // connectToServer will handle file watcher setup for enabled servers await this.connectToServer(name, validatedConfig, source) } catch (error) { this.showErrorMessage(`Failed to connect to new MCP server ${name}`, error) @@ -1076,11 +1072,9 @@ export class McpHub { } else if (!deepEqual(JSON.parse(currentConnection.server.config), config)) { // Existing server with changed config try { - // Only setup file watcher for enabled servers - if (!validatedConfig.disabled) { - this.setupFileWatcher(name, validatedConfig, source) - } + // Delete connection first (this cleans up old file watchers) await this.deleteConnection(name, source) + // connectToServer will handle file watcher setup for enabled servers await this.connectToServer(name, validatedConfig, source) } catch (error) { this.showErrorMessage(`Failed to reconnect MCP server ${name}`, error)