Open
Description
Here's a little program that starts 2 threads:
- Bind a Hyper server that simply reads through the request body and returns 200.
- Make a Hyper client and make parallel POST requests with 10MB bodies in a loop.
When configured to use HTTP1, everything runs fine, and we process ~250 requests per second. When instead switched to use HTTP2, no request completes.
The parallelism seems to be important to the issue - when only running 1 request at a time, we do make progress with HTTP2. It is very slow compared to HTTP1, though - ~.2 requests/sec vs ~35 requests/sec.
use futures::future;
use futures::stream;
use futures::{Future, Sink, Stream};
use hyper::body::Sender;
use hyper::client::HttpConnector;
use hyper::service::service_fn;
use hyper::{Body, Chunk, Client, Method, Request, Response, Server};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::thread;
use std::time::Duration;
use tokio::net::TcpListener;
fn main() {
let http2 = true;
let listener = TcpListener::bind(&"127.0.0.1:0".parse().unwrap()).unwrap();
let port = listener.local_addr().unwrap().port();
thread::spawn(move || serve(listener, http2));
let big = Arc::new(AtomicUsize::new(0));
thread::spawn({
let big = big.clone();
move || client(port, big, http2)
});
loop {
thread::sleep(Duration::from_secs(5));
println!("{:010}", big.load(Ordering::SeqCst));
}
}
fn serve(listener: TcpListener, http2: bool) {
hyper::rt::run(
Server::builder(listener.incoming())
.http2_only(http2)
.serve(|| service_fn(service))
.map_err(|e| println!("{}", e)),
);
}
fn service(request: Request<Body>) -> impl Future<Item = Response<Body>, Error = hyper::Error> {
request
.into_body()
.for_each(|_| Ok(()))
.map(|_| Response::new(Body::empty()))
}
fn client(port: u16, big: Arc<AtomicUsize>, http2: bool) {
let url = format!("http://localhost:{}", port);
let client = Client::builder().http2_only(http2).build_http();
hyper::rt::run(future::lazy(move || {
for _ in 0..16 {
big_requests(url.clone(), client.clone(), big.clone());
}
Ok(())
}));
}
fn big_requests(url: String, client: Client<HttpConnector>, count: Arc<AtomicUsize>) {
let f = make_big_request(&url, &client).then(|_| {
count.fetch_add(1, Ordering::SeqCst);
big_requests(url, client, count);
Ok(())
});
tokio::spawn(f);
}
fn make_big_request(
url: &str,
client: &Client<HttpConnector>,
) -> impl Future<Item = (), Error = ()> {
let (sender, body) = Body::channel();
tokio::spawn(big_body(sender));
let request = Request::builder()
.method(Method::POST)
.uri(url)
.body(body)
.unwrap();
client
.request(request)
.map(|_| ())
.map_err(|e| println!("{}", e))
}
fn big_body(sender: Sender) -> impl Future<Item = (), Error = ()> {
let body = stream::iter_ok::<_, hyper::Error>(0..10_240).map(|_| Chunk::from(vec![0; 1024]));
sender
.send_all(body)
.map(|_| ())
.map_err(|e| println!("{}", e))
}