refactor(client): breakout connect_to future into separate function

This commit is contained in:
Sean McArthur
2018-09-27 19:06:16 -07:00
parent af5e4f3ec2
commit 5e159a58b4
5 changed files with 99 additions and 61 deletions

View File

@@ -119,6 +119,8 @@ pub struct Parts<T> {
// ========== internal client api
/// A `Future` for when `SendRequest::poll_ready()` is ready.
// FIXME: allow() required due to `impl Trait` leaking types to this lint
#[allow(missing_debug_implementations)]
#[must_use = "futures do nothing unless polled"]
pub(super) struct WhenReady<B> {
tx: Option<SendRequest<B>>,

View File

@@ -90,10 +90,9 @@ use http::header::{Entry, HeaderValue, HOST};
use http::uri::Scheme;
use body::{Body, Payload};
use common::Exec;
use common::lazy as hyper_lazy;
use common::{Exec, lazy as hyper_lazy, Lazy};
use self::connect::{Connect, Destination};
use self::pool::{Pool, Poolable, Reservation};
use self::pool::{Key as PoolKey, Pool, Poolable, Pooled, Reservation};
#[cfg(feature = "runtime")] pub use self::connect::HttpConnector;
@@ -261,62 +260,10 @@ where C: Connect + Sync + 'static,
//TODO: replace with `impl Future` when stable
fn send_request(&self, mut req: Request<B>, domain: &str) -> Box<Future<Item=Response<Body>, Error=ClientError<B>> + Send> {
let url = req.uri().clone();
let ver = self.ver;
let pool_key = (Arc::new(domain.to_string()), self.ver);
let checkout = self.pool.checkout(pool_key.clone());
let connect = {
let executor = self.executor.clone();
let pool = self.pool.clone();
let h1_writev = self.h1_writev;
let h1_title_case_headers = self.h1_title_case_headers;
let connector = self.connector.clone();
let dst = Destination {
uri: url,
};
hyper_lazy(move || {
if let Some(connecting) = pool.connecting(&pool_key) {
Either::A(connector.connect(dst)
.map_err(::Error::new_connect)
.and_then(move |(io, connected)| {
conn::Builder::new()
.exec(executor.clone())
.h1_writev(h1_writev)
.h1_title_case_headers(h1_title_case_headers)
.http2_only(pool_key.1 == Ver::Http2)
.handshake(io)
.and_then(move |(tx, conn)| {
let bg = executor.execute(conn.map_err(|e| {
debug!("client connection error: {}", e)
}));
// This task is critical, so an execute error
// should be returned.
if let Err(err) = bg {
warn!("error spawning critical client task: {}", err);
return Either::A(future::err(err));
}
// Wait for 'conn' to ready up before we
// declare this tx as usable
Either::B(tx.when_ready())
})
.map(move |tx| {
pool.pooled(connecting, PoolClient {
is_proxied: connected.is_proxied,
tx: match ver {
Ver::Http1 => PoolTx::Http1(tx),
Ver::Http2 => PoolTx::Http2(tx.into_http2()),
},
})
})
}))
} else {
let canceled = ::Error::new_canceled(Some("HTTP/2 connection in progress"));
Either::B(future::err(canceled))
}
})
};
let connect = self.connect_to(req.uri().clone(), pool_key);
let executor = self.executor.clone();
// The order of the `select` is depended on below...
@@ -486,6 +433,69 @@ where C: Connect + Sync + 'static,
Box::new(resp)
}
fn connect_to(&self, uri: Uri, pool_key: PoolKey)
-> impl Lazy<Item=Pooled<PoolClient<B>>, Error=::Error>
{
let executor = self.executor.clone();
let pool = self.pool.clone();
let h1_writev = self.h1_writev;
let h1_title_case_headers = self.h1_title_case_headers;
let connector = self.connector.clone();
let ver = pool_key.1;
let dst = Destination {
uri,
};
hyper_lazy(move || {
// Try to take a "connecting lock".
//
// If the pool_key is for HTTP/2, and there is already a
// connection being estabalished, then this can't take a
// second lock. The "connect_to" future is Canceled.
let connecting = match pool.connecting(&pool_key) {
Some(lock) => lock,
None => {
let canceled = ::Error::new_canceled(Some("HTTP/2 connection in progress"));
return Either::B(future::err(canceled));
}
};
Either::A(connector.connect(dst)
.map_err(::Error::new_connect)
.and_then(move |(io, connected)| {
conn::Builder::new()
.exec(executor.clone())
.h1_writev(h1_writev)
.h1_title_case_headers(h1_title_case_headers)
.http2_only(pool_key.1 == Ver::Http2)
.handshake(io)
.and_then(move |(tx, conn)| {
let bg = executor.execute(conn.map_err(|e| {
debug!("client connection error: {}", e)
}));
// This task is critical, so an execute error
// should be returned.
if let Err(err) = bg {
warn!("error spawning critical client task: {}", err);
return Either::A(future::err(err));
}
// Wait for 'conn' to ready up before we
// declare this tx as usable
Either::B(tx.when_ready())
})
.map(move |tx| {
pool.pooled(connecting, PoolClient {
is_proxied: connected.is_proxied,
tx: match ver {
Ver::Http1 => PoolTx::Http1(tx),
Ver::Http2 => PoolTx::Http2(tx.into_http2()),
},
})
})
}))
})
}
}
impl<C, B> Clone for Client<C, B> {
@@ -588,6 +598,8 @@ where
}
}
// FIXME: allow() required due to `impl Trait` leaking types to this lint
#[allow(missing_debug_implementations)]
struct PoolClient<B> {
is_proxied: bool,
tx: PoolTx<B>,

View File

@@ -12,6 +12,8 @@ use tokio_timer::Interval;
use common::Exec;
use super::Ver;
// FIXME: allow() required due to `impl Trait` leaking types to this lint
#[allow(missing_debug_implementations)]
pub(super) struct Pool<T> {
inner: Arc<PoolInner<T>>,
}
@@ -34,6 +36,8 @@ pub(super) trait Poolable: Send + Sized + 'static {
///
/// Specifically, HTTP/1 requires a unique reservation, but HTTP/2 can be
/// used for multiple requests.
// FIXME: allow() required due to `impl Trait` leaking types to this lint
#[allow(missing_debug_implementations)]
pub(super) enum Reservation<T> {
/// This connection could be used multiple times, the first one will be
/// reinserted into the `idle` pool, and the second will be given to
@@ -45,7 +49,7 @@ pub(super) enum Reservation<T> {
}
/// Simple type alias in case the key type needs to be adjusted.
type Key = (Arc<String>, Ver);
pub(super) type Key = (Arc<String>, Ver);
struct PoolInner<T> {
connections: Mutex<Connections<T>>,
@@ -85,8 +89,17 @@ struct Connections<T> {
struct WeakOpt<T>(Option<Weak<T>>);
// Newtypes to act as keyword arguments for `Pool::new`...
// FIXME: allow() required due to `impl Trait` leaking types to this lint
#[allow(missing_debug_implementations)]
pub(super) struct Enabled(pub(super) bool);
// FIXME: allow() required due to `impl Trait` leaking types to this lint
#[allow(missing_debug_implementations)]
pub(super) struct IdleTimeout(pub(super) Option<Duration>);
// FIXME: allow() required due to `impl Trait` leaking types to this lint
#[allow(missing_debug_implementations)]
pub(super) struct MaxIdlePerHost(pub(super) usize);
impl<T> Pool<T> {
@@ -593,6 +606,8 @@ struct Idle<T> {
value: T,
}
// FIXME: allow() required due to `impl Trait` leaking types to this lint
#[allow(missing_debug_implementations)]
pub(super) struct Checkout<T> {
key: Key,
pool: Pool<T>,
@@ -662,6 +677,8 @@ impl<T> Drop for Checkout<T> {
}
}
// FIXME: allow() required due to `impl Trait` leaking types to this lint
#[allow(missing_debug_implementations)]
pub(super) struct Connecting<T: Poolable> {
key: Key,
pool: WeakOpt<PoolInner<T>>,

View File

@@ -2,6 +2,10 @@ use std::mem;
use futures::{Future, IntoFuture, Poll};
pub(crate) trait Started: Future {
fn started(&self) -> bool;
}
pub(crate) fn lazy<F, R>(func: F) -> Lazy<F, R>
where
F: FnOnce() -> R,
@@ -12,7 +16,9 @@ where
}
}
pub struct Lazy<F, R: IntoFuture> {
// FIXME: allow() required due to `impl Trait` leaking types to this lint
#[allow(missing_debug_implementations)]
pub(crate) struct Lazy<F, R: IntoFuture> {
inner: Inner<F, R::Future>
}
@@ -22,12 +28,12 @@ enum Inner<F, R> {
Empty,
}
impl<F, R> Lazy<F, R>
impl<F, R> Started for Lazy<F, R>
where
F: FnOnce() -> R,
R: IntoFuture,
{
pub fn started(&self) -> bool {
fn started(&self) -> bool {
match self.inner {
Inner::Init(_) => false,
Inner::Fut(_) |
@@ -61,3 +67,4 @@ where
}
}
}

View File

@@ -9,5 +9,5 @@ mod never;
pub(crate) use self::buf::StaticBuf;
pub(crate) use self::exec::Exec;
pub(crate) use self::lazy::lazy;
pub(crate) use self::lazy::{lazy, Started as Lazy};
pub use self::never::Never;