fix: prevent memory leak when switching to code mode

- Fix duplicate subscription adding in updateCodeIndexStatusSubscription
- Stop code indexing when switching away from code mode
- Add proper disposal of CodeIndexManager resources
- Clear service references to allow garbage collection

Fixes #9074
This commit is contained in:
Roo Code 2025-11-06 10:54:28 +00:00
parent 7320d79c08
commit a2e6911690
2 changed files with 58 additions and 2 deletions

View file

@ -585,6 +585,26 @@ export class ClineProvider
this.clearAllPendingEditOperations()
this.log("Cleared pending operations")
// Dispose code index manager and stop any ongoing indexing
if (this.codeIndexStatusSubscription) {
this.codeIndexStatusSubscription.dispose()
this.codeIndexStatusSubscription = undefined
}
// Stop code indexing if it's running
const codeIndexManager = this.getCurrentWorkspaceCodeIndexManager()
if (codeIndexManager) {
try {
codeIndexManager.stopWatcher()
this.log("Stopped code indexing during dispose")
} catch (error) {
this.log(
`Failed to stop code indexing during dispose: ${error instanceof Error ? error.message : String(error)}`,
)
}
}
this.codeIndexManager = undefined
if (this.view && "dispose" in this.view) {
this.view.dispose()
this.log("Disposed webview")
@ -1217,6 +1237,21 @@ export class ClineProvider
* @param newMode The mode to switch to
*/
public async handleModeSwitch(newMode: Mode) {
// Stop code indexing if switching away from code mode
const currentMode = this.getGlobalState("mode") ?? defaultModeSlug
if (currentMode === "code" && newMode !== "code") {
const codeIndexManager = this.getCurrentWorkspaceCodeIndexManager()
if (codeIndexManager) {
try {
// Stop the file watcher to prevent continuous disk reading
codeIndexManager.stopWatcher()
this.log(`Stopped code indexing when switching from code mode to ${newMode}`)
} catch (error) {
this.log(`Failed to stop code indexing: ${error instanceof Error ? error.message : String(error)}`)
}
}
}
const task = this.getCurrentTask()
if (task) {
@ -2472,6 +2507,11 @@ export class ClineProvider
// Dispose the old subscription if it exists
if (this.codeIndexStatusSubscription) {
this.codeIndexStatusSubscription.dispose()
// Remove from webviewDisposables to prevent duplicate disposal
const index = this.webviewDisposables.indexOf(this.codeIndexStatusSubscription)
if (index > -1) {
this.webviewDisposables.splice(index, 1)
}
this.codeIndexStatusSubscription = undefined
}
@ -2492,7 +2532,8 @@ export class ClineProvider
}
})
if (this.view) {
// Only add to disposables if view exists and subscription is not already added
if (this.view && this.webviewDisposables.indexOf(this.codeIndexStatusSubscription) === -1) {
this.webviewDisposables.push(this.codeIndexStatusSubscription)
}

View file

@ -247,10 +247,25 @@ export class CodeIndexManager {
* Cleans up the manager instance.
*/
public dispose(): void {
// Stop file watcher and indexing if running
if (this._orchestrator) {
this.stopWatcher()
this._orchestrator = undefined
}
this._stateManager.dispose()
// Dispose state manager
if (this._stateManager) {
this._stateManager.dispose()
}
// Clear all service references to allow garbage collection
this._configManager = undefined
this._serviceFactory = undefined
this._searchService = undefined
this._cacheManager = undefined
// Remove this instance from the static instances map
CodeIndexManager.instances.delete(this.workspacePath)
}
/**