Cancel stale body fetch requests when selection changes quickly

Uses Arc<AtomicU32> to track the most recently wanted email sequence
number. The worker skips fetch requests that no longer match, avoiding
wasted network calls when scrolling through emails rapidly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Shautvast 2026-02-18 08:59:42 +01:00
parent 7269eca3e9
commit 0eda9045cd

View file

@ -1,5 +1,6 @@
use std::io::{Error, Stdout}; use std::io::{Error, Stdout};
use std::sync::mpsc; use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::{mpsc, Arc};
use std::thread; use std::thread;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use crossterm::event; use crossterm::event;
@ -49,6 +50,7 @@ fn worker_loop(
config: Config, config: Config,
cmd_rx: mpsc::Receiver<WorkerCmd>, cmd_rx: mpsc::Receiver<WorkerCmd>,
result_tx: mpsc::Sender<WorkerResult>, result_tx: mpsc::Sender<WorkerResult>,
wanted_body_seq: Arc<AtomicU32>,
) { ) {
let mut session: Option<connect::ImapSession> = None; let mut session: Option<connect::ImapSession> = None;
@ -63,6 +65,10 @@ fn worker_loop(
let _ = result_tx.send(WorkerResult::FetchedMore(result)); let _ = result_tx.send(WorkerResult::FetchedMore(result));
} }
WorkerCmd::FetchBody { seq } => { WorkerCmd::FetchBody { seq } => {
// Skip if a newer body request has been made
if wanted_body_seq.load(Ordering::Relaxed) != seq {
continue;
}
let result = inbox::fetch_body(&mut session, seq, &config); let result = inbox::fetch_body(&mut session, seq, &config);
let _ = result_tx.send(WorkerResult::Body { seq, result }); let _ = result_tx.send(WorkerResult::Body { seq, result });
} }
@ -83,9 +89,11 @@ pub fn main(config: &Config, terminal: &mut Terminal<CrosstermBackend<Stdout>>)
let (cmd_tx, cmd_rx) = mpsc::channel(); let (cmd_tx, cmd_rx) = mpsc::channel();
let (result_tx, result_rx) = mpsc::channel(); let (result_tx, result_rx) = mpsc::channel();
let wanted_body_seq = Arc::new(AtomicU32::new(0));
let worker_config = config.clone(); let worker_config = config.clone();
let worker_wanted = Arc::clone(&wanted_body_seq);
let worker = thread::spawn(move || { let worker = thread::spawn(move || {
worker_loop(worker_config, cmd_rx, result_tx); worker_loop(worker_config, cmd_rx, result_tx, worker_wanted);
}); });
// Send initial refresh // Send initial refresh
@ -129,6 +137,7 @@ pub fn main(config: &Config, terminal: &mut Terminal<CrosstermBackend<Stdout>>)
preview_body.clear(); preview_body.clear();
preview_scroll = 0; preview_scroll = 0;
body_loading = true; body_loading = true;
wanted_body_seq.store(seq, Ordering::Relaxed);
let _ = cmd_tx.send(WorkerCmd::FetchBody { seq }); let _ = cmd_tx.send(WorkerCmd::FetchBody { seq });
} }
} else { } else {
@ -301,6 +310,7 @@ pub fn main(config: &Config, terminal: &mut Terminal<CrosstermBackend<Stdout>>)
preview_body.clear(); preview_body.clear();
preview_scroll = 0; preview_scroll = 0;
body_loading = true; body_loading = true;
wanted_body_seq.store(seq, Ordering::Relaxed);
let _ = cmd_tx.send(WorkerCmd::FetchBody { seq }); let _ = cmd_tx.send(WorkerCmd::FetchBody { seq });
} }
} }
@ -320,6 +330,7 @@ pub fn main(config: &Config, terminal: &mut Terminal<CrosstermBackend<Stdout>>)
preview_body.clear(); preview_body.clear();
preview_scroll = 0; preview_scroll = 0;
body_loading = true; body_loading = true;
wanted_body_seq.store(seq, Ordering::Relaxed);
let _ = cmd_tx.send(WorkerCmd::FetchBody { seq }); let _ = cmd_tx.send(WorkerCmd::FetchBody { seq });
} }
} }
@ -347,6 +358,7 @@ pub fn main(config: &Config, terminal: &mut Terminal<CrosstermBackend<Stdout>>)
preview_body.clear(); preview_body.clear();
preview_scroll = 0; preview_scroll = 0;
body_loading = true; body_loading = true;
wanted_body_seq.store(new_seq, Ordering::Relaxed);
let _ = cmd_tx.send(WorkerCmd::FetchBody { seq: new_seq }); let _ = cmd_tx.send(WorkerCmd::FetchBody { seq: new_seq });
} }
} }