You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by va...@apache.org on 2026/10/05 23:21:56 UTC
(couchdb-gun) 06/16: Do not crash gun_pool on HTTP/2 server-pushed streams
This is an automated email from the ASF dual-hosted git repository.
nickva pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/couchdb-gun.git
commit 1fe90ade5a996878e09481eb93cb975f3babe6c5
Author: Loïc Hoguin <es...@ninenines.eu>
AuthorDate: Sat Sep 26 12:30:57 2026 +0200
Do not crash gun_pool on HTTP/2 server-pushed streams
A pushed stream calls response_end on a ref that never passed
request_start. That event had no clause and crashed the
connection. An unknown ref leaves the stream counter unchanged.
---
src/gun_pool_events_h.erl | 6 +++++-
test/pool_SUITE.erl | 40 ++++++++++++++++++++++++++++++++++++++++
2 files changed, 45 insertions(+), 1 deletion(-)
diff --git a/src/gun_pool_events_h.erl b/src/gun_pool_events_h.erl
index 3bae13a..ba27543 100644
--- a/src/gun_pool_events_h.erl
+++ b/src/gun_pool_events_h.erl
@@ -107,7 +107,11 @@ response_end(Event=#{stream_ref := StreamRef}, State0=#{table := Tid}) ->
_ = ets:update_counter(Tid, self(), -1),
maps:remove(StreamRef, State0);
#{StreamRef := {IsFin, nofin}} ->
- State0#{StreamRef => {IsFin, fin}}
+ State0#{StreamRef => {IsFin, fin}};
+ %% A pushed stream calls response_end without request_start.
+ %% The ref is unknown, so the counter stays unchanged.
+ _ ->
+ State0
end,
propagate(Event, State, ?FUNCTION_NAME).
diff --git a/test/pool_SUITE.erl b/test/pool_SUITE.erl
index a125a09..610191a 100644
--- a/test/pool_SUITE.erl
+++ b/test/pool_SUITE.erl
@@ -41,6 +41,8 @@ do_proto_opts() ->
Routes = [
{"/", hello_h, []},
{"/delay", delayed_hello_h, 3000},
+ {"/empty", empty_h, []},
+ {"/push", push_h, []},
{"/ws", ws_echo_h, []}
],
#{
@@ -375,6 +377,44 @@ reconnect_h2(Config) ->
%% @todo reconnect_ws
+push_promise_no_crash(Config) ->
+ doc("Confirm a server-pushed HTTP/2 stream does not crash "
+ "the pooled connection or change its stream counter."),
+ Port = config(port, Config),
+ Authority = ["localhost:", integer_to_binary(Port)],
+ {ok, ManagerPid} = gun_pool:start_pool("localhost", Port, #{
+ conn_opts => #{protocols => [http2]},
+ scope => ?FUNCTION_NAME,
+ size => 1
+ }),
+ gun_pool:await_up(ManagerPid),
+ {async, PoolRef={ConnPid, _}} = gun_pool:get("/push",
+ #{<<"host">> => Authority},
+ #{scope => ?FUNCTION_NAME}),
+ {push, PromisedRef1, <<"GET">>, _, _} = gun_pool:await(PoolRef),
+ {push, PromisedRef2, <<"GET">>, _, _} = gun_pool:await(PoolRef),
+ {response, nofin, 200, _} = gun_pool:await(PoolRef),
+ {ok, <<"Hello world!">>} = gun_pool:await_body(PoolRef),
+ ok = do_await_pushed({ConnPid, PromisedRef1}),
+ ok = do_await_pushed({ConnPid, PromisedRef2}),
+ {operational, #{table := Tid, conns := Conns}} = gun_pool:info(ManagerPid),
+ {up, http2, _} = maps:get(ConnPid, Conns),
+ [{_, 0}] = ets:lookup(Tid, ConnPid),
+ {async, NextRef} = gun_pool:get("/",
+ #{<<"host">> => Authority},
+ #{scope => ?FUNCTION_NAME}),
+ {response, nofin, 200, _} = gun_pool:await(NextRef),
+ {ok, <<"Hello world!">>} = gun_pool:await_body(NextRef).
+
+do_await_pushed(PoolRef) ->
+ case gun_pool:await(PoolRef) of
+ {response, fin, 200, _} ->
+ ok;
+ {response, nofin, 200, _} ->
+ {ok, _} = gun_pool:await_body(PoolRef),
+ ok
+ end.
+
stop_pool(Config) ->
doc("Confirm the pool can be stopped."),
Port = config(port, Config),