- indexer.rs: embedder mutex is now locked only around each chunk's embed_document() call instead of the whole file's chunk loop, so a slow MIGraphX JIT compile on one file no longer blocks every query for minutes - sync_ext.rs (new)/indexer.rs/main.rs/serve.rs: added MutexExt::lock_recover(), replacing every .lock().unwrap() so a panic on one thread (poisoning the mutex) logs a warning and recovers instead of cascading into every future lock().unwrap() call - store.rs: save_index now writes to a .tmp sibling and renames atomically into place; Store::open recovers from an index that fails to load by wiping it and the SQLite files/chunks tables so the next scan rebuilds from scratch, instead of refusing to start at all. Note: this recovery only catches corruption usearch's loader reports as Err — verified experimentally that sufficiently garbled input segfaults the process instead, which no Rust-side handling can catch; the atomic save is what actually prevents the realistic mid-crash corruption case from arising - breadsearch/src/main.rs: GUI query-result wait switched from glib::idle_add_local (re-invoked every main-loop tick, pegging a core for the whole wait) to a 15ms glib::timeout_add_local poll - breadsearch/Cargo.toml: bread-theme pin bumped v0.2.8 -> v0.2.10 to match the rest of the family
41 lines
1.8 KiB
Rust
41 lines
1.8 KiB
Rust
//! Poison-tolerant `Mutex` locking.
|
|
//!
|
|
//! A panic on any thread while holding `SharedState::store` or
|
|
//! `SharedState::embedder` (e.g. an untrapped ONNX Runtime panic — only PDF
|
|
//! extraction is `catch_unwind`-guarded, see `extract.rs`) poisons the
|
|
//! `Mutex`. Every subsequent plain `.lock().unwrap()` — in the indexer *and*
|
|
//! in every query handler in `serve.rs` — would then immediately panic too,
|
|
//! silently bricking indexing and search until the daemon is restarted by
|
|
//! hand.
|
|
//!
|
|
//! `Mutex` poisoning exists to flag "the data guarded by this lock might be
|
|
//! in an inconsistent state," but every lock scope in this crate is a short,
|
|
//! single-step SQLite call or usearch operation — nothing here spans a
|
|
//! multi-step invariant across a single `lock()` call — so recovering the
|
|
//! guard and logging loudly is a reasonable trade here: continuing to serve
|
|
//! (and re-attempting the operation that panicked, on the next file/query)
|
|
//! beats a daemon that silently stops answering everything after one panic.
|
|
|
|
use std::sync::{Mutex, MutexGuard};
|
|
|
|
pub trait MutexExt<T> {
|
|
/// Like `.lock().unwrap()`, but recovers from a poisoned mutex instead
|
|
/// of panicking again — logs once per recovery so it's visible in the
|
|
/// daemon's own output, not just silently swallowed.
|
|
fn lock_recover(&self) -> MutexGuard<'_, T>;
|
|
}
|
|
|
|
impl<T> MutexExt<T> for Mutex<T> {
|
|
fn lock_recover(&self) -> MutexGuard<'_, T> {
|
|
match self.lock() {
|
|
Ok(guard) => guard,
|
|
Err(poisoned) => {
|
|
eprintln!(
|
|
"breadmill: WARNING: a mutex was poisoned by a panic on another thread; \
|
|
recovering it and continuing instead of cascading the panic"
|
|
);
|
|
poisoned.into_inner()
|
|
}
|
|
}
|
|
}
|
|
}
|