aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLoïc Hoguin <[email protected]>2025-01-08 16:54:14 +0100
committerLoïc Hoguin <[email protected]>2025-01-15 13:31:19 +0100
commit1724575b4222465ed71f3103b83db549a8036e16 (patch)
tree5970d60f92500b43017069cfd1159eadec462955
parent086f60cca45b2ebc1a8181545d64461436791dcc (diff)
downloadcowboy-1724575b4222465ed71f3103b83db549a8036e16.tar.gz
cowboy-1724575b4222465ed71f3103b83db549a8036e16.tar.bz2
cowboy-1724575b4222465ed71f3103b83db549a8036e16.zip
Avoid resetting HTTP/2 idle_timeout timer too often
Following the same strategy as Websocket described in commit cbed21c383e4cebb7df5a0a8b81f18c1738bef3e Gains are comparable as far as Websocket over HTTP/2 is concerned.
-rw-r--r--src/cowboy_http2.erl38
-rw-r--r--test/ws_perf_SUITE.erl2
2 files changed, 30 insertions, 10 deletions
diff --git a/src/cowboy_http2.erl b/src/cowboy_http2.erl
index 2e73d5f..0e110cd 100644
--- a/src/cowboy_http2.erl
+++ b/src/cowboy_http2.erl
@@ -85,6 +85,14 @@
state :: {module, any()}
}).
+%% We don't want to reset the idle timeout too often,
+%% so we don't reset it on data. Instead we reset the
+%% number of ticks we have observed. We divide the
+%% timeout value by a value and that value becomes
+%% the number of ticks at which point we can drop
+%% the connection. This value is the number of ticks.
+-define(IDLE_TIMEOUT_TICKS, 10).
+
-record(state, {
parent = undefined :: pid(),
ref :: ranch:ref(),
@@ -95,6 +103,7 @@
%% Timer for idle_timeout; also used for goaway timers.
timer = undefined :: undefined | reference(),
+ idle_timeout_num = 0 :: 0..?IDLE_TIMEOUT_TICKS,
%% Remote address and port for the connection.
peer = undefined :: {inet:ip_address(), inet:port_number()},
@@ -166,7 +175,7 @@ init(Parent, Ref, Socket, Transport, ProxyHeader, Opts, Peer, Sock, Cert, Buffer
State = set_idle_timeout(init_rate_limiting(#state{parent=Parent, ref=Ref, socket=Socket,
transport=Transport, proxy_header=ProxyHeader,
opts=Opts, peer=Peer, sock=Sock, cert=Cert,
- http2_status=sequence, http2_machine=HTTP2Machine})),
+ http2_status=sequence, http2_machine=HTTP2Machine}), 0),
safe_setopts_active(State),
case Buffer of
<<>> -> loop(State, Buffer);
@@ -222,7 +231,7 @@ init(Parent, Ref, Socket, Transport, ProxyHeader, Opts, Peer, Sock, Cert, Buffer
<<"connection">> => <<"Upgrade">>,
<<"upgrade">> => <<"h2c">>
}, ?MODULE, undefined}), %% @todo undefined or #{}?
- State = set_idle_timeout(init_rate_limiting(State2#state{http2_status=sequence})),
+ State = set_idle_timeout(init_rate_limiting(State2#state{http2_status=sequence}), 0),
%% In the case of HTTP/1.1 Upgrade we cannot send the Preface
%% until we send the 101 response.
ok = maybe_socket_error(State, Transport:send(Socket, Preface)),
@@ -249,7 +258,7 @@ loop(State=#state{parent=Parent, socket=Socket, transport=Transport,
receive
%% Socket messages.
{OK, Socket, Data} when OK =:= element(1, Messages) ->
- parse(set_idle_timeout(State), << Buffer/binary, Data/binary >>);
+ parse(State#state{idle_timeout_num=0}, << Buffer/binary, Data/binary >>);
{Closed, Socket} when Closed =:= element(2, Messages) ->
Reason = case State#state.http2_status of
closing -> {stop, closed, 'The client is going away.'};
@@ -273,8 +282,7 @@ loop(State=#state{parent=Parent, socket=Socket, transport=Transport,
sys:handle_system_msg(Request, From, Parent, ?MODULE, [], {State, Buffer});
%% Timeouts.
{timeout, TimerRef, idle_timeout} ->
- terminate(State, {stop, timeout,
- 'Connection idle longer than configuration allows.'});
+ tick_idle_timeout(State, Buffer);
{timeout, Ref, {shutdown, Pid}} ->
cowboy_children:shutdown_timeout(Children, Ref, Pid),
loop(State, Buffer);
@@ -302,12 +310,24 @@ loop(State=#state{parent=Parent, socket=Socket, transport=Transport,
terminate(State, {internal_error, timeout, 'No message or data received before timeout.'})
end.
-set_idle_timeout(State=#state{http2_status=Status, timer=TimerRef})
+tick_idle_timeout(State=#state{idle_timeout_num=?IDLE_TIMEOUT_TICKS}, _) ->
+ terminate(State, {stop, timeout,
+ 'Connection idle longer than configuration allows.'});
+tick_idle_timeout(State=#state{idle_timeout_num=TimeoutNum}, Buffer) ->
+ loop(set_idle_timeout(State, TimeoutNum + 1), Buffer).
+
+set_idle_timeout(State=#state{http2_status=Status, timer=TimerRef}, _)
when Status =:= closing_initiated orelse Status =:= closing,
TimerRef =/= undefined ->
State;
-set_idle_timeout(State=#state{opts=Opts}) ->
- set_timeout(State, maps:get(idle_timeout, Opts, 60000), idle_timeout).
+set_idle_timeout(State=#state{opts=Opts}, TimeoutNum) ->
+ case maps:get(idle_timeout, Opts, 60000) of
+ infinity ->
+ State#state{timer=undefined};
+ Timeout ->
+ set_timeout(State#state{idle_timeout_num=TimeoutNum},
+ Timeout div ?IDLE_TIMEOUT_TICKS, idle_timeout)
+ end.
set_timeout(State=#state{timer=TimerRef0}, Timeout, Message) ->
ok = case TimerRef0 of
@@ -323,7 +343,7 @@ set_timeout(State=#state{timer=TimerRef0}, Timeout, Message) ->
maybe_reset_idle_timeout(State=#state{opts=Opts}) ->
case maps:get(reset_idle_timeout_on_send, Opts, false) of
true ->
- set_idle_timeout(State);
+ State#state{idle_timeout_num=0};
false ->
State
end.
diff --git a/test/ws_perf_SUITE.erl b/test/ws_perf_SUITE.erl
index e02a60c..19d1c31 100644
--- a/test/ws_perf_SUITE.erl
+++ b/test/ws_perf_SUITE.erl
@@ -39,7 +39,7 @@ groups() ->
init_per_suite(Config) ->
%% Optionally enable `perf` for the current node.
-% spawn(fun() -> ct:pal(os:cmd("perf record -g -F 9999 -o /tmp/ws_perf.data -p " ++ os:getpid() ++ " -- sleep 11")) end),
+% spawn(fun() -> ct:pal(os:cmd("perf record -g -F 9999 -o /tmp/ws_perf.data -p " ++ os:getpid() ++ " -- sleep 60")) end),
Config.
end_per_suite(_Config) ->