Build settings index in parallel for the native server (#12299)

## Summary

This PR updates the server to build the settings index in parallel using
similar logic as `python_files_in_path`.

This should help with https://github.com/astral-sh/ruff/issues/11366 but
ideally we would want to build it lazily.

## Test Plan

`cargo insta test`
This commit is contained in:
Dhruv Manilawala 2024-07-15 15:27:54 +05:30 committed by GitHub
parent b9a8cd390f
commit ecd4b4d943
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 82 additions and 62 deletions

2
Cargo.lock generated
View file

@ -2439,6 +2439,7 @@ version = "0.2.2"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"crossbeam", "crossbeam",
"ignore",
"insta", "insta",
"jod-thread", "jod-thread",
"libc", "libc",
@ -2463,7 +2464,6 @@ dependencies = [
"shellexpand", "shellexpand",
"tracing", "tracing",
"tracing-subscriber", "tracing-subscriber",
"walkdir",
] ]
[[package]] [[package]]

View file

@ -28,6 +28,7 @@ ruff_workspace = { workspace = true }
anyhow = { workspace = true } anyhow = { workspace = true }
crossbeam = { workspace = true } crossbeam = { workspace = true }
ignore = { workspace = true }
jod-thread = { workspace = true } jod-thread = { workspace = true }
lsp-server = { workspace = true } lsp-server = { workspace = true }
lsp-types = { workspace = true } lsp-types = { workspace = true }
@ -38,7 +39,6 @@ serde_json = { workspace = true }
shellexpand = { workspace = true } shellexpand = { workspace = true }
tracing = { workspace = true } tracing = { workspace = true }
tracing-subscriber = { workspace = true } tracing-subscriber = { workspace = true }
walkdir = { workspace = true }
[dev-dependencies] [dev-dependencies]
insta = { workspace = true } insta = { workspace = true }

View file

@ -1,3 +1,9 @@
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use ignore::{WalkBuilder, WalkState};
use ruff_linter::{ use ruff_linter::{
display_settings, fs::normalize_path_to, settings::types::FilePattern, display_settings, fs::normalize_path_to, settings::types::FilePattern,
settings::types::PreviewMode, settings::types::PreviewMode,
@ -8,12 +14,6 @@ use ruff_workspace::{
pyproject::{find_user_settings_toml, settings_toml}, pyproject::{find_user_settings_toml, settings_toml},
resolver::{ConfigurationTransformer, Relativity}, resolver::{ConfigurationTransformer, Relativity},
}; };
use std::{
collections::BTreeMap,
path::{Path, PathBuf},
sync::Arc,
};
use walkdir::WalkDir;
use crate::session::settings::{ConfigurationPreference, ResolvedEditorSettings}; use crate::session::settings::{ConfigurationPreference, ResolvedEditorSettings};
@ -30,7 +30,7 @@ pub struct RuffSettings {
} }
pub(super) struct RuffSettingsIndex { pub(super) struct RuffSettingsIndex {
/// Index from folder to the resoled ruff settings. /// Index from folder to the resolved ruff settings.
index: BTreeMap<PathBuf, Arc<RuffSettings>>, index: BTreeMap<PathBuf, Arc<RuffSettings>>,
fallback: Arc<RuffSettings>, fallback: Arc<RuffSettings>,
} }
@ -100,6 +100,7 @@ impl RuffSettings {
impl RuffSettingsIndex { impl RuffSettingsIndex {
pub(super) fn new(root: &Path, editor_settings: &ResolvedEditorSettings) -> Self { pub(super) fn new(root: &Path, editor_settings: &ResolvedEditorSettings) -> Self {
let mut index = BTreeMap::default(); let mut index = BTreeMap::default();
let mut respect_gitignore = true;
// Add any settings from above the workspace root. // Add any settings from above the workspace root.
for directory in root.ancestors() { for directory in root.ancestors() {
@ -112,6 +113,7 @@ impl RuffSettingsIndex {
continue; continue;
}; };
respect_gitignore = settings.file_resolver.respect_gitignore;
index.insert( index.insert(
directory.to_path_buf(), directory.to_path_buf(),
Arc::new(RuffSettings { Arc::new(RuffSettings {
@ -126,70 +128,88 @@ impl RuffSettingsIndex {
} }
// Add any settings within the workspace itself // Add any settings within the workspace itself
let mut walker = WalkDir::new(root).into_iter(); let mut builder = WalkBuilder::new(root);
builder.standard_filters(respect_gitignore);
builder.hidden(false);
builder.threads(
std::thread::available_parallelism()
.map_or(1, std::num::NonZeroUsize::get)
.min(12),
);
let walker = builder.build_parallel();
while let Some(entry) = walker.next() { let index = std::sync::RwLock::new(index);
let Ok(entry) = entry else { walker.run(|| {
continue; Box::new(|result| {
}; let Ok(entry) = result else {
return WalkState::Continue;
};
// Skip non-directories. // Skip non-directories.
if !entry.file_type().is_dir() { if !entry
continue; .file_type()
} .is_some_and(|file_type| file_type.is_dir())
let directory = entry.into_path();
// If the directory is excluded from the workspace, skip it.
if let Some(file_name) = directory.file_name() {
if let Some((_, settings)) = index
.range(..directory.clone())
.rfind(|(path, _)| directory.starts_with(path))
{ {
if match_exclusion(&directory, file_name, &settings.file_resolver.exclude) { return WalkState::Continue;
tracing::debug!("Ignored path via `exclude`: {}", directory.display()); }
walker.skip_current_dir(); let directory = entry.into_path();
continue; tracing::debug!("Visiting: {}", directory.display());
} else if match_exclusion(
&directory,
file_name,
&settings.file_resolver.extend_exclude,
) {
tracing::debug!(
"Ignored path via `extend-exclude`: {}",
directory.display()
);
walker.skip_current_dir(); // If the directory is excluded from the workspace, skip it.
continue; if let Some(file_name) = directory.file_name() {
if let Some((_, settings)) = index
.read()
.unwrap()
.range(..directory.clone())
.rfind(|(path, _)| directory.starts_with(path))
{
if match_exclusion(&directory, file_name, &settings.file_resolver.exclude) {
tracing::debug!("Ignored path via `exclude`: {}", directory.display());
return WalkState::Continue;
} else if match_exclusion(
&directory,
file_name,
&settings.file_resolver.extend_exclude,
) {
tracing::debug!(
"Ignored path via `extend-exclude`: {}",
directory.display()
);
return WalkState::Continue;
}
} }
} }
}
if let Some(pyproject) = settings_toml(&directory).ok().flatten() { if let Some(pyproject) = settings_toml(&directory).ok().flatten() {
let Ok(settings) = ruff_workspace::resolver::resolve_root_settings( let Ok(settings) = ruff_workspace::resolver::resolve_root_settings(
&pyproject, &pyproject,
Relativity::Parent, Relativity::Parent,
&EditorConfigurationTransformer(editor_settings, root), &EditorConfigurationTransformer(editor_settings, root),
) else { ) else {
continue; return WalkState::Continue;
}; };
index.insert( index.write().unwrap().insert(
directory, directory,
Arc::new(RuffSettings { Arc::new(RuffSettings {
path: Some(pyproject), path: Some(pyproject),
file_resolver: settings.file_resolver, file_resolver: settings.file_resolver,
linter: settings.linter, linter: settings.linter,
formatter: settings.formatter, formatter: settings.formatter,
}), }),
); );
} }
}
WalkState::Continue
})
});
let fallback = Arc::new(RuffSettings::fallback(editor_settings, root)); let fallback = Arc::new(RuffSettings::fallback(editor_settings, root));
Self { index, fallback } Self {
index: index.into_inner().unwrap(),
fallback,
}
} }
pub(super) fn get(&self, document_path: &Path) -> Arc<RuffSettings> { pub(super) fn get(&self, document_path: &Path) -> Arc<RuffSettings> {