Reduce session logging and background churn
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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(())
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user