refactor(lib): Switch from pin-project to pin-project-lite (#2566)

Note the practical affects of this change:

- Dependency count with --features full dropped from 65 to 55.
- Time to compile after a clean dropped from 48s to 35s (on a pretty underpowered VM).

Closes #2388
This commit is contained in:
Jonas Platte
2021-06-04 23:57:27 +02:00
committed by GitHub
parent 0d82405a7b
commit 6a6a24030e
15 changed files with 423 additions and 288 deletions

View File

@@ -9,7 +9,7 @@
#[cfg(feature = "stream")]
use futures_core::Stream;
#[cfg(feature = "stream")]
use pin_project::pin_project;
use pin_project_lite::pin_project;
use crate::common::{
task::{self, Poll},
@@ -86,8 +86,12 @@ pub fn from_stream<S, IO, E>(stream: S) -> impl Accept<Conn = IO, Error = E>
where
S: Stream<Item = Result<IO, E>>,
{
#[pin_project]
struct FromStream<S>(#[pin] S);
pin_project! {
struct FromStream<S> {
#[pin]
stream: S,
}
}
impl<S, IO, E> Accept for FromStream<S>
where
@@ -99,9 +103,9 @@ where
self: Pin<&mut Self>,
cx: &mut task::Context<'_>,
) -> Poll<Option<Result<Self::Conn, Self::Error>>> {
self.project().0.poll_next(cx)
self.project().stream.poll_next(cx)
}
}
FromStream(stream)
FromStream { stream }
}

View File

@@ -45,7 +45,7 @@
use std::error::Error as StdError;
use std::fmt;
#[cfg(feature = "http1")]
#[cfg(not(all(feature = "http1", feature = "http2")))]
use std::marker::PhantomData;
#[cfg(feature = "tcp")]
use std::net::SocketAddr;
@@ -53,7 +53,7 @@ use std::net::SocketAddr;
use std::time::Duration;
use bytes::Bytes;
use pin_project::pin_project;
use pin_project_lite::pin_project;
use tokio::io::{AsyncRead, AsyncWrite};
use super::accept::Accept;
@@ -61,6 +61,8 @@ use crate::body::{Body, HttpBody};
use crate::common::exec::{ConnStreamExec, Exec, NewSvcExec};
#[cfg(feature = "http2")]
use crate::common::io::Rewind;
#[cfg(not(all(feature = "http1", feature = "http2")))]
use crate::common::Never;
use crate::common::{task, Future, Pin, Poll, Unpin};
#[cfg(all(feature = "http1", feature = "http2"))]
use crate::error::{Kind, Parse};
@@ -111,77 +113,93 @@ enum ConnectionMode {
Fallback,
}
/// A stream mapping incoming IOs to new services.
///
/// Yields `Connecting`s that are futures that should be put on a reactor.
#[must_use = "streams do nothing unless polled"]
#[pin_project]
#[derive(Debug)]
pub(super) struct Serve<I, S, E = Exec> {
#[pin]
incoming: I,
make_service: S,
protocol: Http<E>,
}
/// A future building a new `Service` to a `Connection`.
///
/// Wraps the future returned from `MakeService` into one that returns
/// a `Connection`.
#[must_use = "futures do nothing unless polled"]
#[pin_project]
#[derive(Debug)]
pub struct Connecting<I, F, E = Exec> {
#[pin]
future: F,
io: Option<I>,
protocol: Http<E>,
}
#[must_use = "futures do nothing unless polled"]
#[pin_project]
#[derive(Debug)]
pub(super) struct SpawnAll<I, S, E> {
// TODO: re-add `pub(super)` once rustdoc can handle this.
//
// See https://github.com/rust-lang/rust/issues/64705
#[pin]
pub(super) serve: Serve<I, S, E>,
}
/// A future binding a connection with a Service.
///
/// Polling this future will drive HTTP forward.
#[must_use = "futures do nothing unless polled"]
#[pin_project]
pub struct Connection<T, S, E = Exec>
where
S: HttpService<Body>,
{
pub(super) conn: Option<ProtoServer<T, S::ResBody, S, E>>,
#[cfg(all(feature = "http1", feature = "http2"))]
fallback: Fallback<E>,
}
#[pin_project(project = ProtoServerProj)]
pub(super) enum ProtoServer<T, B, S, E = Exec>
where
S: HttpService<Body>,
B: HttpBody,
{
#[cfg(feature = "http1")]
H1(
pin_project! {
/// A stream mapping incoming IOs to new services.
///
/// Yields `Connecting`s that are futures that should be put on a reactor.
#[must_use = "streams do nothing unless polled"]
#[derive(Debug)]
pub(super) struct Serve<I, S, E = Exec> {
#[pin]
proto::h1::Dispatcher<
proto::h1::dispatch::Server<S, Body>,
B,
T,
proto::ServerTransaction,
>,
PhantomData<E>,
),
#[cfg(feature = "http2")]
H2(#[pin] proto::h2::Server<Rewind<T>, S, B, E>),
incoming: I,
make_service: S,
protocol: Http<E>,
}
}
pin_project! {
/// A future building a new `Service` to a `Connection`.
///
/// Wraps the future returned from `MakeService` into one that returns
/// a `Connection`.
#[must_use = "futures do nothing unless polled"]
#[derive(Debug)]
pub struct Connecting<I, F, E = Exec> {
#[pin]
future: F,
io: Option<I>,
protocol: Http<E>,
}
}
pin_project! {
#[must_use = "futures do nothing unless polled"]
#[derive(Debug)]
pub(super) struct SpawnAll<I, S, E> {
// TODO: re-add `pub(super)` once rustdoc can handle this.
//
// See https://github.com/rust-lang/rust/issues/64705
#[pin]
pub(super) serve: Serve<I, S, E>,
}
}
pin_project! {
/// A future binding a connection with a Service.
///
/// Polling this future will drive HTTP forward.
#[must_use = "futures do nothing unless polled"]
pub struct Connection<T, S, E = Exec>
where
S: HttpService<Body>,
{
pub(super) conn: Option<ProtoServer<T, S::ResBody, S, E>>,
fallback: Fallback<E>,
}
}
#[cfg(feature = "http1")]
type Http1Dispatcher<T, B, S> =
proto::h1::Dispatcher<proto::h1::dispatch::Server<S, Body>, B, T, proto::ServerTransaction>;
#[cfg(not(feature = "http1"))]
type Http1Dispatcher<T, B, S> = (Never, PhantomData<(T, Box<Pin<B>>, Box<Pin<S>>)>);
#[cfg(feature = "http2")]
type Http2Server<T, B, S, E> = proto::h2::Server<Rewind<T>, S, B, E>;
#[cfg(not(feature = "http2"))]
type Http2Server<T, B, S, E> = (
Never,
PhantomData<(T, Box<Pin<S>>, Box<Pin<B>>, Box<Pin<E>>)>,
);
pin_project! {
#[project = ProtoServerProj]
pub(super) enum ProtoServer<T, B, S, E = Exec>
where
S: HttpService<Body>,
B: HttpBody,
{
H1 {
#[pin]
h1: Http1Dispatcher<T, B, S>,
},
H2 {
#[pin]
h2: Http2Server<T, B, S, E>,
},
}
}
#[cfg(all(feature = "http1", feature = "http2"))]
@@ -191,6 +209,9 @@ enum Fallback<E> {
Http1Only,
}
#[cfg(not(all(feature = "http1", feature = "http2")))]
type Fallback<E> = PhantomData<E>;
#[cfg(all(feature = "http1", feature = "http2"))]
impl<E> Fallback<E> {
fn to_h2(&self) -> bool {
@@ -557,7 +578,9 @@ impl<E> Http<E> {
conn.set_max_buf_size(max);
}
let sd = proto::h1::dispatch::Server::new(service);
ProtoServer::H1(proto::h1::Dispatcher::new(sd, conn), PhantomData)
ProtoServer::H1 {
h1: proto::h1::Dispatcher::new(sd, conn),
}
}};
}
@@ -573,19 +596,20 @@ impl<E> Http<E> {
let rewind_io = Rewind::new(io);
let h2 =
proto::h2::Server::new(rewind_io, service, &self.h2_builder, self.exec.clone());
ProtoServer::H2(h2)
ProtoServer::H2 { h2 }
}
};
Connection {
conn: Some(proto),
#[cfg(feature = "http1")]
#[cfg(feature = "http2")]
#[cfg(all(feature = "http1", feature = "http2"))]
fallback: if self.mode == ConnectionMode::Fallback {
Fallback::ToHttp2(self.h2_builder.clone(), self.exec.clone())
} else {
Fallback::Http1Only
},
#[cfg(not(all(feature = "http1", feature = "http2")))]
fallback: PhantomData,
}
}
@@ -628,17 +652,22 @@ where
/// This should only be called while the `Connection` future is still
/// pending. If called after `Connection::poll` has resolved, this does
/// nothing.
pub fn graceful_shutdown(self: Pin<&mut Self>) {
match self.project().conn {
pub fn graceful_shutdown(mut self: Pin<&mut Self>) {
match self.conn {
#[cfg(feature = "http1")]
Some(ProtoServer::H1(ref mut h1, _)) => {
Some(ProtoServer::H1 { ref mut h1, .. }) => {
h1.disable_keep_alive();
}
#[cfg(feature = "http2")]
Some(ProtoServer::H2(ref mut h2)) => {
Some(ProtoServer::H2 { ref mut h2 }) => {
h2.graceful_shutdown();
}
None => (),
#[cfg(not(feature = "http1"))]
Some(ProtoServer::H1 { ref mut h1, .. }) => match h1.0 {},
#[cfg(not(feature = "http2"))]
Some(ProtoServer::H2 { ref mut h2 }) => match h2.0 {},
}
}
@@ -662,7 +691,7 @@ where
pub fn try_into_parts(self) -> Option<Parts<I, S>> {
match self.conn.unwrap() {
#[cfg(feature = "http1")]
ProtoServer::H1(h1, _) => {
ProtoServer::H1 { h1, .. } => {
let (io, read_buf, dispatch) = h1.into_inner();
Some(Parts {
io,
@@ -671,8 +700,10 @@ where
_inner: (),
})
}
#[cfg(feature = "http2")]
ProtoServer::H2(_h2) => None,
ProtoServer::H2 { .. } => None,
#[cfg(not(feature = "http1"))]
ProtoServer::H1 { h1, .. } => match h1.0 {},
}
}
@@ -696,7 +727,7 @@ where
loop {
match *self.conn.as_mut().unwrap() {
#[cfg(feature = "http1")]
ProtoServer::H1(ref mut h1, _) => match ready!(h1.poll_without_shutdown(cx)) {
ProtoServer::H1 { ref mut h1, .. } => match ready!(h1.poll_without_shutdown(cx)) {
Ok(()) => return Poll::Ready(Ok(())),
Err(e) => {
#[cfg(feature = "http2")]
@@ -712,7 +743,12 @@ where
}
},
#[cfg(feature = "http2")]
ProtoServer::H2(ref mut h2) => return Pin::new(h2).poll(cx).map_ok(|_| ()),
ProtoServer::H2 { ref mut h2 } => return Pin::new(h2).poll(cx).map_ok(|_| ()),
#[cfg(not(feature = "http1"))]
ProtoServer::H1 { ref mut h1, .. } => match h1.0 {},
#[cfg(not(feature = "http2"))]
ProtoServer::H2 { ref mut h2 } => match h2.0 {},
};
}
}
@@ -738,8 +774,8 @@ where
let conn = self.conn.take();
let (io, read_buf, dispatch) = match conn.unwrap() {
ProtoServer::H1(h1, _) => h1.into_inner(),
ProtoServer::H2(_h2) => {
ProtoServer::H1 { h1, .. } => h1.into_inner(),
ProtoServer::H2 { .. } => {
panic!("h2 cannot into_inner");
}
};
@@ -752,7 +788,7 @@ where
let h2 = proto::h2::Server::new(rewind_io, dispatch.into_service(), builder, exec.clone());
debug_assert!(self.conn.is_none());
self.conn = Some(ProtoServer::H2(h2));
self.conn = Some(ProtoServer::H2 { h2 });
}
/// Enable this connection to support higher-level HTTP upgrades.
@@ -986,9 +1022,14 @@ where
fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
match self.project() {
#[cfg(feature = "http1")]
ProtoServerProj::H1(s, _) => s.poll(cx),
ProtoServerProj::H1 { h1, .. } => h1.poll(cx),
#[cfg(feature = "http2")]
ProtoServerProj::H2(s) => s.poll(cx),
ProtoServerProj::H2 { h2 } => h2.poll(cx),
#[cfg(not(feature = "http1"))]
ProtoServerProj::H1 { h1, .. } => match h1.0 {},
#[cfg(not(feature = "http2"))]
ProtoServerProj::H2 { h2 } => match h2.0 {},
}
}
}
@@ -1002,7 +1043,7 @@ pub(crate) mod spawn_all {
use crate::common::exec::ConnStreamExec;
use crate::common::{task, Future, Pin, Poll, Unpin};
use crate::service::HttpService;
use pin_project::pin_project;
use pin_project_lite::pin_project;
// Used by `SpawnAll` to optionally watch a `Connection` future.
//
@@ -1047,23 +1088,36 @@ pub(crate) mod spawn_all {
// Users cannot import this type, nor the associated `NewSvcExec`. Instead,
// a blanket implementation for `Executor<impl Future>` is sufficient.
#[pin_project]
#[allow(missing_debug_implementations)]
pub struct NewSvcTask<I, N, S: HttpService<Body>, E, W: Watcher<I, S, E>> {
#[pin]
state: State<I, N, S, E, W>,
pin_project! {
#[allow(missing_debug_implementations)]
pub struct NewSvcTask<I, N, S: HttpService<Body>, E, W: Watcher<I, S, E>> {
#[pin]
state: State<I, N, S, E, W>,
}
}
#[pin_project(project = StateProj)]
pub(super) enum State<I, N, S: HttpService<Body>, E, W: Watcher<I, S, E>> {
Connecting(#[pin] Connecting<I, N, E>, W),
Connected(#[pin] W::Future),
pin_project! {
#[project = StateProj]
pub(super) enum State<I, N, S: HttpService<Body>, E, W: Watcher<I, S, E>> {
Connecting {
#[pin]
connecting: Connecting<I, N, E>,
watcher: W,
},
Connected {
#[pin]
future: W::Future,
},
}
}
impl<I, N, S: HttpService<Body>, E, W: Watcher<I, S, E>> NewSvcTask<I, N, S, E, W> {
pub(super) fn new(connecting: Connecting<I, N, E>, watcher: W) -> Self {
NewSvcTask {
state: State::Connecting(connecting, watcher),
state: State::Connecting {
connecting,
watcher,
},
}
}
}
@@ -1090,7 +1144,10 @@ pub(crate) mod spawn_all {
loop {
let next = {
match me.state.as_mut().project() {
StateProj::Connecting(connecting, watcher) => {
StateProj::Connecting {
connecting,
watcher,
} => {
let res = ready!(connecting.poll(cx));
let conn = match res {
Ok(conn) => conn,
@@ -1100,10 +1157,10 @@ pub(crate) mod spawn_all {
return Poll::Ready(());
}
};
let connected = watcher.watch(conn.with_upgrades());
State::Connected(connected)
let future = watcher.watch(conn.with_upgrades());
State::Connected { future }
}
StateProj::Connected(future) => {
StateProj::Connected { future } => {
return future.poll(cx).map(|res| {
if let Err(err) = res {
debug!("connection error: {}", err);
@@ -1171,7 +1228,7 @@ mod upgrades {
#[cfg(feature = "http1")]
Ok(proto::Dispatched::Upgrade(pending)) => {
match self.inner.conn.take() {
Some(ProtoServer::H1(h1, _)) => {
Some(ProtoServer::H1 { h1, .. }) => {
let (io, buf, _) = h1.into_inner();
pending.fulfill(Upgraded::new(io, buf));
return Poll::Ready(Ok(()));

View File

@@ -6,7 +6,7 @@ use std::net::{SocketAddr, TcpListener as StdTcpListener};
#[cfg(feature = "tcp")]
use std::time::Duration;
use pin_project::pin_project;
use pin_project_lite::pin_project;
use tokio::io::{AsyncRead, AsyncWrite};
use super::accept::Accept;
@@ -21,16 +21,17 @@ use super::shutdown::{Graceful, GracefulWatcher};
#[cfg(feature = "tcp")]
use super::tcp::AddrIncoming;
/// A listening HTTP server that accepts connections in both HTTP1 and HTTP2 by default.
///
/// `Server` is a `Future` mapping a bound listener with a set of service
/// handlers. It is built using the [`Builder`](Builder), and the future
/// completes when the server has been shutdown. It should be run by an
/// `Executor`.
#[pin_project]
pub struct Server<I, S, E = Exec> {
#[pin]
spawn_all: SpawnAll<I, S, E>,
pin_project! {
/// A listening HTTP server that accepts connections in both HTTP1 and HTTP2 by default.
///
/// `Server` is a `Future` mapping a bound listener with a set of service
/// handlers. It is built using the [`Builder`](Builder), and the future
/// completes when the server has been shutdown. It should be run by an
/// `Executor`.
pub struct Server<I, S, E = Exec> {
#[pin]
spawn_all: SpawnAll<I, S, E>,
}
}
/// A builder for a [`Server`](Server).

View File

@@ -1,33 +1,36 @@
use std::error::Error as StdError;
use pin_project::pin_project;
use pin_project_lite::pin_project;
use tokio::io::{AsyncRead, AsyncWrite};
use super::conn::{SpawnAll, UpgradeableConnection, Watcher};
use super::accept::Accept;
use super::conn::{SpawnAll, UpgradeableConnection, Watcher};
use crate::body::{Body, HttpBody};
use crate::common::drain::{self, Draining, Signal, Watch, Watching};
use crate::common::exec::{ConnStreamExec, NewSvcExec};
use crate::common::{task, Future, Pin, Poll, Unpin};
use crate::service::{HttpService, MakeServiceRef};
#[allow(missing_debug_implementations)]
#[pin_project]
pub struct Graceful<I, S, F, E> {
#[pin]
state: State<I, S, F, E>,
pin_project! {
#[allow(missing_debug_implementations)]
pub struct Graceful<I, S, F, E> {
#[pin]
state: State<I, S, F, E>,
}
}
#[pin_project(project = StateProj)]
pub(super) enum State<I, S, F, E> {
Running {
drain: Option<(Signal, Watch)>,
#[pin]
spawn_all: SpawnAll<I, S, E>,
#[pin]
signal: F,
},
Draining(Draining),
pin_project! {
#[project = StateProj]
pub(super) enum State<I, S, F, E> {
Running {
drain: Option<(Signal, Watch)>,
#[pin]
spawn_all: SpawnAll<I, S, E>,
#[pin]
signal: F,
},
Draining { draining: Draining },
}
}
impl<I, S, F, E> Graceful<I, S, F, E> {
@@ -71,14 +74,16 @@ where
Poll::Ready(()) => {
debug!("signal received, starting graceful shutdown");
let sig = drain.take().expect("drain channel").0;
State::Draining(sig.drain())
State::Draining {
draining: sig.drain(),
}
}
Poll::Pending => {
let watch = drain.as_ref().expect("drain channel").1.clone();
return spawn_all.poll_watch(cx, &GracefulWatcher(watch));
}
},
StateProj::Draining(ref mut draining) => {
StateProj::Draining { ref mut draining } => {
return Pin::new(draining).poll(cx).map(Ok);
}
}

View File

@@ -199,13 +199,14 @@ mod addr_stream {
use crate::common::{task, Pin, Poll};
/// A transport returned yieled by `AddrIncoming`.
#[pin_project::pin_project]
#[derive(Debug)]
pub struct AddrStream {
#[pin]
inner: TcpStream,
pub(super) remote_addr: SocketAddr,
pin_project_lite::pin_project! {
/// A transport returned yieled by `AddrIncoming`.
#[derive(Debug)]
pub struct AddrStream {
#[pin]
inner: TcpStream,
pub(super) remote_addr: SocketAddr,
}
}
impl AddrStream {