-
-
Notifications
You must be signed in to change notification settings - Fork 754
fix(workspace): unload plugin cache when project closes #7790
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
WalkthroughThe Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
crates/biome_service/src/workspace/server.rs(1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
crates/biome_*/**
📄 CodeRabbit inference engine (CLAUDE.md)
Place core crates under /crates/biome_*/
Files:
crates/biome_service/src/workspace/server.rs
**/*.rs
📄 CodeRabbit inference engine (CONTRIBUTING.md)
**/*.rs: Format Rust files before committing (e.g., viajust fwhich formats Rust)
Document rules, assists, and options with inline rustdoc in source
Files:
crates/biome_service/src/workspace/server.rs
crates/biome_service/src/workspace/server.rs
📄 CodeRabbit inference engine (crates/biome_service/CONTRIBUTING.md)
WorkspaceServer must maintain workspace state and is used in the daemon and CLI daemonless mode
Files:
crates/biome_service/src/workspace/server.rs
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
- GitHub Check: Documentation
- GitHub Check: Lint project (depot-ubuntu-24.04-arm-16)
- GitHub Check: Lint project (depot-windows-2022)
- GitHub Check: Test Node.js API
- GitHub Check: Check Dependencies
- GitHub Check: Test (depot-ubuntu-24.04-arm-16)
- GitHub Check: Test (depot-windows-2022-16)
- GitHub Check: autofix
|
|
||
| self.module_graph.unload_path(&project_path); | ||
| self.project_layout.unload_folder(&project_path); | ||
| self.plugin_caches.pin().remove(&project_path); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Excellent fix for the plugin cache leak!
The cleanup is correctly placed and follows the same pattern as module_graph and project_layout. This addresses the memory leak reported by @Boshen.
One thing worth verifying: load_plugins (line 519) keys the cache by base_path, which can be either the project root or a nested workspace_directory (lines 887-896). When closing a project, does the current cleanup handle nested plugin caches, or do we only clean up the project root entry?
🏁 Script executed:
#!/bin/bash
# Check if there are scenarios where plugin_caches could have multiple entries per project
rg -n -C5 'load_plugins' --type rustLength of output: 1743
I need to dig deeper into the caching logic to verify if there's a potential mismatch. Let me search for where the plugin cache is actually stored and what key is used.
[/run_scripts]
🏁 Script executed:
#!/bin/bash
# Find where plugin_caches is inserted/stored to confirm the key
rg -n 'plugin_caches.*insert' --type rust -C3Length of output: 0
🏁 Script executed:
#!/bin/bash
# Find close_project method signature and project_path origin
rg -n 'fn close_project|project_path' crates/biome_service/src/workspace/server.rs -C2Length of output: 4183
🏁 Script executed:
#!/bin/bash
# Check if project_path is related to loading_directory
rg -n 'close_project|project_key|loading_directory' crates/biome_service/src/workspace/server.rs | head -40Length of output: 2384
I found a potential issue. Let me verify when extends_root is false and how plugin_caches actually stores data:
[/run_scripts]
🏁 Script executed:
#!/bin/bash
# Search for extends_root to understand when it's false vs true
rg -n 'extends_root' crates/biome_service/src/workspace/server.rs -B5 -A5Length of output: 1380
🏁 Script executed:
#!/bin/bash
# Search more broadly for how plugin_caches stores/retrieves data
rg -n 'plugin_caches' crates/biome_service/src/workspace/server.rs -B3 -A3Length of output: 1358
Plugin cache cleanup misses workspace-level caches.
Found a real issue: when a project uses extends_root = false, the plugin cache is keyed by workspace_directory (line 519), but close_project removes using project_path (line 984). These are different paths, so the cleanup never fires.
The fix needs to track which path the cache was stored under, or ensure both keys are cleaned up:
// Option 1: Also remove workspace_directory entry if it exists
if let Some(settings) = self.projects.get_root_settings(params.project_key) {
if !settings.configuration.extends_root() {
// Clean up workspace-level cache too
}
}
// Or: Store cache under both keys for consistent cleanup🤖 Prompt for AI Agents
In crates/biome_service/src/workspace/server.rs around lines 519 and 984, the
plugin cache is stored under workspace_directory when a project has extends_root
= false (line ~519) but close_project currently only removes the cache by
project_path (line 984), so workspace-level entries are never cleaned up; fix by
changing close_project to also detect when the project used workspace_directory
as the cache key (e.g., fetch root settings for the project_key and if
configuration.extends_root() == false then remove the workspace_directory key as
well), or alternatively, when inserting the cache store it under both
project_path and workspace_directory so either removal path will succeed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@coderabbitai a project will never be initialised with extenda_root = false, because we catch that early and raise an error. A project is always a root. Sub projects are called packages and they can't/can extends the root
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check how plugin_caches entries are organized - are they hierarchical or flat?
# Look for all places where plugin_caches is accessed
rg -n 'plugin_caches' crates/biome_service/src/workspace/server.rs -A2 -B2
Length of output: 1081
🏁 Script executed:
#!/bin/bash
# Check if papaya HashMap has any special cleanup for nested paths
# or if each entry must be removed individually
rg -n 'HashMap.*remove|papaya' crates/biome_service/src/workspace/server.rs -C3
Length of output: 753
🏁 Script executed:
#!/bin/bash
# Find where workspace_directory is used vs project_path to understand the key structure
rg -n 'workspace_directory.*clone\(\)|loading_directory' crates/biome_service/src/workspace/server.rs -C2
Length of output: 494
🏁 Script executed:
#!/bin/bash
# Search for how packages/nested configurations are managed and tracked
rg -n 'nested.*settings|package.*path|workspace_directory' crates/biome_service/src/workspace/server.rs -C2 | head -60
Length of output: 2541
🏁 Script executed:
#!/bin/bash
# Check the Projects structure to see if it tracks nested packages/workspaces
rg -n 'struct Projects|get_nested|set_nested' crates/biome_service/src/projects -A5 | head -80
Length of output: 73
🏁 Script executed:
#!/bin/bash
# Look for any existing cleanup logic that iterates through nested items
rg -n 'close_project|remove_project' crates/biome_service/src -A10 -B2 | head -100
Length of output: 5772
🏁 Script executed:
#!/bin/bash
# Find the Projects struct definition and its storage structure
fd -e rs . crates/biome_service/src -x rg -l 'struct Projects' {} | head -5
Length of output: 36
🏁 Script executed:
#!/bin/bash
# Look at the Projects implementation to understand nested settings tracking
rg -n 'impl Projects|get_nested_settings|set_nested_settings' crates/biome_service/src -A8 | head -150
Length of output: 3779
🏁 Script executed:
#!/bin/bash
# Check if there's any iteration over nested settings in the codebase
rg -n 'nested.*iter|workspace_directory.*keys|for.*nested' crates/biome_service/src -C2 | head -50
Length of output: 1831
🏁 Script executed:
#!/bin/bash
# Get the ProjectData struct to see nested_settings type
rg -n 'struct ProjectData|nested_settings:' crates/biome_service/src/projects.rs -A15 | head -50
Length of output: 1596
🏁 Script executed:
#!/bin/bash
# Look for how nested_settings is populated to understand the keys
rg -n 'nested_settings.*insert|nested_settings\.insert' crates/biome_service/src -C3
Length of output: 590
@ematipico Thanks for the clarification! You're absolutely right that projects are always roots. However, I believe there's still a potential leak for nested packages (not projects) that have extends_root = false.
Here's the issue:
When a nested package has extends_root = false:
- Line 890:
loading_directory = workspace_directory(the package's directory) - Line 519: Plugin cache is stored with
loading_directoryas the key
When the project closes:
- Line 984: Only removes
plugin_caches[project_path] - Nested package caches (keyed by their
workspace_directory) remain in memory
The nested_settings structure:
// Line 32 of projects.rs
nested_settings: BTreeMap<Utf8PathBuf, Settings>Each key in nested_settings is a potential plugin cache entry that needs cleanup.
Suggested fix:
fn close_project(&self, params: CloseProjectParams) -> Result<(), WorkspaceError> {
let project_path = self.projects.get_project_path(params.project_key)
.ok_or_else(WorkspaceError::no_project)?;
// Get nested package paths before removing the project
let nested_paths: Vec<Utf8PathBuf> = self.projects.0.pin()
.get(¶ms.project_key)
.map(|data| data.nested_settings.keys().cloned().collect())
.unwrap_or_default();
self.projects.remove_project(params.project_key);
self.scanner.unload_project(params.project_key, project_path.clone());
// ... existing document cleanup ...
self.module_graph.unload_path(&project_path);
self.project_layout.unload_folder(&project_path);
// Clean up project-level cache
self.plugin_caches.pin().remove(&project_path);
// Clean up all nested package caches
for nested_path in nested_paths {
self.plugin_caches.pin().remove(&nested_path);
}
Ok(())
}Does this accurately reflect the architecture, or am I still misunderstanding something?
🧬 Code Graph Analysis Results
[crates/biome_configuration/src/lib.rs:256-261]
pub fn is_root(&self) -> bool {
match self.root {
Some(root) => root.value(),
None => !self.extends_root(),
}
}
[crates/biome_configuration/src/lib.rs:263-265]
pub fn extends_root(&self) -> bool {
self.extends.as_ref().is_some_and(Extends::extends_root)
}
[crates/biome_service/src/workspace.rs:update_settings() - loading_directory logic]
Doc summary:
- Context: In update_settings, after merging the new configuration, the code decides which directory should be used for loading plugin settings and ignore patterns.
- Parameters:
- extends_root: bool derived from the configuration (whether the config extends root settings).
- workspace_directory: Optional directory where current workspace-level settings live.
- project_key: identifier for the project being updated.
- Behavior:
- If extends_root is true, set loading_directory to the project’s root path (project_path) for loading root-level configuration/plugins.
- Otherwise, set loading_directory to the provided workspace_directory.
- Return/Effect: Determines which directory’s settings/plugins are loaded and merged into the project’s configuration.
Notes:
- This logic ties directly to the user comment: a project’s root vs nested packages (packages) when root-extension behavior is involved. If extends_root is false, the code uses the explicit workspace directory; if true, it uses the project root path.
🧠 Learnings used
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_service/CONTRIBUTING.md:0-0
Timestamp: 2025-10-15T09:25:05.698Z
Learning: Applies to crates/biome_service/src/workspace.rs : Implement and expose the Workspace trait in src/workspace.rs
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_service/CONTRIBUTING.md:0-0
Timestamp: 2025-10-15T09:25:05.698Z
Learning: Applies to crates/biome_service/src/workspace/server.rs : WorkspaceServer must maintain workspace state and is used in the daemon and CLI daemonless mode
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_service/CONTRIBUTING.md:0-0
Timestamp: 2025-10-15T09:25:05.698Z
Learning: Applies to crates/biome_service/src/workspace/watcher.tests.rs : Place watcher tests related to workspace methods in src/workspace/watcher.tests.rs
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_service/CONTRIBUTING.md:0-0
Timestamp: 2025-10-15T09:25:05.698Z
Learning: Applies to crates/biome_service/src/workspace/client.rs : WorkspaceClient is for creating connections to the daemon and communicating with WorkspaceServer
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dyc3 the bot might be right. I don't know how plugin caches are indexed. Could you verify that?
Summary
We weren't unloading
plugin_cacheswhen we close a project. Technically a memory leak.Pointed out on twitter, thanks @Boshen!
Test Plan
CI should remain green
Docs