imap::connect timeout
wookietreiber opened this issue · comments
I have a specific server with a long connection time and would like to configure a timeout for that, specifically for the imap::connect
function.
From looking at the documentation and source code, my current understanding is that imap::connect
uses TcpStream::connect
. There is also an alternative TcpStream::connect_timeout
. Would it be useful to either add a timeout function argument to imap::connect
or to mimic TcpStream
and an imap::connect_timeout
alternative?
Hmm, it's a good question. My first instinct here is that if you want to set particular settings for the TCP connection (whether that be connection timeout or something else), you should prefer using Client::new
, which lets you establish the TcpStream
yourself, and then just give the established stream to imap
. Would that work for your use-case?
Thanks for the tip about Client::new
! This has been quite an interesting journey.
At first I was a bit annoyed that I had to handle the socket addresses myself, because TcpStream::connect_timeout
doesn't take ToSocketAddrs
like connect
does but a single SocketAddr
. Then I found out that it's the IPv6 address that keeps blocking, the IPv4 address works just fine. That's probably because my network isn't set up correctly for IPv6. Having to actually handle the addresses myself helped me to figure that out (see also note below).
What I have now is this for the single-address timeout function:
fn connect_timeout<S: AsRef<str>>(
addr: &SocketAddr,
domain: S,
ssl_connector: &TlsConnector,
timeout: Duration,
) -> Result<Client<TlsStream<TcpStream>>> {
let tcp_stream = TcpStream::connect_timeout(addr, timeout)
.with_context(|| format!("connecting tcp stream to {}", addr))?;
let tls_stream =
TlsConnector::connect(ssl_connector, domain.as_ref(), tcp_stream)
.with_context(|| format!("tls handshake with {}", addr))?;
let mut client = Client::new(tls_stream);
client
.read_greeting()
.with_context(|| format!("tls greeting from {}", addr))?;
Ok(client)
}
I'm using this to have a convenience function to try all addresses from the DNS resolution:
fn connect_all_timeout<A: ToSocketAddrs + fmt::Debug, S: AsRef<str>>(
addr: A,
domain: S,
ssl_connector: &TlsConnector,
timeout: Duration,
) -> Result<Client<TlsStream<TcpStream>>> {
let addrs = addr
.to_socket_addrs()
.with_context(|| format!("unable to resolve {:?}", addr))?;
let mut errors = vec![];
for addr in addrs {
match connect_timeout(&addr, &domain, ssl_connector, timeout) {
Ok(client) => return Ok(client),
Err(error) => errors.push(error),
}
}
Err(anyhow!("all addresses failed to connect: {:#?}", errors))
}
Note: I'm using anyhow for the detailed error messages. This was how I was able to figure out that it's the IPv6 address that doesn't work.
@jonhoo: Would you like me to PR any of these?
Haha, yeah, I can imagine that was quite a journey! I'm glad you figured it out.
I think I'd like to keep both of those outside of imap
— they are somewhat specialized, pretty easy to model with Client::new
, and once we add that, there are also other configurations we may have to add, like more advanced TLS connection options, TCP read/write timeouts, reuse port/addr options, TCP nodelay, etc. I'd prefer we keep it to Client::new
, and leave such configuration to the user.
I do think that mentioning Client::read_greeting
in the documentation for Client::new
would be a good thing to add though! Arguably, Client::new
should just do it for you — I'm not sure why it doesn't at the moment. But that'd be a backwards incompatible change. I think for now, if you could submit a PR that adds a mention of read_greeting
to Client::new
, that'd be great!
I am using Client::new
in my fn connect_timeout
. I adapted the code from imap::connect
to use TcpStream::connect_timeout
instead of TcpStream::connect
. The remaining diff is from using anyhow
.
I'm wondering how to reference read_greeting
. It is defined on Connection
which is not a completely public struct, i.e. I can find neither Connection
nor read_greeting
in the docs. I'm wondering how I'm even able to use it.
@wookietreiber The methods from Connection
are usable on both Client
and Session
due to the Deref
impl, and the implicit Deref coercions; basically the compiler inserts .deref()
or .deref_mut()
calls where necessary.
@dario23 Thanks, that explains half of my questions :) I'm still wondering why I'm not seeing it in the docs.
@wookietreiber Ah, that's because the Connection
type isn't public — the Deref
is there primarily so that we can conveniently call methods on Connection
through Client
internally, but just so happens to enable this particular workaround. This is the other reason why I'd ideally like for read_greeting
to never be something the user has to call..