diff --git a/meli.conf.5 b/meli.conf.5 index 95e041e6c..697abd73d 100644 --- a/meli.conf.5 +++ b/meli.conf.5 @@ -240,6 +240,12 @@ Use IDLE extension. Use COMPRESS=DEFLATE extension (if built with DEFLATE support). .\" default value .Pq Em true +.It Ic timeout Ar integer +.Pq Em optional +Timeout to use for server connections in seconds. +A timeout of 0 seconds means there's no timeout. +.\" default value +.Pq Em 16 .El .Ss JMAP only JMAP specific options diff --git a/melib/src/backends/imap.rs b/melib/src/backends/imap.rs index ecc75796b..3aeea0f70 100644 --- a/melib/src/backends/imap.rs +++ b/melib/src/backends/imap.rs @@ -97,6 +97,7 @@ pub struct ImapServerConf { pub use_tls: bool, pub danger_accept_invalid_certs: bool, pub protocol: ImapProtocol, + pub timeout: Option, } struct IsSubscribedFn(Box bool + Send + Sync>); @@ -167,6 +168,7 @@ pub struct UIDStore { mailboxes: Arc>>, is_online: Arc)>>, event_consumer: BackendEventConsumer, + timeout: Option, } impl UIDStore { @@ -174,6 +176,7 @@ impl UIDStore { account_hash: AccountHash, account_name: Arc, event_consumer: BackendEventConsumer, + timeout: Option, ) -> Self { UIDStore { account_hash, @@ -197,6 +200,7 @@ impl UIDStore { Err(MeliError::new("Account is uninitialised.")), ))), event_consumer, + timeout, } } } @@ -354,12 +358,12 @@ impl MailBackend for ImapType { let main_conn = self.connection.clone(); let uid_store = self.uid_store.clone(); Ok(Box::pin(async move { - let inbox = timeout(Duration::from_secs(3), uid_store.mailboxes.lock()) + let inbox = timeout(uid_store.timeout, uid_store.mailboxes.lock()) .await? .get(&mailbox_hash) .map(std::clone::Clone::clone) .unwrap(); - let mut conn = timeout(Duration::from_secs(3), main_conn.lock()).await?; + let mut conn = timeout(uid_store.timeout, main_conn.lock()).await?; watch::examine_updates(inbox, &mut conn, &uid_store).await?; Ok(()) })) @@ -416,11 +420,12 @@ impl MailBackend for ImapType { fn is_online(&self) -> ResultFuture<()> { let connection = self.connection.clone(); + let timeout_dur = self.server_conf.timeout; Ok(Box::pin(async move { - match timeout(std::time::Duration::from_secs(3), connection.lock()).await { + match timeout(timeout_dur, connection.lock()).await { Ok(mut conn) => { debug!("is_online"); - match debug!(timeout(std::time::Duration::from_secs(3), conn.connect()).await) { + match debug!(timeout(timeout_dur, conn.connect()).await) { Ok(Ok(())) => Ok(()), Err(err) | Ok(Err(err)) => { conn.stream = Err(err.clone()); @@ -453,6 +458,7 @@ impl MailBackend for ImapType { Ok(Box::pin(async move { debug!(has_idle); let main_conn2 = main_conn.clone(); + let timeout_dur = uid_store.timeout; let kit = ImapWatchKit { conn, main_conn, @@ -463,7 +469,7 @@ impl MailBackend for ImapType { } else { poll_with_examine(kit).await } { - let mut main_conn = timeout(Duration::from_secs(3), main_conn2.lock()).await?; + let mut main_conn = timeout(timeout_dur, main_conn2.lock()).await?; if err.kind.is_network() { main_conn.uid_store.is_online.lock().unwrap().1 = Err(err.clone()); } @@ -1222,6 +1228,12 @@ impl ImapType { s.name, ))); } + let timeout = get_conf_val!(s["timeout"], 16_u64)?; + let timeout = if timeout == 0 { + None + } else { + Some(Duration::from_secs(timeout)) + }; let server_conf = ImapServerConf { server_hostname: server_hostname.to_string(), server_username: server_username.to_string(), @@ -1238,6 +1250,7 @@ impl ImapType { deflate: get_conf_val!(s["use_deflate"], true)?, }, }, + timeout, }; let account_hash = { let mut hasher = DefaultHasher::new(); @@ -1247,7 +1260,12 @@ impl ImapType { let account_name = Arc::new(s.name().to_string()); let uid_store: Arc = Arc::new(UIDStore { keep_offline_cache, - ..UIDStore::new(account_hash, account_name, event_consumer) + ..UIDStore::new( + account_hash, + account_name, + event_consumer, + server_conf.timeout, + ) }); let connection = ImapConnection::new_connection(&server_conf, uid_store.clone()); @@ -1262,15 +1280,18 @@ impl ImapType { pub fn shell(&mut self) { let mut conn = ImapConnection::new_connection(&self.server_conf, self.uid_store.clone()); - futures::executor::block_on(timeout(Duration::from_secs(3), conn.connect())) + futures::executor::block_on(timeout(self.server_conf.timeout, conn.connect())) .unwrap() .unwrap(); let mut res = String::with_capacity(8 * 1024); - futures::executor::block_on(timeout(Duration::from_secs(3), conn.send_command(b"NOOP"))) - .unwrap() - .unwrap(); futures::executor::block_on(timeout( - Duration::from_secs(3), + self.server_conf.timeout, + conn.send_command(b"NOOP"), + )) + .unwrap() + .unwrap(); + futures::executor::block_on(timeout( + self.server_conf.timeout, conn.read_response(&mut res, RequiredResponses::empty()), )) .unwrap() @@ -1284,13 +1305,13 @@ impl ImapType { match io::stdin().read_line(&mut input) { Ok(_) => { futures::executor::block_on(timeout( - Duration::from_secs(3), + self.server_conf.timeout, conn.send_command(input.as_bytes()), )) .unwrap() .unwrap(); futures::executor::block_on(timeout( - Duration::from_secs(3), + self.server_conf.timeout, conn.read_lines(&mut res, String::new()), )) .unwrap() @@ -1460,6 +1481,7 @@ impl ImapType { s.name.as_str(), ))); } + let _timeout = get_conf_val!(s["timeout"], 16_u64)?; Ok(()) } diff --git a/melib/src/backends/imap/connection.rs b/melib/src/backends/imap/connection.rs index 64c5364f4..6a205bef8 100644 --- a/melib/src/backends/imap/connection.rs +++ b/melib/src/backends/imap/connection.rs @@ -80,6 +80,7 @@ pub struct ImapStream { pub stream: AsyncWrapper, pub protocol: ImapProtocol, pub current_mailbox: MailboxSelection, + pub timeout: Option, } #[derive(Debug, Copy, Clone, Eq, PartialEq)] @@ -134,8 +135,12 @@ impl ImapStream { }; let mut socket = AsyncWrapper::new(Connection::Tcp( - TcpStream::connect_timeout(&addr, Duration::new(4, 0)) - .chain_err_kind(crate::error::ErrorKind::Network)?, + if let Some(timeout) = server_conf.timeout { + TcpStream::connect_timeout(&addr, timeout) + .chain_err_kind(crate::error::ErrorKind::Network)? + } else { + TcpStream::connect(&addr).chain_err_kind(crate::error::ErrorKind::Network)? + }, )) .chain_err_kind(crate::error::ErrorKind::Network)?; if server_conf.use_starttls { @@ -239,8 +244,12 @@ impl ImapStream { ))); }; AsyncWrapper::new(Connection::Tcp( - TcpStream::connect_timeout(&addr, Duration::new(4, 0)) - .chain_err_kind(crate::error::ErrorKind::Network)?, + if let Some(timeout) = server_conf.timeout { + TcpStream::connect_timeout(&addr, timeout) + .chain_err_kind(crate::error::ErrorKind::Network)? + } else { + TcpStream::connect(&addr).chain_err_kind(crate::error::ErrorKind::Network)? + }, )) .chain_err_kind(crate::error::ErrorKind::Network)? }; @@ -250,6 +259,7 @@ impl ImapStream { stream, protocol: server_conf.protocol, current_mailbox: MailboxSelection::None, + timeout: server_conf.timeout, }; if let ImapProtocol::ManageSieve = server_conf.protocol { use data_encoding::BASE64; @@ -384,7 +394,7 @@ impl ImapStream { ret.clear(); let mut last_line_idx: usize = 0; loop { - match timeout(Duration::from_secs(16), self.stream.read(&mut buf)).await? { + match timeout(self.timeout, self.stream.read(&mut buf)).await? { Ok(0) => break, Ok(b) => { ret.push_str(unsafe { std::str::from_utf8_unchecked(&buf[0..b]) }); @@ -432,7 +442,7 @@ impl ImapStream { pub async fn send_command(&mut self, command: &[u8]) -> Result<()> { if let Err(err) = timeout( - Duration::from_secs(16), + self.timeout, try_await(async move { let command = command.trim(); match self.protocol { @@ -603,6 +613,7 @@ impl ImapConnection { stream, protocol, current_mailbox, + timeout, } = std::mem::replace(&mut self.stream, Err(MeliError::new("")))?; let stream = stream.into_inner()?; self.stream = Ok(ImapStream { @@ -610,6 +621,7 @@ impl ImapConnection { stream: AsyncWrapper::new(stream.deflate())?, protocol, current_mailbox, + timeout, }); } } diff --git a/melib/src/backends/imap/managesieve.rs b/melib/src/backends/imap/managesieve.rs index 1417d69bf..97f676551 100644 --- a/melib/src/backends/imap/managesieve.rs +++ b/melib/src/backends/imap/managesieve.rs @@ -96,6 +96,12 @@ pub fn new_managesieve_connection( let server_password = get_conf_val!(s["server_password"])?; let server_port = get_conf_val!(s["server_port"], 4190)?; let danger_accept_invalid_certs: bool = get_conf_val!(s["danger_accept_invalid_certs"], false)?; + let timeout = get_conf_val!(s["timeout"], 16_u64)?; + let timeout = if timeout == 0 { + None + } else { + Some(std::time::Duration::from_secs(timeout)) + }; let server_conf = ImapServerConf { server_hostname: server_hostname.to_string(), server_username: server_username.to_string(), @@ -105,13 +111,19 @@ pub fn new_managesieve_connection( use_tls: true, danger_accept_invalid_certs, protocol: ImapProtocol::ManageSieve, + timeout, }; let uid_store = Arc::new(UIDStore { is_online: Arc::new(Mutex::new(( Instant::now(), Err(MeliError::new("Account is uninitialised.")), ))), - ..UIDStore::new(account_hash, Arc::new(account_name), event_consumer) + ..UIDStore::new( + account_hash, + Arc::new(account_name), + event_consumer, + server_conf.timeout, + ) }); Ok(ImapConnection::new_connection(&server_conf, uid_store)) } diff --git a/melib/src/backends/imap/operations.rs b/melib/src/backends/imap/operations.rs index 9fae0c9d9..000a25274 100644 --- a/melib/src/backends/imap/operations.rs +++ b/melib/src/backends/imap/operations.rs @@ -66,8 +66,7 @@ impl BackendOp for ImapOp { if !exists_in_cache { let mut response = String::with_capacity(8 * 1024); { - let mut conn = - timeout(std::time::Duration::from_secs(3), connection.lock()).await?; + let mut conn = timeout(uid_store.timeout, connection.lock()).await?; conn.examine_mailbox(mailbox_hash, &mut response, false) .await?; conn.send_command(format!("UID FETCH {} (FLAGS RFC822)", uid).as_bytes()) diff --git a/melib/src/backends/imap/watch.rs b/melib/src/backends/imap/watch.rs index dfa220bd8..786b32c70 100644 --- a/melib/src/backends/imap/watch.rs +++ b/melib/src/backends/imap/watch.rs @@ -39,7 +39,7 @@ pub async fn poll_with_examine(kit: ImapWatchKit) -> Result<()> { conn.connect().await?; loop { let mailboxes: HashMap = { - let mailboxes_lck = timeout(Duration::from_secs(3), uid_store.mailboxes.lock()).await?; + let mailboxes_lck = timeout(uid_store.timeout, uid_store.mailboxes.lock()).await?; mailboxes_lck.clone() }; for (_, mailbox) in mailboxes { @@ -109,14 +109,16 @@ pub async fn idle(kit: ImapWatchKit) -> Result<()> { let mut blockn = ImapBlockingConnection::from(conn); let mut beat = std::time::Instant::now(); let mut watch = std::time::Instant::now(); + /* duration interval before IMAP timeouts */ + const _35_MINS: std::time::Duration = std::time::Duration::from_secs(35 * 60); /* duration interval to send heartbeat */ const _26_MINS: std::time::Duration = std::time::Duration::from_secs(26 * 60); /* duration interval to check other mailboxes for changes */ const _5_MINS: std::time::Duration = std::time::Duration::from_secs(5 * 60); - while let Some(line) = timeout(Duration::from_secs(35 * 60), blockn.as_stream()).await? { + while let Some(line) = timeout(Some(_35_MINS), blockn.as_stream()).await? { let now = std::time::Instant::now(); if now.duration_since(beat) >= _26_MINS { - let mut main_conn_lck = timeout(Duration::from_secs(3), main_conn.lock()).await?; + let mut main_conn_lck = timeout(uid_store.timeout, main_conn.lock()).await?; blockn.conn.send_raw(b"DONE").await?; blockn .conn @@ -131,10 +133,10 @@ pub async fn idle(kit: ImapWatchKit) -> Result<()> { } if now.duration_since(watch) >= _5_MINS { /* Time to poll all inboxes */ - let mut conn = timeout(Duration::from_secs(3), main_conn.lock()).await?; + let mut conn = timeout(uid_store.timeout, main_conn.lock()).await?; let mailboxes: HashMap = { let mailboxes_lck = - timeout(Duration::from_secs(3), uid_store.mailboxes.lock()).await?; + timeout(uid_store.timeout, uid_store.mailboxes.lock()).await?; mailboxes_lck.clone() }; for (h, mailbox) in mailboxes { @@ -183,16 +185,6 @@ pub async fn idle(kit: ImapWatchKit) -> Result<()> { } debug!("IDLE connection dropped"); let err: &str = blockn.err().unwrap_or("Unknown reason."); - timeout(Duration::from_secs(3), main_conn.lock()) - .await? - .add_refresh_event(RefreshEvent { - account_hash: uid_store.account_hash, - mailbox_hash, - kind: RefreshEventKind::Failure(MeliError::new(format!( - "IDLE connection dropped: {}", - &err - ))), - }); Err(MeliError::new(format!("IDLE connection dropped: {}", err))) } diff --git a/melib/src/connections.rs b/melib/src/connections.rs index 1d0a1cc39..6ce1872ad 100644 --- a/melib/src/connections.rs +++ b/melib/src/connections.rs @@ -191,13 +191,18 @@ pub fn lookup_ipv4(host: &str, port: u16) -> crate::Result use futures::future::{self, Either, Future}; -pub async fn timeout(dur: std::time::Duration, f: impl Future) -> crate::Result { +pub async fn timeout( + dur: Option, + f: impl Future, +) -> crate::Result { futures::pin_mut!(f); - match future::select(f, smol::Timer::after(dur)).await { - Either::Left((out, _)) => Ok(out), - Either::Right(_) => { - Err(crate::error::MeliError::new("Timed out.") - .set_kind(crate::error::ErrorKind::Timeout)) + if let Some(dur) = dur { + match future::select(f, smol::Timer::after(dur)).await { + Either::Left((out, _)) => Ok(out), + Either::Right(_) => Err(crate::error::MeliError::new("Timed out.") + .set_kind(crate::error::ErrorKind::Timeout)), } + } else { + Ok(f.await) } }