mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-05 08:10:14 +00:00
fix: reduce CPU load from file watchers and improve performance
- Increased debounce delays to reduce frequency of file system events - Fixed MCP Hub file watcher cleanup to prevent duplicate watchers - Optimized ripgrep process handling with early termination - Improved timeout handling in list-files scanning - Updated tests to match new debounce timings Fixes #7595
This commit is contained in:
parent
2e59347a9d
commit
34b81fafc3
5 changed files with 50 additions and 38 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ export class FileWatcher implements IFileWatcher {
|
|||
private ignoreController: RooIgnoreController
|
||||
private accumulatedEvents: Map<string, { uri: vscode.Uri; type: "create" | "change" | "delete" }> = 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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue