From 7fef812b1e97bd1fe382263f550f480df6be89ef Mon Sep 17 00:00:00 2001 From: link2xt Date: Wed, 5 Nov 2025 22:15:22 +0000 Subject: [PATCH] refactor(imap): move resync request from Context to Imap For multiple transports we will need to run multiple IMAP clients in parallel. UID validity change detected by one IMAP client should not result in UID resync for another IMAP client. --- src/configure.rs | 8 -------- src/context.rs | 12 +----------- src/imap.rs | 14 ++++++++++++-- src/imap/select_folder.rs | 4 ++-- src/imap/session.rs | 4 ++++ src/scheduler.rs | 6 ++---- 6 files changed, 21 insertions(+), 27 deletions(-) diff --git a/src/configure.rs b/src/configure.rs index 80f6ddc7f..59809591a 100644 --- a/src/configure.rs +++ b/src/configure.rs @@ -565,14 +565,6 @@ async fn configure(ctx: &Context, param: &EnteredLoginParam) -> Result>, - /// IMAP UID resync request. - pub(crate) resync_request: AtomicBool, - /// Notify about new messages. /// /// This causes [`Context::wait_next_msgs`] to wake up. @@ -457,7 +454,6 @@ impl Context { scheduler: SchedulerState::new(), ratelimit: RwLock::new(Ratelimit::new(Duration::new(60, 0), 6.0)), // Allow at least 1 message every 10 seconds + a burst of 6. quota: RwLock::new(None), - resync_request: AtomicBool::new(false), new_msgs_notify, server_id: RwLock::new(None), metadata: RwLock::new(None), @@ -616,12 +612,6 @@ impl Context { Ok(()) } - pub(crate) async fn schedule_resync(&self) -> Result<()> { - self.resync_request.store(true, Ordering::Relaxed); - self.scheduler.interrupt_inbox().await; - Ok(()) - } - /// Returns a reference to the underlying SQL instance. /// /// Warning: this is only here for testing, not part of the public API. diff --git a/src/imap.rs b/src/imap.rs index 7826558af..a78643374 100644 --- a/src/imap.rs +++ b/src/imap.rs @@ -104,6 +104,12 @@ pub(crate) struct Imap { /// immediately after logging in or returning an error in response to LOGIN command /// due to internal server error. ratelimit: Ratelimit, + + /// IMAP UID resync request sender. + pub(crate) resync_request_sender: async_channel::Sender<()>, + + /// IMAP UID resync request receiver. + pub(crate) resync_request_receiver: async_channel::Receiver<()>, } #[derive(Debug)] @@ -254,6 +260,7 @@ impl Imap { oauth2: bool, idle_interrupt_receiver: Receiver<()>, ) -> Self { + let (resync_request_sender, resync_request_receiver) = async_channel::bounded(1); Imap { idle_interrupt_receiver, addr: addr.to_string(), @@ -268,6 +275,8 @@ impl Imap { conn_backoff_ms: 0, // 1 connection per minute + a burst of 2. ratelimit: Ratelimit::new(Duration::new(120, 0), 2.0), + resync_request_sender, + resync_request_receiver, } } @@ -392,6 +401,7 @@ impl Imap { match login_res { Ok(mut session) => { let capabilities = determine_capabilities(&mut session).await?; + let resync_request_sender = self.resync_request_sender.clone(); let session = if capabilities.can_compress { info!(context, "Enabling IMAP compression."); @@ -402,9 +412,9 @@ impl Imap { }) .await .context("Failed to enable IMAP compression")?; - Session::new(compressed_session, capabilities) + Session::new(compressed_session, capabilities, resync_request_sender) } else { - Session::new(session, capabilities) + Session::new(session, capabilities, resync_request_sender) }; // Store server ID in the context to display in account info. diff --git a/src/imap/select_folder.rs b/src/imap/select_folder.rs index 5796c4f0e..6023c8cff 100644 --- a/src/imap/select_folder.rs +++ b/src/imap/select_folder.rs @@ -206,7 +206,7 @@ impl ImapSession { "The server illegally decreased the uid_next of folder {folder:?} from {old_uid_next} to {new_uid_next} without changing validity ({new_uid_validity}), resyncing UIDs...", ); set_uid_next(context, folder, new_uid_next).await?; - context.schedule_resync().await?; + self.resync_request_sender.try_send(()).ok(); } // If UIDNEXT changed, there are new emails. @@ -243,7 +243,7 @@ impl ImapSession { .await?; if old_uid_validity != 0 || old_uid_next != 0 { - context.schedule_resync().await?; + self.resync_request_sender.try_send(()).ok(); } info!( context, diff --git a/src/imap/session.rs b/src/imap/session.rs index a633974d4..8cf0a17de 100644 --- a/src/imap/session.rs +++ b/src/imap/session.rs @@ -48,6 +48,8 @@ pub(crate) struct Session { /// /// Should be false if no folder is currently selected. pub new_mail: bool, + + pub resync_request_sender: async_channel::Sender<()>, } impl Deref for Session { @@ -68,6 +70,7 @@ impl Session { pub(crate) fn new( inner: ImapSession>, capabilities: Capabilities, + resync_request_sender: async_channel::Sender<()>, ) -> Self { Self { inner, @@ -77,6 +80,7 @@ impl Session { selected_folder_needs_expunge: false, last_full_folder_scan: Mutex::new(None), new_mail: false, + resync_request_sender, } } diff --git a/src/scheduler.rs b/src/scheduler.rs index 2a3537daf..daca1aeb1 100644 --- a/src/scheduler.rs +++ b/src/scheduler.rs @@ -1,7 +1,6 @@ use std::cmp; use std::iter::{self, once}; use std::num::NonZeroUsize; -use std::sync::atomic::Ordering; use anyhow::{Context as _, Error, Result, bail}; use async_channel::{self as channel, Receiver, Sender}; @@ -481,11 +480,10 @@ async fn inbox_fetch_idle(ctx: &Context, imap: &mut Imap, mut session: Session) } } - let resync_requested = ctx.resync_request.swap(false, Ordering::Relaxed); - if resync_requested { + if let Ok(()) = imap.resync_request_receiver.try_recv() { if let Err(err) = session.resync_folders(ctx).await { warn!(ctx, "Failed to resync folders: {:#}.", err); - ctx.resync_request.store(true, Ordering::Relaxed); + imap.resync_request_sender.try_send(()).ok(); } }