search: New old search implementation (#39956)

This is an in-progress work on changing how task scheduler affects
performance of project search. Instead of relying on tasks being
executed at a discretion of the task scheduler, we want to experiment
with having a set of "agents" that prioritize driving in-progress
project search matches to completion over pushing the whole thing to
completion. This should hopefully significantly improve throughput &
latency of project search.

Release Notes:

- Improved project search performance

---------

Co-authored-by: Smit Barmase <smit@zed.dev>
Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
This commit is contained in:
Piotr Osiewicz
2025-10-20 16:40:02 +02:00
committed by GitHub
co-authored by Smit Barmase Smit Barmase
parent 85c2aa7325
commit 7c4fb5a899
11 changed files with 1023 additions and 389 deletions
+8 -63
View File
@@ -1,14 +1,12 @@
use crate::{
ProjectItem as _, ProjectPath,
ProjectPath,
lsp_store::OpenLspBufferHandle,
search::SearchQuery,
worktree_store::{WorktreeStore, WorktreeStoreEvent},
};
use anyhow::{Context as _, Result, anyhow};
use client::Client;
use collections::{HashMap, HashSet, hash_map};
use fs::Fs;
use futures::{Future, FutureExt as _, StreamExt, channel::oneshot, future::Shared};
use futures::{Future, FutureExt as _, channel::oneshot, future::Shared};
use gpui::{
App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, Subscription, Task, WeakEntity,
};
@@ -23,8 +21,8 @@ use rpc::{
AnyProtoClient, ErrorCode, ErrorExt as _, TypedEnvelope,
proto::{self},
};
use smol::channel::Receiver;
use std::{io, pin::pin, sync::Arc, time::Instant};
use std::{io, sync::Arc, time::Instant};
use text::{BufferId, ReplicaId};
use util::{ResultExt as _, TryFutureExt, debug_panic, maybe, rel_path::RelPath};
use worktree::{File, PathChange, ProjectEntryId, Worktree, WorktreeId};
@@ -972,6 +970,10 @@ impl BufferStore {
.filter_map(|buffer| buffer.upgrade())
}
pub(crate) fn is_searchable(&self, id: &BufferId) -> bool {
!self.non_searchable_buffers.contains(&id)
}
pub fn loading_buffers(
&self,
) -> impl Iterator<Item = (&ProjectPath, impl Future<Output = Result<Entity<Buffer>>>)> {
@@ -1096,63 +1098,6 @@ impl BufferStore {
Some(())
}
pub fn find_search_candidates(
&mut self,
query: &SearchQuery,
mut limit: usize,
fs: Arc<dyn Fs>,
cx: &mut Context<Self>,
) -> Receiver<Entity<Buffer>> {
let (tx, rx) = smol::channel::unbounded();
let mut open_buffers = HashSet::default();
let mut unnamed_buffers = Vec::new();
for handle in self.buffers() {
let buffer = handle.read(cx);
if self.non_searchable_buffers.contains(&buffer.remote_id()) {
continue;
} else if let Some(entry_id) = buffer.entry_id(cx) {
open_buffers.insert(entry_id);
} else {
limit = limit.saturating_sub(1);
unnamed_buffers.push(handle)
};
}
const MAX_CONCURRENT_BUFFER_OPENS: usize = 64;
let project_paths_rx = self
.worktree_store
.update(cx, |worktree_store, cx| {
worktree_store.find_search_candidates(query.clone(), limit, open_buffers, fs, cx)
})
.chunks(MAX_CONCURRENT_BUFFER_OPENS);
cx.spawn(async move |this, cx| {
for buffer in unnamed_buffers {
tx.send(buffer).await.ok();
}
let mut project_paths_rx = pin!(project_paths_rx);
while let Some(project_paths) = project_paths_rx.next().await {
let buffers = this.update(cx, |this, cx| {
project_paths
.into_iter()
.map(|project_path| this.open_buffer(project_path, cx))
.collect::<Vec<_>>()
})?;
for buffer_task in buffers {
if let Some(buffer) = buffer_task.await.log_err()
&& tx.send(buffer).await.is_err()
{
return anyhow::Ok(());
}
}
}
anyhow::Ok(())
})
.detach();
rx
}
fn on_buffer_event(
&mut self,
buffer: Entity<Buffer>,