Skip to content

Conversation

@kiwina
Copy link
Contributor

@kiwina kiwina commented Jun 2, 2025

PR for FileWatcher Leak (file-watcher_93)

This PR addresses the potential FileSystemWatcher leak identified in leak-report/guaranteed/file-watcher_93.md.

Description

The FileWatcher creates a FileSystemWatcher that might not be disposed if the CodeIndexManager's disposal chain is interrupted. While CodeIndexManager is added to context.subscriptions (which should ensure its dispose method is called on extension deactivation), this patch aims to add robustness to the disposal process within the CodeIndexOrchestrator as a defensive measure.

The proposed fix involves ensuring CodeIndexOrchestrator has an explicit dispose method that reliably calls stopWatcher() (which in turn disposes the FileWatcher).

Related Bug Report

Closes: #4230

Changes

  • Added/Updated dispose() method in src/services/code-index/orchestrator.ts to ensure stopWatcher() is called.
  • Ensured CodeIndexManager.dispose() calls the orchestrator's dispose method if available (this might be redundant but adds safety).
  • Add/update unit tests if applicable to verify disposal chain.

How to Test

  1. Verify that CodeIndexManager.dispose() is called upon extension deactivation.
  2. Trace the disposal calls: CodeIndexManager.dispose() -> CodeIndexOrchestrator.stopWatcher() (or dispose()) -> FileWatcher.dispose().
  3. Simulate scenarios where extension deactivation might have errors to see if the watcher is still disposed of (this might be hard to test directly).

Important

Fixes potential FileSystemWatcher leak by ensuring proper disposal in CodeIndexOrchestrator and CodeIndexManager.

  • Behavior:
    • Ensures CodeIndexOrchestrator.dispose() calls stopWatcher() to dispose FileWatcher.
    • CodeIndexManager.dispose() now checks and calls CodeIndexOrchestrator.dispose() if available.
  • Code Changes:
    • Added dispose() method in orchestrator.ts to call stopWatcher().
    • Updated dispose() method in manager.ts to ensure orchestrator disposal.
  • Testing:
    • Verify disposal chain: CodeIndexManager.dispose() -> CodeIndexOrchestrator.stopWatcher() -> FileWatcher.dispose().

This description was created by Ellipsis for 5014270. You can customize this summary. It will automatically update as commits are pushed.

@kiwina kiwina requested review from cte and mrubens as code owners June 2, 2025 04:53
@dosubot dosubot bot added size:S This PR changes 10-29 lines, ignoring generated files. bug Something isn't working labels Jun 2, 2025
@daniel-lxs daniel-lxs moved this from Triage to PR [Needs Prelim Review] in Roo Code Roadmap Jun 2, 2025
Copy link
Member

@daniel-lxs daniel-lxs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @kiwina, thank you again for your contribution.
I just have a couple of questions/suggestions for this one, let me know what you think about them!

Comment on lines +219 to +222
this.stopWatcher() // This calls _orchestrator.stopWatcher()
// Ensure orchestrator is fully cleaned up if it has a dispose method
if (typeof (this._orchestrator as any).dispose === "function") {
;(this._orchestrator as any).dispose()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like _orchestrator.stopWatcher() might be called twice here: once via this.stopWatcher() on line 219, and then again when this._orchestrator.dispose() is called on line 222 (since CodeIndexOrchestrator.dispose() also calls stopWatcher()).

Is this intentional?

I think to make this simpler we could have CodeIndexManager.dispose() rely primarily on this._orchestrator.dispose() to handle its own cleanup:

public dispose(): void {
  if (this._orchestrator) {
    // The orchestrator's dispose should handle its own stopWatcher call
    if (typeof this._orchestrator.dispose === 'function') {
      this._orchestrator.dispose();
    } else {
      // Fallback if for some reason dispose isn't on the orchestrator
      this.stopWatcher(); 
    }
  }
  this._stateManager.dispose();
}

What are your thoughts on this approach?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I belive everything should be unified with dispose method, just like some of the other PR.
If that is the case than stopWatcher is actually not even necessay. In the long run that would make contribution/testing across the board more unified

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also
public stopWatcher(): void { if (!this.isFeatureEnabled) { return } if (this._orchestrator) { this._orchestrator.stopWatcher() } }
this._orchestrator needs to be disposed 100% stopWatcher() does not guarantee that

Comment on lines +221 to +222
if (typeof (this._orchestrator as any).dispose === "function") {
;(this._orchestrator as any).dispose()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see this._orchestrator is typed as CodeIndexOrchestrator | undefined (on line 24), and CodeIndexOrchestrator now has a dispose() method.

Given this, the as any cast on line 221 might not be strictly necessary. Was it included for a specific reason?

@daniel-lxs daniel-lxs moved this from PR [Needs Prelim Review] to PR [Changes Requested] in Roo Code Roadmap Jun 2, 2025
@daniel-lxs
Copy link
Member

daniel-lxs commented Jun 16, 2025

Hi @kiwina, thanks for your contribution and for taking the time to address the FileWatcher leak concern!

After reviewing the changes and the existing code, I believe the FileWatcher is already being properly disposed through the current implementation:

  1. When CodeIndexManager.dispose() is called, it invokes stopWatcher()
  2. This calls orchestrator.stopWatcher(), which in turn calls fileWatcher.dispose() and cleans up all subscriptions
  3. The FileWatcher disposal chain is complete and working as intended

The changes in this PR add a dispose() method to the orchestrator that simply calls stopWatcher(), resulting in the same cleanup being performed twice. While the intention to ensure proper disposal is good, the current implementation already handles this correctly.

If there's a specific scenario where you've observed the FileWatcher not being disposed, could you share more details? That would help us identify if there's a different root cause we need to address.

For now, I'll temporarily close this PR as the disposal mechanism is already functioning properly. However, if you'd like to improve the disposal pattern for better consistency with VSCode conventions (using dispose() instead of stopWatcher()), that could be a valuable refactoring - but it would need to be done without the redundant calls.

What do you think?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working PR - Changes Requested size:S This PR changes 10-29 lines, ignoring generated files.

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

fix: Potential FileSystemWatcher leak in CodeIndexManager (file-watcher_93)

2 participants