Fix file descriptors leak in evals (#18351)
Fixes an issue where evals were hitting "too many open files" errors because we were adding (and detaching) new directory watches for each project. Now we add those watches globally/at the worktree level, and we store the tasks so they stop watching on drop. Release Notes: - N/A --------- Co-authored-by: Max <max@zed.dev> Co-authored-by: Piotr <piotr@zed.dev> Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
This commit is contained in:
parent
dc48af0ca1
commit
1eddd2f38d
5 changed files with 49 additions and 22 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -10498,6 +10498,7 @@ dependencies = [
|
||||||
"futures 0.3.30",
|
"futures 0.3.30",
|
||||||
"gpui",
|
"gpui",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
|
"paths",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"snippet",
|
"snippet",
|
||||||
|
|
|
@ -70,9 +70,7 @@ mod tests {
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let image: id = msg_send![class!(NSImage), alloc];
|
let image: id = msg_send![class!(NSImage), alloc];
|
||||||
image.initWithContentsOfFile_(
|
image.initWithContentsOfFile_(NSString::alloc(nil).init_str("test.jpeg"));
|
||||||
NSString::alloc(nil).init_str("/Users/rtfeldman/Downloads/test.jpeg"),
|
|
||||||
);
|
|
||||||
let _size = image.size();
|
let _size = image.size();
|
||||||
|
|
||||||
let string = NSString::alloc(nil).init_str("Test String");
|
let string = NSString::alloc(nil).init_str("Test String");
|
||||||
|
|
|
@ -587,10 +587,7 @@ impl Project {
|
||||||
cx.spawn(move |this, cx| Self::send_buffer_ordered_messages(this, rx, cx))
|
cx.spawn(move |this, cx| Self::send_buffer_ordered_messages(this, rx, cx))
|
||||||
.detach();
|
.detach();
|
||||||
let tasks = Inventory::new(cx);
|
let tasks = Inventory::new(cx);
|
||||||
let global_snippets_dir = paths::config_dir().join("snippets");
|
let snippets = SnippetProvider::new(fs.clone(), BTreeSet::from_iter([]), cx);
|
||||||
let snippets =
|
|
||||||
SnippetProvider::new(fs.clone(), BTreeSet::from_iter([global_snippets_dir]), cx);
|
|
||||||
|
|
||||||
let worktree_store = cx.new_model(|_| WorktreeStore::local(false, fs.clone()));
|
let worktree_store = cx.new_model(|_| WorktreeStore::local(false, fs.clone()));
|
||||||
cx.subscribe(&worktree_store, Self::on_worktree_store_event)
|
cx.subscribe(&worktree_store, Self::on_worktree_store_event)
|
||||||
.detach();
|
.detach();
|
||||||
|
@ -875,9 +872,8 @@ impl Project {
|
||||||
let this = cx.new_model(|cx| {
|
let this = cx.new_model(|cx| {
|
||||||
let replica_id = response.payload.replica_id as ReplicaId;
|
let replica_id = response.payload.replica_id as ReplicaId;
|
||||||
let tasks = Inventory::new(cx);
|
let tasks = Inventory::new(cx);
|
||||||
let global_snippets_dir = paths::config_dir().join("snippets");
|
|
||||||
let snippets =
|
let snippets = SnippetProvider::new(fs.clone(), BTreeSet::from_iter([]), cx);
|
||||||
SnippetProvider::new(fs.clone(), BTreeSet::from_iter([global_snippets_dir]), cx);
|
|
||||||
|
|
||||||
let mut worktrees = Vec::new();
|
let mut worktrees = Vec::new();
|
||||||
for worktree in response.payload.worktrees {
|
for worktree in response.payload.worktrees {
|
||||||
|
|
|
@ -15,6 +15,7 @@ fs.workspace = true
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
parking_lot.workspace = true
|
parking_lot.workspace = true
|
||||||
|
paths.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
snippet.workspace = true
|
snippet.workspace = true
|
||||||
|
|
|
@ -130,8 +130,29 @@ async fn initial_scan(
|
||||||
pub struct SnippetProvider {
|
pub struct SnippetProvider {
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
snippets: HashMap<SnippetKind, BTreeMap<PathBuf, Vec<Arc<Snippet>>>>,
|
snippets: HashMap<SnippetKind, BTreeMap<PathBuf, Vec<Arc<Snippet>>>>,
|
||||||
|
watch_tasks: Vec<Task<Result<()>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Watches global snippet directory, is created just once and reused across multiple projects
|
||||||
|
struct GlobalSnippetWatcher(Model<SnippetProvider>);
|
||||||
|
|
||||||
|
impl GlobalSnippetWatcher {
|
||||||
|
fn new(fs: Arc<dyn Fs>, cx: &mut AppContext) -> Self {
|
||||||
|
let global_snippets_dir = paths::config_dir().join("snippets");
|
||||||
|
let provider = cx.new_model(|_cx| SnippetProvider {
|
||||||
|
fs,
|
||||||
|
snippets: Default::default(),
|
||||||
|
watch_tasks: vec![],
|
||||||
|
});
|
||||||
|
provider.update(cx, |this, cx| {
|
||||||
|
this.watch_directory(&global_snippets_dir, cx)
|
||||||
|
});
|
||||||
|
Self(provider)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl gpui::Global for GlobalSnippetWatcher {}
|
||||||
|
|
||||||
impl SnippetProvider {
|
impl SnippetProvider {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
|
@ -139,29 +160,29 @@ impl SnippetProvider {
|
||||||
cx: &mut AppContext,
|
cx: &mut AppContext,
|
||||||
) -> Model<Self> {
|
) -> Model<Self> {
|
||||||
cx.new_model(move |cx| {
|
cx.new_model(move |cx| {
|
||||||
|
if !cx.has_global::<GlobalSnippetWatcher>() {
|
||||||
|
let global_watcher = GlobalSnippetWatcher::new(fs.clone(), cx);
|
||||||
|
cx.set_global(global_watcher);
|
||||||
|
}
|
||||||
let mut this = Self {
|
let mut this = Self {
|
||||||
fs,
|
fs,
|
||||||
|
watch_tasks: Vec::new(),
|
||||||
snippets: Default::default(),
|
snippets: Default::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut task_handles = vec![];
|
|
||||||
for dir in dirs_to_watch {
|
for dir in dirs_to_watch {
|
||||||
task_handles.push(this.watch_directory(&dir, cx));
|
this.watch_directory(&dir, cx);
|
||||||
}
|
}
|
||||||
cx.spawn(|_, _| async move {
|
|
||||||
futures::future::join_all(task_handles).await;
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
|
|
||||||
this
|
this
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add directory to be watched for content changes
|
/// Add directory to be watched for content changes
|
||||||
fn watch_directory(&mut self, path: &Path, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
fn watch_directory(&mut self, path: &Path, cx: &mut ModelContext<Self>) {
|
||||||
let path: Arc<Path> = Arc::from(path);
|
let path: Arc<Path> = Arc::from(path);
|
||||||
|
|
||||||
cx.spawn(|this, mut cx| async move {
|
self.watch_tasks.push(cx.spawn(|this, mut cx| async move {
|
||||||
let fs = this.update(&mut cx, |this, _| this.fs.clone())?;
|
let fs = this.update(&mut cx, |this, _| this.fs.clone())?;
|
||||||
let watched_path = path.clone();
|
let watched_path = path.clone();
|
||||||
let watcher = fs.watch(&watched_path, Duration::from_secs(1));
|
let watcher = fs.watch(&watched_path, Duration::from_secs(1));
|
||||||
|
@ -177,10 +198,10 @@ impl SnippetProvider {
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lookup_snippets<'a>(
|
fn lookup_snippets<'a, const LOOKUP_GLOBALS: bool>(
|
||||||
&'a self,
|
&'a self,
|
||||||
language: &'a SnippetKind,
|
language: &'a SnippetKind,
|
||||||
cx: &AppContext,
|
cx: &AppContext,
|
||||||
|
@ -193,6 +214,16 @@ impl SnippetProvider {
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flat_map(|(_, snippets)| snippets.into_iter())
|
.flat_map(|(_, snippets)| snippets.into_iter())
|
||||||
.collect();
|
.collect();
|
||||||
|
if LOOKUP_GLOBALS {
|
||||||
|
if let Some(global_watcher) = cx.try_global::<GlobalSnippetWatcher>() {
|
||||||
|
user_snippets.extend(
|
||||||
|
global_watcher
|
||||||
|
.0
|
||||||
|
.read(cx)
|
||||||
|
.lookup_snippets::<false>(language, cx),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let Some(registry) = SnippetRegistry::try_global(cx) else {
|
let Some(registry) = SnippetRegistry::try_global(cx) else {
|
||||||
return user_snippets;
|
return user_snippets;
|
||||||
|
@ -205,11 +236,11 @@ impl SnippetProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn snippets_for(&self, language: SnippetKind, cx: &AppContext) -> Vec<Arc<Snippet>> {
|
pub fn snippets_for(&self, language: SnippetKind, cx: &AppContext) -> Vec<Arc<Snippet>> {
|
||||||
let mut requested_snippets = self.lookup_snippets(&language, cx);
|
let mut requested_snippets = self.lookup_snippets::<true>(&language, cx);
|
||||||
|
|
||||||
if language.is_some() {
|
if language.is_some() {
|
||||||
// Look up global snippets as well.
|
// Look up global snippets as well.
|
||||||
requested_snippets.extend(self.lookup_snippets(&None, cx));
|
requested_snippets.extend(self.lookup_snippets::<true>(&None, cx));
|
||||||
}
|
}
|
||||||
requested_snippets
|
requested_snippets
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue