diff --git a/CHANGELOG.md b/CHANGELOG.md index 10f42a08d..df034b6f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ - python: do not pass NULL to ffi.gc if the context can't be created #3818 - Add read/write timeouts to IMAP sockets #3820 - Add connection timeout to IMAP sockets #3828 +- Disable read timeout during IMAP IDLE #3826 ## 1.102.0 diff --git a/Cargo.lock b/Cargo.lock index e6875e093..495f76821 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -123,7 +123,7 @@ dependencies = [ [[package]] name = "async-imap" version = "0.6.0" -source = "git+https://github.com/async-email/async-imap?branch=master#8755b666fcd8991ed4d09864b67aa88a1eb6934f" +source = "git+https://github.com/async-email/async-imap?branch=master#85ff7a3d9d71a3715354fabf2fc1a8d047b5710e" dependencies = [ "async-channel", "async-native-tls", diff --git a/src/imap/client.rs b/src/imap/client.rs index 70e7b02a4..b6f4867ad 100644 --- a/src/imap/client.rs +++ b/src/imap/client.rs @@ -20,7 +20,7 @@ use crate::login_param::{build_tls, Socks5Config}; use super::session::SessionStream; /// IMAP write and read timeout in seconds. -const IMAP_TIMEOUT: Duration = Duration::from_secs(30); +pub(crate) const IMAP_TIMEOUT: Duration = Duration::from_secs(30); #[derive(Debug)] pub(crate) struct Client { diff --git a/src/imap/idle.rs b/src/imap/idle.rs index 61821a82e..78232982e 100644 --- a/src/imap/idle.rs +++ b/src/imap/idle.rs @@ -7,8 +7,11 @@ use futures_lite::FutureExt; use std::time::{Duration, SystemTime}; use super::session::Session; +use crate::imap::client::IMAP_TIMEOUT; use crate::{context::Context, scheduler::InterruptInfo}; +const IDLE_TIMEOUT: Duration = Duration::from_secs(23 * 60); + impl Session { pub async fn idle( mut self, @@ -22,7 +25,6 @@ impl Session { bail!("IMAP server does not have IDLE capability"); } - let timeout = Duration::from_secs(23 * 60); let mut info = Default::default(); self.select_folder(context, watch_folder.as_deref()).await?; @@ -41,7 +43,12 @@ impl Session { bail!("IMAP IDLE protocol failed to init/complete: {}", err); } - let (idle_wait, interrupt) = handle.wait_with_timeout(timeout); + // At this point IDLE command was sent and we received a "+ idling" response. We will now + // read from the stream without getting any data for up to `IDLE_TIMEOUT`. If we don't + // disable read timeout, we would get a timeout after `IMAP_TIMEOUT`, which is a lot + // shorter than `IDLE_TIMEOUT`. + handle.as_mut().set_read_timeout(None); + let (idle_wait, interrupt) = handle.wait_with_timeout(IDLE_TIMEOUT); enum Event { IdleResponse(IdleResponse), @@ -90,10 +97,11 @@ impl Session { } } - let session = tokio::time::timeout(Duration::from_secs(15), handle.done()) + let mut session = tokio::time::timeout(Duration::from_secs(15), handle.done()) .await .with_context(|| format!("{}: IMAP IDLE protocol timed out", folder_name))? .with_context(|| format!("{}: IMAP IDLE failed", folder_name))?; + session.as_mut().set_read_timeout(Some(IMAP_TIMEOUT)); self.inner = session; Ok((self, info)) diff --git a/src/imap/session.rs b/src/imap/session.rs index 2897aa09a..b27e1b7e3 100644 --- a/src/imap/session.rs +++ b/src/imap/session.rs @@ -1,5 +1,6 @@ use std::ops::{Deref, DerefMut}; use std::pin::Pin; +use std::time::Duration; use async_imap::types::Mailbox; use async_imap::Session as ImapSession; @@ -28,14 +29,31 @@ pub(crate) struct Session { pub(crate) trait SessionStream: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + Send + Sync + std::fmt::Debug { + /// Change the read timeout on the session stream. + fn set_read_timeout(&mut self, timeout: Option); } -impl SessionStream for TlsStream> {} -impl SessionStream for TlsStream>>> {} -impl SessionStream for TlsStream {} -impl SessionStream for TcpStream {} -impl SessionStream for Pin>> {} -impl SessionStream for Socks5Stream {} +impl SessionStream for TlsStream> { + fn set_read_timeout(&mut self, timeout: Option) { + self.get_mut().set_read_timeout(timeout); + } +} +impl SessionStream for TlsStream>>> { + fn set_read_timeout(&mut self, timeout: Option) { + self.get_mut().set_read_timeout(timeout); + } +} +impl SessionStream for Pin>> { + fn set_read_timeout(&mut self, timeout: Option) { + self.as_mut().set_read_timeout_pinned(timeout); + } +} +impl SessionStream for Socks5Stream { + fn set_read_timeout(&mut self, _timeout: Option) { + // FIXME: build SOCKS streams on top of TimeoutStream, not directly TcpStream, + // so we can set a read timeout for them. + } +} impl Deref for Session { type Target = ImapSession>;