feature(tcp): alarm when tcp connection congested
This commit is contained in:
parent
505257b25b
commit
d00ea48752
|
@ -1139,6 +1139,13 @@ listener.tcp.external.send_timeout_close = on
|
||||||
## Value: on | off
|
## Value: on | off
|
||||||
## listener.tcp.external.tune_buffer = off
|
## listener.tcp.external.tune_buffer = off
|
||||||
|
|
||||||
|
## The socket is set to a busy state when the amount of data queued internally
|
||||||
|
## by the ERTS socket implementation reaches this limit.
|
||||||
|
##
|
||||||
|
## Value: on | off
|
||||||
|
## Defaults to 1MB
|
||||||
|
## listener.tcp.external.high_watermark = 1MB
|
||||||
|
|
||||||
## The TCP_NODELAY flag for MQTT connections. Small amounts of data are
|
## The TCP_NODELAY flag for MQTT connections. Small amounts of data are
|
||||||
## sent immediately if the option is enabled.
|
## sent immediately if the option is enabled.
|
||||||
##
|
##
|
||||||
|
|
|
@ -1244,6 +1244,11 @@ end}.
|
||||||
hidden
|
hidden
|
||||||
]}.
|
]}.
|
||||||
|
|
||||||
|
{mapping, "listener.tcp.$name.high_watermark", "emqx.listeners", [
|
||||||
|
{datatype, bytesize},
|
||||||
|
{default, "1MB"}
|
||||||
|
]}.
|
||||||
|
|
||||||
{mapping, "listener.tcp.$name.tune_buffer", "emqx.listeners", [
|
{mapping, "listener.tcp.$name.tune_buffer", "emqx.listeners", [
|
||||||
{datatype, flag},
|
{datatype, flag},
|
||||||
hidden
|
hidden
|
||||||
|
@ -1336,6 +1341,11 @@ end}.
|
||||||
hidden
|
hidden
|
||||||
]}.
|
]}.
|
||||||
|
|
||||||
|
{mapping, "listener.ssl.$name.high_watermark", "emqx.listeners", [
|
||||||
|
{datatype, bytesize},
|
||||||
|
{default, "1MB"}
|
||||||
|
]}.
|
||||||
|
|
||||||
{mapping, "listener.ssl.$name.tune_buffer", "emqx.listeners", [
|
{mapping, "listener.ssl.$name.tune_buffer", "emqx.listeners", [
|
||||||
{datatype, flag},
|
{datatype, flag},
|
||||||
hidden
|
hidden
|
||||||
|
@ -1844,6 +1854,7 @@ end}.
|
||||||
{recbuf, cuttlefish:conf_get(Prefix ++ ".recbuf", Conf, undefined)},
|
{recbuf, cuttlefish:conf_get(Prefix ++ ".recbuf", Conf, undefined)},
|
||||||
{sndbuf, cuttlefish:conf_get(Prefix ++ ".sndbuf", Conf, undefined)},
|
{sndbuf, cuttlefish:conf_get(Prefix ++ ".sndbuf", Conf, undefined)},
|
||||||
{buffer, cuttlefish:conf_get(Prefix ++ ".buffer", Conf, undefined)},
|
{buffer, cuttlefish:conf_get(Prefix ++ ".buffer", Conf, undefined)},
|
||||||
|
{high_watermark, cuttlefish:conf_get(Prefix ++ ".high_watermark", Conf, undefined)},
|
||||||
{nodelay, cuttlefish:conf_get(Prefix ++ ".nodelay", Conf, true)},
|
{nodelay, cuttlefish:conf_get(Prefix ++ ".nodelay", Conf, true)},
|
||||||
{reuseaddr, cuttlefish:conf_get(Prefix ++ ".reuseaddr", Conf, undefined)}])
|
{reuseaddr, cuttlefish:conf_get(Prefix ++ ".reuseaddr", Conf, undefined)}])
|
||||||
end,
|
end,
|
||||||
|
|
|
@ -344,6 +344,8 @@ normalize_message(partition, #{occurred := Node}) ->
|
||||||
list_to_binary(io_lib:format("Partition occurs at node ~s", [Node]));
|
list_to_binary(io_lib:format("Partition occurs at node ~s", [Node]));
|
||||||
normalize_message(<<"resource", _/binary>>, #{type := Type, id := ID}) ->
|
normalize_message(<<"resource", _/binary>>, #{type := Type, id := ID}) ->
|
||||||
list_to_binary(io_lib:format("Resource ~s(~s) is down", [Type, ID]));
|
list_to_binary(io_lib:format("Resource ~s(~s) is down", [Type, ID]));
|
||||||
|
normalize_message(<<"mqtt_conn/congested/", ClientId/binary>>, _) ->
|
||||||
|
list_to_binary(io_lib:format("MQTT connection for clientid '~s' is congested", [ClientId]));
|
||||||
normalize_message(_Name, _UnknownDetails) ->
|
normalize_message(_Name, _UnknownDetails) ->
|
||||||
<<"Unknown alarm">>.
|
<<"Unknown alarm">>.
|
||||||
|
|
||||||
|
|
|
@ -103,6 +103,9 @@
|
||||||
|
|
||||||
-define(ENABLED(X), (X =/= undefined)).
|
-define(ENABLED(X), (X =/= undefined)).
|
||||||
|
|
||||||
|
-define(ALARM_TCP_CONGEST(Channel),
|
||||||
|
list_to_binary(io_lib:format("mqtt_conn/congested/~s", [emqx_channel:info(clientid, Channel)]))).
|
||||||
|
|
||||||
-dialyzer({no_match, [info/2]}).
|
-dialyzer({no_match, [info/2]}).
|
||||||
-dialyzer({nowarn_function, [ init/4
|
-dialyzer({nowarn_function, [ init/4
|
||||||
, init_state/3
|
, init_state/3
|
||||||
|
@ -429,6 +432,7 @@ handle_msg(Msg, State) ->
|
||||||
|
|
||||||
terminate(Reason, State = #state{channel = Channel}) ->
|
terminate(Reason, State = #state{channel = Channel}) ->
|
||||||
?LOG(debug, "Terminated due to ~p", [Reason]),
|
?LOG(debug, "Terminated due to ~p", [Reason]),
|
||||||
|
emqx_alarm:deactivate(?ALARM_TCP_CONGEST(Channel)),
|
||||||
emqx_channel:terminate(Reason, Channel),
|
emqx_channel:terminate(Reason, Channel),
|
||||||
close_socket(State),
|
close_socket(State),
|
||||||
exit(Reason).
|
exit(Reason).
|
||||||
|
@ -595,11 +599,12 @@ serialize_and_inc_stats_fun(#state{serialize = Serialize}) ->
|
||||||
%% Send data
|
%% Send data
|
||||||
|
|
||||||
-spec(send(iodata(), state()) -> ok).
|
-spec(send(iodata(), state()) -> ok).
|
||||||
send(IoData, #state{transport = Transport, socket = Socket}) ->
|
send(IoData, #state{transport = Transport, socket = Socket, channel = Channel}) ->
|
||||||
Oct = iolist_size(IoData),
|
Oct = iolist_size(IoData),
|
||||||
ok = emqx_metrics:inc('bytes.sent', Oct),
|
ok = emqx_metrics:inc('bytes.sent', Oct),
|
||||||
emqx_pd:inc_counter(outgoing_bytes, Oct),
|
emqx_pd:inc_counter(outgoing_bytes, Oct),
|
||||||
case Transport:async_send(Socket, IoData) of
|
maybe_warn_congestion(Socket, Transport, Channel),
|
||||||
|
case Transport:async_send(Socket, IoData, [nosuspend]) of
|
||||||
ok -> ok;
|
ok -> ok;
|
||||||
Error = {error, _Reason} ->
|
Error = {error, _Reason} ->
|
||||||
%% Send an inet_reply to postpone handling the error
|
%% Send an inet_reply to postpone handling the error
|
||||||
|
@ -607,6 +612,36 @@ send(IoData, #state{transport = Transport, socket = Socket}) ->
|
||||||
ok
|
ok
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
maybe_warn_congestion(Socket, Transport, Channel) ->
|
||||||
|
IsCongestAlarmSet = is_congestion_alarm_set(),
|
||||||
|
case is_congested(Socket, Transport) of
|
||||||
|
true when not IsCongestAlarmSet ->
|
||||||
|
{ok, Stat} = Transport:getstat(Socket, [recv_cnt, recv_oct, send_cnt, send_oct]),
|
||||||
|
{ok, Opts} = Transport:getopts(Socket, [high_watermark,high_msgq_watermark, sndbuf, recbuf, buffer]),
|
||||||
|
ok = set_congestion_alarm(),
|
||||||
|
emqx_alarm:activate(?ALARM_TCP_CONGEST(Channel), maps:from_list(Stat++Opts));
|
||||||
|
false when IsCongestAlarmSet ->
|
||||||
|
ok = clear_congestion_alarm(),
|
||||||
|
emqx_alarm:deactivate(?ALARM_TCP_CONGEST(Channel));
|
||||||
|
_ -> ok
|
||||||
|
end.
|
||||||
|
|
||||||
|
is_congested(Socket, Transport) ->
|
||||||
|
case Transport:getstat(Socket, [send_pend]) of
|
||||||
|
{ok, [{send_pend, N}]} when N > 0 -> true;
|
||||||
|
_ -> false
|
||||||
|
end.
|
||||||
|
|
||||||
|
is_congestion_alarm_set() ->
|
||||||
|
case erlang:get(conn_congested) of
|
||||||
|
true -> true;
|
||||||
|
_ -> false
|
||||||
|
end.
|
||||||
|
set_congestion_alarm() ->
|
||||||
|
erlang:put(conn_congested, true), ok.
|
||||||
|
clear_congestion_alarm() ->
|
||||||
|
erlang:put(conn_congested, false), ok.
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Handle Info
|
%% Handle Info
|
||||||
|
|
||||||
|
@ -622,7 +657,7 @@ handle_info(activate_socket, State = #state{sockstate = OldSst}) ->
|
||||||
end;
|
end;
|
||||||
|
|
||||||
handle_info({sock_error, Reason}, State) ->
|
handle_info({sock_error, Reason}, State) ->
|
||||||
?LOG(debug, "Socket error: ~p", [Reason]),
|
Reason =/= closed andalso ?LOG(error, "Socket error: ~p", [Reason]),
|
||||||
handle_info({sock_closed, Reason}, close_socket(State));
|
handle_info({sock_closed, Reason}, close_socket(State));
|
||||||
|
|
||||||
handle_info(Info, State) ->
|
handle_info(Info, State) ->
|
||||||
|
|
|
@ -52,6 +52,9 @@ init_per_suite(Config) ->
|
||||||
|
|
||||||
ok = meck:expect(emqx_channel, ensure_disconnected, fun(_, Channel) -> Channel end),
|
ok = meck:expect(emqx_channel, ensure_disconnected, fun(_, Channel) -> Channel end),
|
||||||
|
|
||||||
|
ok = meck:expect(emqx_alarm, activate, fun(_, _) -> ok end),
|
||||||
|
ok = meck:expect(emqx_alarm, deactivate, fun(_) -> ok end),
|
||||||
|
|
||||||
Config.
|
Config.
|
||||||
|
|
||||||
end_per_suite(_Config) ->
|
end_per_suite(_Config) ->
|
||||||
|
@ -62,6 +65,7 @@ end_per_suite(_Config) ->
|
||||||
ok = meck:unload(emqx_pd),
|
ok = meck:unload(emqx_pd),
|
||||||
ok = meck:unload(emqx_metrics),
|
ok = meck:unload(emqx_metrics),
|
||||||
ok = meck:unload(emqx_hooks),
|
ok = meck:unload(emqx_hooks),
|
||||||
|
ok = meck:unload(emqx_alarm),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
init_per_testcase(_TestCase, Config) ->
|
init_per_testcase(_TestCase, Config) ->
|
||||||
|
@ -77,6 +81,7 @@ init_per_testcase(_TestCase, Config) ->
|
||||||
{ok, [{K, 0} || K <- Options]}
|
{ok, [{K, 0} || K <- Options]}
|
||||||
end),
|
end),
|
||||||
ok = meck:expect(emqx_transport, async_send, fun(_Sock, _Data) -> ok end),
|
ok = meck:expect(emqx_transport, async_send, fun(_Sock, _Data) -> ok end),
|
||||||
|
ok = meck:expect(emqx_transport, async_send, fun(_Sock, _Data, _Opts) -> ok end),
|
||||||
ok = meck:expect(emqx_transport, fast_close, fun(_Sock) -> ok end),
|
ok = meck:expect(emqx_transport, fast_close, fun(_Sock) -> ok end),
|
||||||
Config.
|
Config.
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue