Reduce session logging and background churn

This commit is contained in:
Ryan Ward
2026-09-02 15:13:24 -05:00
parent 13cbb232ee
commit b115946534
29 changed files with 268 additions and 127 deletions
+1
View File
@@ -49,6 +49,7 @@ strsim.workspace = true
thiserror.workspace = true
tokio = { workspace = true, features = ["rt"] }
futures.workspace = true
fs4.workspace = true
generic-array = "0.14.7"
derivative.workspace = true
galaxy_core.workspace = true
+54 -12
View File
@@ -6,12 +6,14 @@
#![cfg(all(feature = "local_fs", not(target_family = "wasm")))]
use std::collections::{HashMap, HashSet};
use std::fs;
use std::fs::{self, File, OpenOptions};
use std::path::{Path, PathBuf};
use std::time::Duration;
use anyhow::{Context, Result};
use galaxy_core::paths::state_dir;
use galaxy_util::standardized_path::StandardizedPath;
use galaxyui_core::r#async::Timer;
use galaxyui_core::{Entity, ModelContext, SingletonEntity};
use itertools::Itertools;
use repo_metadata::{RepoMetadataEvent, RepositoryIdentifier};
@@ -29,6 +31,7 @@ const MAX_INDEXED_BODY_BYTES: usize = 256_000;
const INDEX_DIRECTORY_NAME: &str = "local_project_indices";
const CURRENT_FILE_NAME: &str = "CURRENT";
const METADATA_FILE_NAME: &str = "metadata.json";
const INDEX_LOCK_RETRY_INTERVAL: Duration = Duration::from_millis(50);
// Field weights intentionally prioritize symbols and paths over implementation text.
define_search_schema!(
@@ -240,6 +243,7 @@ impl LocalProjectIndexManager {
});
let should_rebuild = manager.pending_rebuilds.remove(&root_path);
cleanup_old_generations(&manager.storage_root, &root_path);
drop(built_index.repository_lock);
if should_rebuild {
manager.start_rebuild(root_path, ctx);
}
@@ -286,7 +290,6 @@ impl LocalProjectIndexManager {
});
}
let should_rebuild = self.pending_rebuilds.remove(&root_path);
cleanup_old_generations(&self.storage_root, &root_path);
if should_rebuild {
self.start_rebuild(root_path, ctx);
}
@@ -350,7 +353,7 @@ impl LocalProjectIndexManager {
/// Removes a local project index and its persisted generations.
pub fn remove_index_for_path(&mut self, root_path: PathBuf, ctx: &mut ModelContext<Self>) {
let root_path = dunce::canonicalize(&root_path).unwrap_or(root_path);
self.remove_index(&root_path);
self.remove_index(&root_path, ctx);
ctx.emit(LocalProjectIndexEvent::IndexRemoved { root_path });
}
@@ -374,7 +377,7 @@ impl LocalProjectIndexManager {
let local_path = path.to_local_path_lossy();
let root_path = dunce::canonicalize(&local_path).unwrap_or(local_path);
if self.statuses.contains_key(&root_path) {
self.remove_index(&root_path);
self.remove_index(&root_path, ctx);
ctx.emit(LocalProjectIndexEvent::IndexRemoved { root_path });
}
return;
@@ -408,19 +411,30 @@ impl LocalProjectIndexManager {
}
}
fn remove_index(&mut self, root_path: &Path) {
fn remove_index(&mut self, root_path: &Path, ctx: &mut ModelContext<Self>) {
let root_path = dunce::canonicalize(root_path).unwrap_or_else(|_| root_path.to_path_buf());
self.indices.remove(&root_path);
self.statuses.remove(&root_path);
self.pending_rebuilds.remove(&root_path);
let epoch = self.rebuild_epochs.entry(root_path.clone()).or_default();
*epoch += 1;
let directory = repository_storage_directory(&self.storage_root, &root_path);
if let Err(error) = fs::remove_dir_all(directory) {
if error.kind() != std::io::ErrorKind::NotFound {
log::warn!("Failed to remove local project index: {error}");
}
}
let storage_root = self.storage_root.clone();
ctx.spawn(
async move {
let _repository_lock = acquire_repository_lock(&storage_root, &root_path).await?;
let directory = repository_storage_directory(&storage_root, &root_path);
match fs::remove_dir_all(directory) {
Ok(()) => Ok(()),
Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(()),
Err(error) => Err(anyhow::Error::new(error)),
}
},
|_, result, _| {
if let Err(error) = result {
log::warn!("Failed to remove local project index: {error:#}");
}
},
);
}
fn restore_persisted_indices(&mut self) {
@@ -503,6 +517,7 @@ struct BuiltLocalProjectIndex {
searcher: SimpleFullTextSearcher<LocalProjectIndexSchema>,
generation: String,
generation_directory: PathBuf,
repository_lock: File,
}
async fn build_persisted_index(
@@ -511,6 +526,10 @@ async fn build_persisted_index(
) -> std::result::Result<(PathBuf, BuiltLocalProjectIndex, usize), (PathBuf, anyhow::Error)> {
let error_root_path = root_path.clone();
let result = async {
// A desktop app and one or more CLI processes can share this storage root. Keep the
// repository snapshot, generation publication, and cleanup in one exclusive section so
// one process cannot delete another process's in-progress Tantivy generation.
let repository_lock = acquire_repository_lock(&storage_root, &root_path).await?;
let documents = build_documents(&root_path).await?;
let index_directory = repository_storage_directory(&storage_root, &root_path);
fs::create_dir_all(index_directory.join("generations"))?;
@@ -565,6 +584,7 @@ async fn build_persisted_index(
searcher,
generation,
generation_directory,
repository_lock,
},
documents.len(),
))
@@ -643,9 +663,31 @@ fn repository_storage_directory(storage_root: &Path, root_path: &Path) -> PathBu
storage_root.join(format_storage_directory_name(root_path))
}
fn repository_lock_path(storage_root: &Path, root_path: &Path) -> PathBuf {
storage_root.join(format!("{}.lock", format_storage_directory_name(root_path)))
}
async fn acquire_repository_lock(storage_root: &Path, root_path: &Path) -> Result<File> {
fs::create_dir_all(storage_root)?;
let lock_path = repository_lock_path(storage_root, root_path);
let lock_file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(&lock_path)
.with_context(|| format!("Failed to open local index lock {}", lock_path.display()))?;
loop {
if fs4::fs_std::FileExt::try_lock_exclusive(&lock_file)? {
return Ok(lock_file);
}
Timer::after(INDEX_LOCK_RETRY_INTERVAL).await;
}
}
fn publish_generation(storage_root: &Path, root_path: &Path, generation: &str) -> Result<()> {
let index_directory = repository_storage_directory(storage_root, root_path);
let temporary_current = index_directory.join(format!(".{CURRENT_FILE_NAME}.tmp"));
let temporary_current =
index_directory.join(format!(".{CURRENT_FILE_NAME}.{}.tmp", uuid::Uuid::new_v4()));
fs::write(&temporary_current, generation.as_bytes())?;
fs::rename(&temporary_current, index_directory.join(CURRENT_FILE_NAME))?;
Ok(())
+15
View File
@@ -22,6 +22,10 @@ const CLI_LOG_SUBDIRECTORY: &str = "oz";
const SESSION_LOG_SUBDIRECTORY: &str = "session-logs";
const TEMP_LOG_FILE_SUFFIX: &str = "old.temp";
const INPUT_CLASSIFIER_LOG_TARGET: &str = "input_classifier";
const FILE_WATCHER_LOG_TARGET: &str = "notify";
const FILE_WATCHER_DEBOUNCER_LOG_TARGET: &str = "notify_debouncer_full";
const GLOBSET_LOG_TARGET: &str = "globset";
const IGNORE_WALKER_LOG_TARGET: &str = "ignore";
const TERMINAL_ANSI_HANDLER_LOG_TARGET: &str =
"galaxy::terminal::model::grid::grid_handler::ansi_handler";
@@ -593,6 +597,17 @@ fn init_internal(
// info/debug records. Keep initialization failures and classification errors, but omit the
// noisy pre-submission decision trail from full-session logs.
.filter(Some(INPUT_CLASSIFIER_LOG_TARGET), LevelFilter::Warn)
// notify logs every platform event and the debouncer logs every raw event at TRACE. A
// repository index can generate hundreds of thousands of these records, multiplying the
// underlying filesystem work with synchronous formatting and log-file writes. Keep
// watcher lifecycle information and all warnings while suppressing per-event payloads.
.filter(Some(FILE_WATCHER_LOG_TARGET), LevelFilter::Info)
.filter(Some(FILE_WATCHER_DEBOUNCER_LOG_TARGET), LevelFilter::Info)
// globset and ignore describe every compiled glob and opened ignore file at DEBUG. Those
// records are useful to their crate maintainers but scale with repository traversal and do
// not help diagnose Galaxy sessions. Preserve informational failures and summaries.
.filter(Some(GLOBSET_LOG_TARGET), LevelFilter::Info)
.filter(Some(IGNORE_WALKER_LOG_TARGET), LevelFilter::Info)
// Since we always pair an insertion with a deletion to avoid duplicate,
// tantivy will log a lot of warnings for deleting a non-existing doc.
.filter(Some("tantivy"), LevelFilter::Error)
@@ -1866,7 +1866,7 @@ impl EventLoop {
// will be dispatched to the active window as TypedCharacters/IME events.
let proxy = self.proxy.clone();
let on_input = Box::new(move |input: SoftKeyboardInput| {
log::debug!("Soft keyboard callback received input: {:?}", input);
log::debug!("Soft keyboard callback received input");
if let Err(e) = proxy.send_event(CustomEvent::SoftKeyboardInput(input)) {
log::error!("Failed to send SoftKeyboardInput event: {:?}", e);
}
@@ -1471,7 +1471,6 @@ impl Element for NewScrollable {
app: &AppContext,
) -> bool {
let Some(z_index) = self.child_max_z_index else {
log::warn!("Tried to handle event in scrollable before the element is painted");
return false;
};
@@ -458,9 +458,8 @@ impl Element for Resizable {
}
crate::Event::MouseMoved { position, .. } => {
// A mouse event over the dragbar should set the cursor
let Some(z_index) = self.z_index() else {
log::warn!("self.z_index() was None in `Resizable`");
return false;
let Some(z_index) = self.dragbar.z_index else {
return child_handled;
};
let hovering_dragbar = self.is_mouse_hovering_dragbar(ctx, *position);
let was_already_hovering =
@@ -1471,7 +1471,6 @@ impl Element for NewScrollable {
app: &AppContext,
) -> bool {
let Some(z_index) = self.child_max_z_index else {
log::warn!("Tried to handle event in scrollable before the element is painted");
return false;
};
@@ -458,9 +458,8 @@ impl Element for Resizable {
}
crate::Event::MouseMoved { position, .. } => {
// A mouse event over the dragbar should set the cursor
let Some(z_index) = self.z_index() else {
log::warn!("self.z_index() was None in `Resizable`");
return false;
let Some(z_index) = self.dragbar.z_index else {
return child_handled;
};
let hovering_dragbar = self.is_mouse_hovering_dragbar(ctx, *position);
let was_already_hovering =
+2 -2
View File
@@ -154,7 +154,7 @@ impl JsonRpcService {
break;
}
log::trace!("JSON-RPC: received message: {message}");
log::trace!("JSON-RPC: received {} bytes", message.len());
if let Err(e) = Self::handle_message(
&transport,
&message,
@@ -319,7 +319,7 @@ impl JsonRpcService {
method: String,
params: Value,
) -> Result<Value> {
log::trace!("Sending request {request_id}: {method}: {params}");
log::trace!("Sending request {request_id}: {method}");
let request = Request {
jsonrpc: JSON_RPC_VERSION,
+4 -13
View File
@@ -433,11 +433,7 @@ pub trait Setting {
};
match <Self::Value as SettingsValue>::from_file_value(&json_value) {
Some(val) => {
log::debug!(
"Loaded {} from settings file; value: {:?}",
Self::setting_name(),
val
);
log::debug!("Loaded {} from settings file", Self::setting_name());
return Some(val);
}
None => {
@@ -453,11 +449,7 @@ pub trait Setting {
match serde_json::from_str(&value) {
Ok(val) => {
log::debug!(
"Loaded {} from user defaults; value: {:?}",
Self::setting_name(),
val
);
log::debug!("Loaded {} from user defaults", Self::setting_name());
Some(val)
}
Err(err) => {
@@ -516,10 +508,9 @@ pub trait Setting {
if !stored_value_matches {
log::debug!(
"Writing new value of {} to storage; key: {}; value: {:?}",
"Writing new value of {} to storage; key: {}",
Self::setting_name(),
key,
value
key
);
let _ = preferences.write_value_with_hierarchy(
key,
+1 -5
View File
@@ -254,11 +254,7 @@ macro_rules! define_setting {
},
None => {
let default_value = Self::default_value();
log::debug!(
"Initializing {} to default value: {:?}",
Self::setting_name(),
default_value
);
log::debug!("Initializing {} to its default value", Self::setting_name());
Self {
inner: default_value,
is_explicitly_set: false,