melib/imap: introduce a conf flag for server timeout

timeout integer                       (optional) Timeout to use for server connections in seconds.  A timeout of 0 seconds means there's no timeout.  (16)
master
Manos Pitsidianakis 2020-09-12 22:05:48 +03:00
parent 96985c9c1f
commit 06a58a70bd
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
7 changed files with 91 additions and 43 deletions

View File

@ -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

View File

@ -97,6 +97,7 @@ pub struct ImapServerConf {
pub use_tls: bool,
pub danger_accept_invalid_certs: bool,
pub protocol: ImapProtocol,
pub timeout: Option<Duration>,
}
struct IsSubscribedFn(Box<dyn Fn(&str) -> bool + Send + Sync>);
@ -167,6 +168,7 @@ pub struct UIDStore {
mailboxes: Arc<FutureMutex<HashMap<MailboxHash, ImapMailbox>>>,
is_online: Arc<Mutex<(Instant, Result<()>)>>,
event_consumer: BackendEventConsumer,
timeout: Option<Duration>,
}
impl UIDStore {
@ -174,6 +176,7 @@ impl UIDStore {
account_hash: AccountHash,
account_name: Arc<String>,
event_consumer: BackendEventConsumer,
timeout: Option<Duration>,
) -> 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<UIDStore> = 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(())
}

View File

@ -80,6 +80,7 @@ pub struct ImapStream {
pub stream: AsyncWrapper<Connection>,
pub protocol: ImapProtocol,
pub current_mailbox: MailboxSelection,
pub timeout: Option<Duration>,
}
#[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,
});
}
}

View File

@ -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))
}

View File

@ -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())

View File

@ -39,7 +39,7 @@ pub async fn poll_with_examine(kit: ImapWatchKit) -> Result<()> {
conn.connect().await?;
loop {
let mailboxes: HashMap<MailboxHash, ImapMailbox> = {
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<MailboxHash, ImapMailbox> = {
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)))
}

View File

@ -191,13 +191,18 @@ pub fn lookup_ipv4(host: &str, port: u16) -> crate::Result<std::net::SocketAddr>
use futures::future::{self, Either, Future};
pub async fn timeout<O>(dur: std::time::Duration, f: impl Future<Output = O>) -> crate::Result<O> {
pub async fn timeout<O>(
dur: Option<std::time::Duration>,
f: impl Future<Output = O>,
) -> crate::Result<O> {
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)
}
}