Skip to content

Commit 14ba273

Browse files
committed
Fix file_viewer: surface SearchStatus::Cancelled to the FE on cancel
search_cancel set the cancel flag and immediately nulled session.search. That made the Cancelled state in the spawned search thread unobservable: search_poll saw session.search == None and returned Idle, erasing the 'search was cancelled' signal the FE needs to distinguish 'completed naturally' from 'aborted partway'. The Cancelled variant existed but had no public path back to the caller. Stop nulling session.search on cancel. The thread observes the flag, exits its loop, and writes SearchStatus::Cancelled to the shared status mutex. Subsequent polls see it. The next search_start atomically replaces session.search with a fresh state, so the Cancelled status is naturally cleared when a new search begins (search_start already calls search_cancel first, so the contract holds end-to-end). Update search_cancel_works to assert the observable Cancelled transition instead of the previous 'Idle (because state was cleared)' behaviour.
1 parent 36487e5 commit 14ba273

2 files changed

Lines changed: 25 additions & 5 deletions

File tree

apps/desktop/src-tauri/src/file_viewer/session.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,12 @@ pub fn search_poll(session_id: &str, since_index: usize) -> Result<SearchPollRes
391391
}
392392

393393
/// Cancels an ongoing search.
394+
///
395+
/// Sets the cancel flag and leaves the `SearchState` in place so the spawned
396+
/// search thread can write the final `SearchStatus::Cancelled` and so
397+
/// subsequent `search_poll` calls surface that transition to the FE. The
398+
/// `SearchState` is replaced atomically the next time `search_start` is called
399+
/// (which itself begins by calling this function, then installs a fresh state).
394400
pub fn search_cancel(session_id: &str) -> Result<(), ViewerError> {
395401
let mut sessions = SESSIONS.lock_ignore_poison();
396402
let session = sessions
@@ -400,7 +406,6 @@ pub fn search_cancel(session_id: &str) -> Result<(), ViewerError> {
400406
if let Some(search) = &session.search {
401407
search.cancel.store(true, Ordering::Relaxed);
402408
}
403-
session.search = None;
404409

405410
Ok(())
406411
}

apps/desktop/src-tauri/src/file_viewer/session_test.rs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -164,12 +164,27 @@ fn search_cancel_works() {
164164

165165
session::search_start(sid, "hello".to_string()).unwrap();
166166

167-
// Cancel immediately
167+
// Cancel immediately. The cancel flag is set synchronously; the search
168+
// thread will see it on its next iteration and exit, writing the final
169+
// `SearchStatus::Cancelled` to the shared status mutex.
168170
session::search_cancel(sid).unwrap();
169171

170-
// Poll should show cancelled or idle (since we removed the search state)
171-
let poll = session::search_poll(sid, 0).unwrap();
172-
assert!(matches!(poll.status, SearchStatus::Idle));
172+
// Poll until the thread observes the cancel and transitions to Cancelled.
173+
let mut saw_cancelled = false;
174+
for _ in 0..200 {
175+
let poll = session::search_poll(sid, 0).unwrap();
176+
if matches!(poll.status, SearchStatus::Cancelled) {
177+
saw_cancelled = true;
178+
break;
179+
}
180+
assert!(
181+
matches!(poll.status, SearchStatus::Running),
182+
"expected Running or Cancelled while cancellation propagates, got {:?}",
183+
poll.status
184+
);
185+
thread::sleep(Duration::from_millis(10));
186+
}
187+
assert!(saw_cancelled, "search did not transition to Cancelled in time");
173188

174189
session::close_session(sid).unwrap();
175190
cleanup(&dir);

0 commit comments

Comments
 (0)