Skip to content

Request bodies are processed slower over HTTP2 #1813

Open
@sfackler

Description

@sfackler

Here's a little program that starts 2 threads:

  1. Bind a Hyper server that simply reads through the request body and returns 200.
  2. 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))
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-http2Area: HTTP/2 specific.C-performanceCategory: performance. This is making existing behavior go faster.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions