imap: disable read timeout during IDLE

Otherwise IDLE restarts every 30 seconds.
This commit is contained in:
link2xt
2022-12-08 20:39:13 +00:00
parent 72432d65ba
commit edd58b4b7a
5 changed files with 38 additions and 11 deletions

View File

@@ -30,6 +30,7 @@
- python: do not pass NULL to ffi.gc if the context can't be created #3818 - 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 read/write timeouts to IMAP sockets #3820
- Add connection timeout to IMAP sockets #3828 - Add connection timeout to IMAP sockets #3828
- Disable read timeout during IMAP IDLE #3826
## 1.102.0 ## 1.102.0

2
Cargo.lock generated
View File

@@ -123,7 +123,7 @@ dependencies = [
[[package]] [[package]]
name = "async-imap" name = "async-imap"
version = "0.6.0" 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 = [ dependencies = [
"async-channel", "async-channel",
"async-native-tls", "async-native-tls",

View File

@@ -20,7 +20,7 @@ use crate::login_param::{build_tls, Socks5Config};
use super::session::SessionStream; use super::session::SessionStream;
/// IMAP write and read timeout in seconds. /// 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)] #[derive(Debug)]
pub(crate) struct Client { pub(crate) struct Client {

View File

@@ -7,8 +7,11 @@ use futures_lite::FutureExt;
use std::time::{Duration, SystemTime}; use std::time::{Duration, SystemTime};
use super::session::Session; use super::session::Session;
use crate::imap::client::IMAP_TIMEOUT;
use crate::{context::Context, scheduler::InterruptInfo}; use crate::{context::Context, scheduler::InterruptInfo};
const IDLE_TIMEOUT: Duration = Duration::from_secs(23 * 60);
impl Session { impl Session {
pub async fn idle( pub async fn idle(
mut self, mut self,
@@ -22,7 +25,6 @@ impl Session {
bail!("IMAP server does not have IDLE capability"); bail!("IMAP server does not have IDLE capability");
} }
let timeout = Duration::from_secs(23 * 60);
let mut info = Default::default(); let mut info = Default::default();
self.select_folder(context, watch_folder.as_deref()).await?; self.select_folder(context, watch_folder.as_deref()).await?;
@@ -41,7 +43,12 @@ impl Session {
bail!("IMAP IDLE protocol failed to init/complete: {}", err); 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 { enum Event {
IdleResponse(IdleResponse), 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 .await
.with_context(|| format!("{}: IMAP IDLE protocol timed out", folder_name))? .with_context(|| format!("{}: IMAP IDLE protocol timed out", folder_name))?
.with_context(|| format!("{}: IMAP IDLE failed", folder_name))?; .with_context(|| format!("{}: IMAP IDLE failed", folder_name))?;
session.as_mut().set_read_timeout(Some(IMAP_TIMEOUT));
self.inner = session; self.inner = session;
Ok((self, info)) Ok((self, info))

View File

@@ -1,5 +1,6 @@
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use std::pin::Pin; use std::pin::Pin;
use std::time::Duration;
use async_imap::types::Mailbox; use async_imap::types::Mailbox;
use async_imap::Session as ImapSession; use async_imap::Session as ImapSession;
@@ -28,14 +29,31 @@ pub(crate) struct Session {
pub(crate) trait SessionStream: pub(crate) trait SessionStream:
tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + Send + Sync + std::fmt::Debug 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<Duration>);
} }
impl SessionStream for TlsStream<Box<dyn SessionStream>> {} impl SessionStream for TlsStream<Box<dyn SessionStream>> {
impl SessionStream for TlsStream<Pin<Box<TimeoutStream<TcpStream>>>> {} fn set_read_timeout(&mut self, timeout: Option<Duration>) {
impl SessionStream for TlsStream<TcpStream> {} self.get_mut().set_read_timeout(timeout);
impl SessionStream for TcpStream {} }
impl SessionStream for Pin<Box<TimeoutStream<TcpStream>>> {} }
impl SessionStream for Socks5Stream<TcpStream> {} impl SessionStream for TlsStream<Pin<Box<TimeoutStream<TcpStream>>>> {
fn set_read_timeout(&mut self, timeout: Option<Duration>) {
self.get_mut().set_read_timeout(timeout);
}
}
impl SessionStream for Pin<Box<TimeoutStream<TcpStream>>> {
fn set_read_timeout(&mut self, timeout: Option<Duration>) {
self.as_mut().set_read_timeout_pinned(timeout);
}
}
impl SessionStream for Socks5Stream<TcpStream> {
fn set_read_timeout(&mut self, _timeout: Option<Duration>) {
// FIXME: build SOCKS streams on top of TimeoutStream, not directly TcpStream,
// so we can set a read timeout for them.
}
}
impl Deref for Session { impl Deref for Session {
type Target = ImapSession<Box<dyn SessionStream>>; type Target = ImapSession<Box<dyn SessionStream>>;