From d1e3f5762967729b7b188a278d68a5eb2e24663d Mon Sep 17 00:00:00 2001 From: 0x676e67 Date: Fri, 15 Nov 2024 20:44:04 +0800 Subject: [PATCH] Sync upstream (#14) * fix: streams awaiting capacity lockout (#730) (#734) This PR changes the the assign-capacity queue to prioritize streams that are send-ready. This is necessary to prevent a lockout when streams aren't able to proceed while waiting for connection capacity, but there is none. Closes /~https://github.com/hyperium/hyper/issues/3338 Co-authored-by: dswij * v0.3.23 * streams: limit error resets for misbehaving connections This change causes GOAWAYs to be issued to misbehaving connections which for one reason or another cause us to emit lots of error resets. Error resets are not generally expected from valid implementations anyways. The threshold after which we issue GOAWAYs is tunable, and will default to 1024. * Prepare v0.3.24 * perf: optimize header list size calculations (#750) This speeds up loading blocks in cases where we have many headers already. * v0.3.25 * refactor: cleanup new unused warnings (#757) * fix: limit number of CONTINUATION frames allowed Calculate the amount of allowed CONTINUATION frames based on other settings. max_header_list_size / max_frame_size That is about how many CONTINUATION frames would be needed to send headers up to the max allowed size. We then multiply by that by a small amount, to allow for implementations that don't perfectly pack into the minimum frames *needed*. In practice, *much* more than that would be a very inefficient peer, or a peer trying to waste resources. See https://seanmonstar.com/blog/hyper-http2-continuation-flood/ for more info. * v0.3.26 * fix: return a WriteZero error if frames cannot be written (#783) Some operating systems will allow you continually call `write()` on a closed socket, and will return `Ok(0)` instead of an error. This patch checks for a zero write, and instead of looping forever trying to write, returns a proper error. Closes #781 Co-authored-by: leibeiyi * lints: fix unexpected cfgs warnings * ci: pin deps for MSRV * ci: pin more deps for MSRV job (#817) * fix: notify_recv after send_reset() in reset_on_recv_stream_err() to ensure local stream is released properly (#816) Similar to what have been done in fn send_reset(), we should notify RecvStream that is parked after send_reset(). Co-authored-by: Jiahao Liang --------- Co-authored-by: Sean McArthur Co-authored-by: dswij Co-authored-by: Noah Kennedy Co-authored-by: beiyi lei Co-authored-by: leibeiyi Co-authored-by: Jiahao Liang --- .github/workflows/CI.yml | 45 ++++++++++++++++++++++++-- CHANGELOG.md | 27 ++-------------- src/codec/framed_write.rs | 8 ++++- src/frame/headers.rs | 2 ++ src/proto/streams/streams.rs | 3 ++ tests/h2-tests/tests/prioritization.rs | 1 + tests/h2-tests/tests/server.rs | 1 + tests/h2-tests/tests/stream_states.rs | 9 ++++-- 8 files changed, 67 insertions(+), 29 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 6abf65b4..17688485 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -22,6 +22,44 @@ jobs: - run: cargo fmt --all --check + test: + name: Test + needs: [style] + runs-on: ubuntu-latest + env: + RUSTFLAGS: -Dwarnings + strategy: + matrix: + rust: + - beta + - stable + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install Rust (${{ matrix.rust }}) + uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + + - name: Install libssl-dev + run: sudo apt-get update && sudo apt-get install libssl-dev + - name: Build without unstable flag + run: cargo build + + - name: Check with unstable flag + run: cargo check --features unstable + + - name: Run lib tests and doc tests + run: cargo test + + - name: Run integration tests + run: cargo test -p h2-tests + + - name: Run h2spec + run: ./ci/h2spec.sh + if: matrix.rust == 'stable' + #clippy_check: # runs-on: ubuntu-latest # steps: @@ -49,9 +87,12 @@ jobs: with: toolchain: ${{ steps.msrv.outputs.version }} - - name: Make sure tokio 1.38.1 is used for MSRV + - name: Pin some dependencies for MSRV run: | - cargo update cargo update --package tokio --precise 1.38.1 + cargo update --package tokio-util --precise 0.7.11 + cargo update --package hashbrown --precise 0.15.0 + + - run: cargo check -p h2 - run: cargo check -p rh2 diff --git a/CHANGELOG.md b/CHANGELOG.md index 4abbf24a..f5dc7467 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,31 +1,10 @@ -# 0.4.5 (May 17, 2024) - -* Fix race condition that sometimes hung connections during shutdown. -* Fix pseudo header construction for CONNECT and OPTIONS requests. - -# 0.4.4 (April 3, 2024) +# 0.3.26 (April 3, 2024) * Limit number of CONTINUATION frames for misbehaving connections. -# 0.4.3 (March 15, 2024) - -* Fix flow control limits to not apply until receiving SETTINGS ack. -* Fix not returning an error if IO ended without `close_notify`. -* Improve performance of decoding many headers. - -# 0.4.2 (January 17th, 2024) - -* Limit error resets for misbehaving connections. -* Fix selecting MAX_CONCURRENT_STREAMS value if no value is advertised initially. - -# 0.4.1 (January 8, 2024) - -* Fix assigning connection capacity which could starve streams in some instances. - -# 0.4.0 (November 15, 2023) +# 0.3.25 (March 15, 2024) -* Update to `http` 1.0. -* Remove deprecated `Server::poll_close()`. +* Improve performance decoding many headers. # 0.3.24 (January 17, 2024) diff --git a/src/codec/framed_write.rs b/src/codec/framed_write.rs index c88af02d..a94647bc 100644 --- a/src/codec/framed_write.rs +++ b/src/codec/framed_write.rs @@ -133,7 +133,7 @@ where loop { while !self.encoder.is_empty() { - match self.encoder.next { + let n = match self.encoder.next { Some(Next::Data(ref mut frame)) => { tracing::trace!(queued_data_frame = true); let mut buf = (&mut self.encoder.buf).chain(frame.payload_mut()); @@ -148,6 +148,12 @@ where ))? } }; + if n == 0 { + return Poll::Ready(Err(io::Error::new( + io::ErrorKind::WriteZero, + "failed to write frame to socket", + ))); + } } match self.encoder.unset_frame() { diff --git a/src/frame/headers.rs b/src/frame/headers.rs index 4a73a1a9..68c81bc1 100644 --- a/src/frame/headers.rs +++ b/src/frame/headers.rs @@ -1052,6 +1052,8 @@ fn decoded_header_size(name: usize, value: usize) -> usize { #[cfg(test)] mod test { + use std::iter::FromIterator; + use super::*; use crate::frame; use crate::hpack::{huffman, Encoder}; diff --git a/src/proto/streams/streams.rs b/src/proto/streams/streams.rs index ead39297..e632b774 100644 --- a/src/proto/streams/streams.rs +++ b/src/proto/streams/streams.rs @@ -1599,6 +1599,9 @@ impl Actions { // Reset the stream. self.send .send_reset(reason, initiator, buffer, stream, counts, &mut self.task); + self.recv.enqueue_reset_expiration(stream, counts); + // if a RecvStream is parked, ensure it's notified + stream.notify_recv(); Ok(()) } else { tracing::warn!( diff --git a/tests/h2-tests/tests/prioritization.rs b/tests/h2-tests/tests/prioritization.rs index dd4ed9fe..11d2c2cc 100644 --- a/tests/h2-tests/tests/prioritization.rs +++ b/tests/h2-tests/tests/prioritization.rs @@ -1,3 +1,4 @@ +use futures::future::{join, select}; use futures::{pin_mut, FutureExt, StreamExt}; use h2_support::prelude::*; diff --git a/tests/h2-tests/tests/server.rs b/tests/h2-tests/tests/server.rs index 91c8d40c..91916406 100644 --- a/tests/h2-tests/tests/server.rs +++ b/tests/h2-tests/tests/server.rs @@ -1,5 +1,6 @@ #![deny(warnings)] +use futures::future::join; use futures::StreamExt; use h2_support::prelude::*; use tokio::io::AsyncWriteExt; diff --git a/tests/h2-tests/tests/stream_states.rs b/tests/h2-tests/tests/stream_states.rs index 9a377d79..d511f92a 100644 --- a/tests/h2-tests/tests/stream_states.rs +++ b/tests/h2-tests/tests/stream_states.rs @@ -1,6 +1,6 @@ #![deny(warnings)] -use futures::future::lazy; +use futures::future::{join, join3, lazy, try_join}; use futures::{FutureExt, StreamExt, TryStreamExt}; use h2_support::prelude::*; use h2_support::util::yield_once; @@ -536,7 +536,12 @@ async fn recv_next_stream_id_updated_by_malformed_headers() { client.recv_frame(frames::go_away(1).protocol_error()).await; }; let srv = async move { - let mut srv = server::handshake(io).await.expect("handshake"); + let mut srv = server::Builder::new() + // forget the bad stream immediately + .max_concurrent_reset_streams(0) + .handshake::<_, Bytes>(io) + .await + .expect("handshake"); let res = srv.next().await.unwrap(); let err = res.unwrap_err(); assert_eq!(err.reason(), Some(h2::Reason::PROTOCOL_ERROR));