From a0d70f2fb59586acf9f23505a0f7033a9a6f164f Mon Sep 17 00:00:00 2001 From: Benoit Chesneau Date: Mon, 21 Sep 2026 21:28:11 +0200 Subject: [PATCH] Update h2 to 0.12.1 h2 0.12.1 decodes the header block of a HEADERS frame it rejects with a stream error. Before, a response that crossed hackney's RST_STREAM had its block dropped undecoded, the HPACK tables fell out of sync, and the next response on the shared connection failed with COMPRESSION_ERROR, closing it. The dead async consumer test answers the reset stream again, which is the case that failed on CI before the h2 fix. --- NEWS.md | 7 +++++++ rebar.config | 2 +- test/hackney_http2_shared_conn_tests.erl | 8 +++++--- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/NEWS.md b/NEWS.md index f226df12..e62a3c7a 100644 --- a/NEWS.md +++ b/NEWS.md @@ -14,6 +14,9 @@ unreleased - Unregistering a pooled HTTP/2 connection no longer leaks its per-host slot. The pool dropped its monitor on the connection, so the slot was never released when the connection stopped. +- A response that crosses a reset of its stream no longer closes the HTTP/2 + connection. `h2` dropped the header block of that response without decoding + it, so the next response on the connection failed with COMPRESSION_ERROR. - A request that races a peer-initiated close now returns `{error, closed}` instead of `{error, invalid_state}`. A connection that sees the peer close stays alive briefly so late calls get an answer, and during that window every @@ -22,6 +25,10 @@ unreleased was gone, `{error, invalid_state}` while it lingered. Callers can now tell a closed connection from a misuse of the API (#932, #933, thanks @kpy3). +### Changed + +- Update `h2` to 0.12.1. + 4.7.4 - 2026-08-12 ------------------ diff --git a/rebar.config b/rebar.config index cc009f0b..9300370f 100644 --- a/rebar.config +++ b/rebar.config @@ -55,7 +55,7 @@ %% Pure Erlang QUIC + HTTP/3 stack {quic, "~>1.8.0"}, %% Pure Erlang HTTP/2 stack - {h2, "~>0.12.0"}, + {h2, "~>0.12.1"}, %% WebTransport client (HTTP/3 and HTTP/2) - powers the wt_* API {webtransport, "~>0.4.5"}, {idna, "~>7.1.0"}, diff --git a/test/hackney_http2_shared_conn_tests.erl b/test/hackney_http2_shared_conn_tests.erl index 9ac5671b..8d804c09 100644 --- a/test/hackney_http2_shared_conn_tests.erl +++ b/test/hackney_http2_shared_conn_tests.erl @@ -81,13 +81,15 @@ dead_async_consumer() -> receive stop -> ok end end), receive async_sent -> ok after 5000 -> error(no_async_request) end, - %% The handler never answers: a response crossing the client's - %% RST_STREAM is a separate h2 HPACK issue, not what this covers. - {_AsyncHandler, _} = started(<<"/async">>), + {AsyncHandler, _} = started(<<"/async">>), Conn = shared_conn(Opts, Port), ?assert(lists:member(Consumer, monitored(Conn))), exit(Consumer, kill), ok = wait_until(fun() -> not lists:member(Consumer, monitored(Conn)) end), + %% Answer the reset stream: the response can cross the client's + %% RST_STREAM, and its header block must still be decoded or the + %% next response fails with COMPRESSION_ERROR (fixed in h2 0.12.1). + AsyncHandler ! respond, ?assertMatch({ok, 200, _, <<"ok">>}, hackney:request(get, <>, [], <<>>, Opts)), ?assertEqual(Conn, shared_conn(Opts, Port))