Skip to content

Commit fb796e7

Browse files
committed
Indexing: Scanning state stuck after replay
- `run_replay_event_loop` handles both replay AND the live event loop, only returning on shutdown. `scanning.store(false)` in `mod.rs` was placed after the `.await`, so it was unreachable during normal operation — the backend reported `scanning: true` for the entire app lifetime after any cold-start replay - Pass `scanning: Arc<AtomicBool>` into `run_replay_event_loop` and clear it at the replay→live transition point, right before entering the live loop - Frontend defense-in-depth: `eventVersion` counter in `index-state.svelte.ts` prevents the `get_index_status` IPC response from overriding state that a more recent event already set
1 parent 415db3f commit fb796e7

File tree

3 files changed

+19
-2
lines changed

3 files changed

+19
-2
lines changed

apps/desktop/src-tauri/src/indexing/event_loop.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,7 @@ pub(super) async fn run_replay_event_loop(
281281
config: ReplayConfig,
282282
fallback_tx: tokio::sync::oneshot::Sender<()>,
283283
watcher_overflow: Option<Arc<AtomicBool>>,
284+
scanning: Arc<AtomicBool>,
284285
) -> Result<(), String> {
285286
let ReplayConfig {
286287
volume_id,
@@ -563,6 +564,9 @@ pub(super) async fn run_replay_event_loop(
563564
]);
564565
DEBUG_STATS.set_phase(super::ActivityPhase::Live, "post-replay");
565566

567+
// Replay done — allow verifier to run and report scanning=false to frontend.
568+
scanning.store(false, Ordering::Relaxed);
569+
566570
log::info!("Replay: switching to live mode");
567571
let mut reconciler = EventReconciler::new();
568572
reconciler.switch_to_live();

apps/desktop/src-tauri/src/indexing/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -404,10 +404,12 @@ impl IndexManager {
404404
},
405405
fallback_tx,
406406
watcher_overflow,
407+
Arc::clone(&scanning),
407408
)
408409
.await;
409410

410-
// Replay done (now in live mode) — allow verifier to run.
411+
// Live event loop ended (shutdown). Clear scanning as a safety net
412+
// (normally cleared inside run_replay_event_loop after replay phase).
411413
scanning.store(false, Ordering::Relaxed);
412414

413415
if let Err(e) = result {

apps/desktop/src/lib/indexing/index-state.svelte.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ let replayEventsProcessed = $state(0)
1818
let replayEstimatedTotal = $state(0)
1919
let replayStartedAt = $state(0)
2020

21+
// Monotonic counter bumped by every scan/replay event. Prevents the `get_index_status`
22+
// IPC response (which can arrive late) from overwriting state that an event already set.
23+
let eventVersion = 0
24+
2125
// Aggregation state
2226
let aggregating = $state(false)
2327
let aggregationPhase = $state('')
@@ -118,6 +122,7 @@ const unlistenHandles: UnlistenFn[] = []
118122
/** Set up listeners for index scan events. Call once during app init. */
119123
export async function initIndexState(): Promise<void> {
120124
const unlistenStarted = await listen<{ volumeId: string }>('index-scan-started', () => {
125+
eventVersion++
121126
scanning = true
122127
resetCounters()
123128
resetAggregation()
@@ -141,6 +146,7 @@ export async function initIndexState(): Promise<void> {
141146
totalDirs: number
142147
durationMs: number
143148
}>('index-scan-complete', (event) => {
149+
eventVersion++
144150
scanning = false
145151
entriesScanned = event.payload.totalEntries
146152
dirsFound = event.payload.totalDirs
@@ -199,6 +205,7 @@ export async function initIndexState(): Promise<void> {
199205
volumeId: string
200206
durationMs: number
201207
}>('index-replay-complete', () => {
208+
eventVersion++
202209
resetReplay()
203210
scanning = false
204211
})
@@ -207,6 +214,10 @@ export async function initIndexState(): Promise<void> {
207214
// Query current status to catch scans already in progress before the frontend loaded.
208215
// The scan starts in Tauri's setup() hook, so the 'index-scan-started' event may fire
209216
// before the frontend's event listeners are registered.
217+
//
218+
// Guard: snapshot eventVersion before the IPC call. If any scan/replay event arrived
219+
// while the response was in flight, the event's state is more recent — skip the IPC result.
220+
const versionBeforeIpc = eventVersion
210221
try {
211222
const status = await invoke<{
212223
initialized: boolean
@@ -216,7 +227,7 @@ export async function initIndexState(): Promise<void> {
216227
indexStatus: unknown
217228
dbFileSize: number | null
218229
}>('get_index_status')
219-
if (status.scanning) {
230+
if (status.scanning && eventVersion === versionBeforeIpc) {
220231
scanning = true
221232
entriesScanned = status.entriesScanned
222233
dirsFound = status.dirsFound

0 commit comments

Comments
 (0)