Fix background scanner deadlock (#44109)

Fixes a deadlock in the background scanner that occurs on single-core
Linux devices. This happens because the background scanner would `block`
on a background thread waiting for a future, but on single-core Linux
devices there would be no other thread to pick it up. This mostly
affects SSH remoting use cases where it's common for servers to have 1
vCPU.

Closes #43884 
Closes #43809

Release Notes:

- Fix SSH remoting hang when connecting to 1 vCPU servers
This commit is contained in:
Agus Zubiaga
2025-12-04 11:30:16 -03:00
committed by GitHub
parent 2dad46c5c0
commit c978db8626
+38 -24
View File
@@ -52,7 +52,7 @@ use std::{
fmt,
future::Future,
mem::{self},
ops::{Deref, DerefMut},
ops::{Deref, DerefMut, Range},
path::{Path, PathBuf},
pin::Pin,
sync::{
@@ -3877,29 +3877,35 @@ impl BackgroundScanner {
abs_paths.dedup_by(|a, b| a.starts_with(b));
{
let snapshot = &self.state.lock().await.snapshot;
abs_paths.retain(|abs_path| {
let abs_path = &SanitizedPath::new(abs_path);
let mut ranges_to_drop = SmallVec::<[Range<usize>; 4]>::new();
fn skip_ix(ranges: &mut SmallVec<[Range<usize>; 4]>, ix: usize) {
if let Some(last_range) = ranges.last_mut()
&& last_range.end == ix
{
last_range.end += 1;
} else {
ranges.push(ix..ix + 1);
}
}
for (ix, abs_path) in abs_paths.iter().enumerate() {
let abs_path = &SanitizedPath::new(&abs_path);
{
let mut is_git_related = false;
let mut dot_git_paths = None;
let dot_git_paths = self.executor.block(maybe!(async {
let mut path = None;
for ancestor in abs_path.as_path().ancestors() {
for ancestor in abs_path.as_path().ancestors() {
if is_git_dir(ancestor, self.fs.as_ref()).await {
let path_in_git_dir = abs_path
.as_path()
.strip_prefix(ancestor)
.expect("stripping off the ancestor");
path = Some((ancestor.to_owned(), path_in_git_dir.to_owned()));
break;
dot_git_paths = Some((ancestor.to_owned(), path_in_git_dir.to_owned()));
break;
}
}
path
}));
}
if let Some((dot_git_abs_path, path_in_git_dir)) = dot_git_paths {
if skipped_files_in_dot_git
@@ -3909,8 +3915,11 @@ impl BackgroundScanner {
path_in_git_dir.starts_with(skipped_git_subdir)
})
{
log::debug!("ignoring event {abs_path:?} as it's in the .git directory among skipped files or directories");
return false;
log::debug!(
"ignoring event {abs_path:?} as it's in the .git directory among skipped files or directories"
);
skip_ix(&mut ranges_to_drop, ix);
continue;
}
is_git_related = true;
@@ -3919,8 +3928,7 @@ impl BackgroundScanner {
}
}
let relative_path = if let Ok(path) =
abs_path.strip_prefix(&root_canonical_path)
let relative_path = if let Ok(path) = abs_path.strip_prefix(&root_canonical_path)
&& let Ok(path) = RelPath::new(path, PathStyle::local())
{
path
@@ -3931,10 +3939,11 @@ impl BackgroundScanner {
);
} else {
log::error!(
"ignoring event {abs_path:?} outside of root path {root_canonical_path:?}",
"ignoring event {abs_path:?} outside of root path {root_canonical_path:?}",
);
}
return false;
skip_ix(&mut ranges_to_drop, ix);
continue;
};
if abs_path.file_name() == Some(OsStr::new(GITIGNORE)) {
@@ -3958,21 +3967,26 @@ impl BackgroundScanner {
});
if !parent_dir_is_loaded {
log::debug!("ignoring event {relative_path:?} within unloaded directory");
return false;
skip_ix(&mut ranges_to_drop, ix);
continue;
}
if self.settings.is_path_excluded(&relative_path) {
if !is_git_related {
log::debug!("ignoring FS event for excluded path {relative_path:?}");
}
return false;
skip_ix(&mut ranges_to_drop, ix);
continue;
}
relative_paths.push(relative_path.into_arc());
true
}
});
for range_to_drop in ranges_to_drop.into_iter().rev() {
abs_paths.drain(range_to_drop);
}
}
if relative_paths.is_empty() && dot_git_abs_paths.is_empty() {
return;
}