hyperium / hyperium/hyper

Request bodies are processed slower over HTTP2

Open
#1,813 13 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

A-http2 C-performance
Dominant language
Rust
Stars
16.3k
Forks
1.8k
Avg merge
1d 22h
Merged PRs (30d)
14

Description

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))
}

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Run the supplied Rust reproducer with http2 set to true and compare it with HTTP/1, focusing on parallel 10MB POST requests and request completion rates. Trace the client and server request-body flow shown in the example, then verify that parallel HTTP/2 requests make progress without regressing the reported HTTP/1 behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
networking, performance
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
38/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.