perf(daemon): answer search polls with counts instead of copies

A CLI one-off search leaves its result set in the daemon until someone
forgets it, and an attached TUI made that permanent state expensive:
every 100ms tick asked for the search list twice, and each answer
deep-cloned every result of every search under the context lock just to
sum file counts - stalling the thread that stores incoming results.
Worse, any active or selected search pulled its entire result set over
the socket again each tick, megabytes of JSON repeating what the window
already held.

search.list is now backed by search_file_counts, a walk of the cache
instead of a copy of it; the TUI syncs once per tick and re-fetches a
result set only when the session's file count differs from what it
holds. get_search_results_count reads the same summary, so get --pick
first against a daemon polls a count instead of the full set, and both
session kinds now count files where the local one counted peer
responses.
This commit is contained in:
Michel de Graaf
2026-08-09 22:16:27 +02:00
parent d11326f149
commit 1551fbbf1b
6 changed files with 283 additions and 37 deletions
+29
View File
@@ -156,4 +156,33 @@ impl Client {
.map(|ctx| ctx.searches.clone())
.unwrap_or_default()
}
/// Every registered search and how many files it has collected, without
/// copying the result sets.
///
/// This is the accessor to poll. [`Client::get_all_searches`] clones every
/// result of every search, which for a popular query is megabytes per
/// call — a caller that only wants to know what exists and whether
/// anything new arrived reads the counts instead.
#[must_use]
pub fn search_file_counts(&self) -> Vec<(String, usize)> {
self.context
.read_safe()
.map(|ctx| {
ctx.searches
.iter()
.map(|(query, search)| {
(
query.clone(),
search
.results
.iter()
.map(|result| result.files.len())
.sum(),
)
})
.collect()
})
.unwrap_or_default()
}
}