pub struct Engine {
config: EngineConfig,
sink: RwLock<Option<EventSink>>,
volumes: RwLock<Vec<Arc<VolumeSlot>>>,
threads: Mutex<Vec<JoinHandle<()>>>,
metrics: MetricsHub,
compile_cache: Mutex<Option<(String, QueryOptions, Arc<CompiledQuery>)>>,
_diag_guard: Mutex<Option<SinkGuard>>,
_writer_lock: File,
}Expand description
The multi-volume engine: owns one index per NTFS volume, drives scans and USN tailing, and answers queries. Holds the single-writer lock for its whole lifetime.
Fields§
§config: EngineConfig§sink: RwLock<Option<EventSink>>§volumes: RwLock<Vec<Arc<VolumeSlot>>>§threads: Mutex<Vec<JoinHandle<()>>>§metrics: MetricsHub§compile_cache: Mutex<Option<(String, QueryOptions, Arc<CompiledQuery>)>>Last (text, options) → compiled query. An identical re-issue — a USN-
driven requery of the same text (the RefreshInPlace path) — then skips
parse + compile, which matters most for a heavy regex. Always sound:
the compiled query is a pure function of (text, options) (the date
resolver maps civil dates to ticks independently of the wall clock).
Keying on the whole QueryOptions over-approximates (only case + the
regex mode/scope actually steer compilation) but stays trivially
correct. Engine-wide because compilation is volume-independent.
_diag_guard: Mutex<Option<SinkGuard>>Keeps the diag→EngineError forwarding registered for our lifetime.
_writer_lock: FileExclusive-write handle on {index_dir}\.writer.lock for our whole
lifetime — the OS releases it on process death, so no stale locks.
Implementations§
Source§impl Engine
impl Engine
Sourcepub fn query(
&self,
text: &str,
opt: &QueryOptions,
) -> Result<(ResultSet, QueryTrace), EngineError>
pub fn query( &self, text: &str, opt: &QueryOptions, ) -> Result<(ResultSet, QueryTrace), EngineError>
Run a query against every Ready volume and merge the per-volume, already-sorted id lists into one ordered result set.
Per volume, the previous result is kept (VolumeSlot::last_query);
when the new query provably narrows it and the index generation is
unchanged, the candidate set is the previous hits instead of the
whole index (query::refine) — typing one more letter costs
O(previous hits), not O(index).
§Errors
Returns EngineError::Parse if text is not a valid query, or
EngineError::Compile if a valid query fails to compile (e.g. a bad
regex term).
§Panics
Panics if a Ready volume’s index is absent during the k-way merge —
an invariant the volume thread upholds (a Ready slot always holds an
index).
Source§impl Engine
impl Engine
Sourcepub fn list_ntfs_volumes() -> Vec<String>
pub fn list_ntfs_volumes() -> Vec<String>
Fixed NTFS volumes (“C:”, “D:”, …).
Sourcepub(super) fn save_slot(
&self,
slot: &VolumeSlot,
checkpoint: JournalCheckpoint,
) -> bool
pub(super) fn save_slot( &self, slot: &VolumeSlot, checkpoint: JournalCheckpoint, ) -> bool
Writes the slot’s snapshot (via its SnapshotStore) under the
per-slot save lock and records the saved generations (the flush
dirty check). Returns false on a failed write — already counted and
logged here.
Source§impl Engine
impl Engine
Sourcepub(super) fn volume_thread(self: Arc<Self>, slot: Arc<VolumeSlot>)
pub(super) fn volume_thread(self: Arc<Self>, slot: Arc<VolumeSlot>)
Production wiring: the Windows journal seam (the snapshot seam
lives on the slot, created by index_start).
Sourcepub(super) fn volume_thread_with(
self: Arc<Self>,
slot: Arc<VolumeSlot>,
journal: &mut dyn JournalSource,
)
pub(super) fn volume_thread_with( self: Arc<Self>, slot: Arc<VolumeSlot>, journal: &mut dyn JournalSource, )
Panic firewall: a crashing volume thread must never leave the UI
stuck on “Scanning” with no explanation. The panic itself is logged
(with backtrace) by the diag hook; this converts it into a visible
Failed state. Worker entry with an injectable journal seam —
production passes the Win implementation; worker_tests.rs passes
scripted fakes.
fn volume_thread_inner( self: Arc<Self>, slot: Arc<VolumeSlot>, journal: &mut dyn JournalSource, )
Sourcepub(super) fn maybe_compact(&self, slot: &VolumeSlot)
pub(super) fn maybe_compact(&self, slot: &VolumeSlot)
Compact once the tombstone/garbage thresholds trip (checked per
applied USN batch). The copy builds under a read guard — this
volume thread is the index’s only writer — and the write lock is
held for the swap alone. install_index bumps the structural
generation, hard-staling open result handles.
Source§impl Engine
impl Engine
Sourcepub fn new(config: EngineConfig) -> Result<Arc<Self>, EngineCreateError>
pub fn new(config: EngineConfig) -> Result<Arc<Self>, EngineCreateError>
Create the engine and acquire the single-writer lock on the index directory.
§Errors
Returns EngineCreateError::Io if the index directory cannot be
created, or EngineCreateError::Locked if another engine process
already holds the writer lock (FMF_E_LOCKED).
Sourcefn acquire_writer_lock(index_dir: &Path) -> Result<File, EngineCreateError>
fn acquire_writer_lock(index_dir: &Path) -> Result<File, EngineCreateError>
Cross-process single-writer guard: exclusive write access on
.writer.lock (readers allowed, so a losing process can report the
holder’s pid). Held until drop; the OS frees it on process death.
Sourcepub fn set_event_sink(&self, sink: Option<EventSink>)
pub fn set_event_sink(&self, sink: Option<EventSink>)
Install (or clear with None) the callback that receives every
EngineEvent.
fn emit(&self, ev: EngineEvent)
Sourcepub fn index_start(self: &Arc<Self>, volumes: &[String])
pub fn index_start(self: &Arc<Self>, volumes: &[String])
Begin indexing the given volumes (asynchronous; progress via events).
Idempotent per volume label: clients re-send IndexStart on every
(re)connect and the service also calls this at startup, so a volume
already being indexed is skipped. A duplicate slot would make every
query return that volume’s rows once per copy (search merges all Ready
slots) — the source of the “each result appears N times” bug.
§Panics
Panics if a volume worker thread cannot be spawned.
Sourcepub fn index_start_scope(
self: &Arc<Self>,
roots: &[String],
excludes: &[String],
)
pub fn index_start_scope( self: &Arc<Self>, roots: &[String], excludes: &[String], )
Begin a non-elevated scope-mode index over roots (absolute base
paths), folder-walked and watched in-process without elevation
(ADR-0024). Unlike Self::index_start, the change source is
ReadDirectoryChangesW, not the USN journal, and the snapshot lives
under a single fixed scope label (so a hostile roots entry can
never steer snapshot_path — only the fixed label does). Idempotent:
a second call while the scope slot exists is a no-op.
§Panics
Panics if the volume worker thread cannot be spawned.
Sourcepub fn status(&self) -> Vec<(String, VolumeState, u64)>
pub fn status(&self) -> Vec<(String, VolumeState, u64)>
Per-volume status: (label, state, files scanned so far).
Sourcepub const fn metrics(&self) -> &MetricsHub
pub const fn metrics(&self) -> &MetricsHub
The engine’s metrics hub (counters and the diagnostics ring).
Sourcepub fn index_stats(&self) -> Vec<IndexStats>
pub fn index_stats(&self) -> Vec<IndexStats>
Per-volume memory accounting (perf panel / fmf stats).
Sourcepub fn metrics_snapshot(&self) -> MetricsSnapshot
pub fn metrics_snapshot(&self) -> MetricsSnapshot
Full observability snapshot (JSON-serializable).
Sourcepub fn flush(&self) -> usize
pub fn flush(&self) -> usize
Persist every Ready volume whose generations moved since its last
save (“dirty”), using the tailing thread’s shared checkpoint. The
checkpoint may trail the index by an in-flight batch — the USN
replay on load covers that. Returns the number of snapshots written
(failed writes are counted in snapshot_save_failures and excluded).
Sourcepub fn insert_ready_volume(&self, label: &str, idx: VolumeIndex)
pub fn insert_ready_volume(&self, label: &str, idx: VolumeIndex)
Test/dev helper: register an already-built index as a Ready volume.
The zero checkpoint stands in for a journal position so flush can
exercise the save path on injected volumes.
Sourcepub fn replace_ready_volume(&self, label: &str, idx: VolumeIndex)
pub fn replace_ready_volume(&self, label: &str, idx: VolumeIndex)
Test/dev helper: swap a rebuilt index into an existing Ready volume — the same structural replacement a journal-gone full rescan performs.
§Panics
Panics if no volume with the given label exists.
Auto Trait Implementations§
impl !Freeze for Engine
impl !RefUnwindSafe for Engine
impl Send for Engine
impl Sync for Engine
impl Unpin for Engine
impl UnsafeUnpin for Engine
impl !UnwindSafe for Engine
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
§impl<T> Instrument for T
impl<T> Instrument for T
§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more