Skip to content

Commit

Permalink
feat(client): add resolve timeout to HttpConnector (#1994)
Browse files Browse the repository at this point in the history
The recently-added connect timeout does not cover resolving hostnames, which
could also stall on an OS-level timeout if there are issues reaching the DNS
server.
  • Loading branch information
chewi authored and seanmonstar committed Nov 12, 2019
1 parent da16ed6 commit d4ee699
Showing 1 changed file with 41 additions and 4 deletions.
45 changes: 41 additions & 4 deletions src/client/connect/http.rs
Expand Up @@ -29,6 +29,7 @@ use super::dns::{self, GaiResolver, Resolve, TokioThreadpoolGaiResolver};
pub struct HttpConnector<R = GaiResolver> {
enforce_http: bool,
handle: Option<Handle>,
resolve_timeout: Option<Duration>,
connect_timeout: Option<Duration>,
happy_eyeballs_timeout: Option<Duration>,
keep_alive_timeout: Option<Duration>,
Expand Down Expand Up @@ -121,6 +122,7 @@ impl<R> HttpConnector<R> {
HttpConnector {
enforce_http: true,
handle: None,
resolve_timeout: None,
connect_timeout: None,
happy_eyeballs_timeout: Some(Duration::from_millis(300)),
keep_alive_timeout: None,
Expand Down Expand Up @@ -189,6 +191,17 @@ impl<R> HttpConnector<R> {
self.local_address = addr;
}

/// Set timeout for hostname resolution.
///
/// If `None`, then no timeout is applied by the connector, making it
/// subject to the timeout imposed by the operating system.
///
/// Default is `None`.
#[inline]
pub fn set_resolve_timeout(&mut self, dur: Option<Duration>) {
self.resolve_timeout = dur;
}

/// Set the connect timeout.
///
/// If a domain resolves to multiple IP addresses, the timeout will be
Expand Down Expand Up @@ -272,6 +285,7 @@ where
HttpConnecting {
state: State::Lazy(self.resolver.clone(), host.into(), self.local_address),
handle: self.handle.clone(),
resolve_timeout: self.resolve_timeout,
connect_timeout: self.connect_timeout,
happy_eyeballs_timeout: self.happy_eyeballs_timeout,
keep_alive_timeout: self.keep_alive_timeout,
Expand Down Expand Up @@ -299,6 +313,7 @@ fn invalid_url<R: Resolve>(err: InvalidUrl, handle: &Option<Handle>) -> HttpConn
keep_alive_timeout: None,
nodelay: false,
port: 0,
resolve_timeout: None,
connect_timeout: None,
happy_eyeballs_timeout: None,
reuse_address: false,
Expand Down Expand Up @@ -334,6 +349,7 @@ impl StdError for InvalidUrl {
pub struct HttpConnecting<R: Resolve = GaiResolver> {
state: State<R>,
handle: Option<Handle>,
resolve_timeout: Option<Duration>,
connect_timeout: Option<Duration>,
happy_eyeballs_timeout: Option<Duration>,
keep_alive_timeout: Option<Duration>,
Expand All @@ -346,11 +362,16 @@ pub struct HttpConnecting<R: Resolve = GaiResolver> {

enum State<R: Resolve> {
Lazy(R, String, Option<IpAddr>),
Resolving(R::Future, Option<IpAddr>),
Resolving(ResolvingFuture<R>, Option<IpAddr>),
Connecting(ConnectingTcp),
Error(Option<io::Error>),
}

enum ResolvingFuture<R: Resolve> {
Timed(Timeout<R::Future>),
Untimed(R::Future),
}

impl<R: Resolve> Future for HttpConnecting<R> {
type Item = (TcpStream, Connected);
type Error = io::Error;
Expand All @@ -367,11 +388,27 @@ impl<R: Resolve> Future for HttpConnecting<R> {
local_addr, addrs, self.connect_timeout, self.happy_eyeballs_timeout, self.reuse_address));
} else {
let name = dns::Name::new(mem::replace(host, String::new()));
state = State::Resolving(resolver.resolve(name), local_addr);
let future = resolver.resolve(name);
state = if let Some(timeout) = self.resolve_timeout {
State::Resolving(ResolvingFuture::Timed(Timeout::new(future, timeout)), local_addr)
} else {
State::Resolving(ResolvingFuture::Untimed(future), local_addr)
}
}
},
State::Resolving(ref mut future, local_addr) => {
match future.poll()? {
State::Resolving(ref mut rfuture, local_addr) => {
let res: Async<R::Addrs> = match rfuture {
ResolvingFuture::Timed(future) => match future.poll() {
Ok(res) => res,
Err(err) => if err.is_inner() {
return Err(err.into_inner().unwrap())
} else {
return Err(io::Error::new(io::ErrorKind::TimedOut, err.description()))
},
},
ResolvingFuture::Untimed(future) => future.poll()?,
};
match res {
Async::NotReady => return Ok(Async::NotReady),
Async::Ready(addrs) => {
let port = self.port;
Expand Down

0 comments on commit d4ee699

Please sign in to comment.