Merge pull request #10019 from qzhuyan/dev/william/quic-hidden-low-level-tunings

230222 feat(quic): add hidden low level settings for listeners.
This commit is contained in:
Zaiming (Stone) Shi 2023-02-24 20:03:31 +01:00 committed by GitHub
commit df7e9db057
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 676 additions and 6 deletions

View File

@ -1901,6 +1901,347 @@ base_listener_acceptors {
}
}
fields_mqtt_quic_listener_max_bytes_per_key {
desc {
en: "Maximum number of bytes to encrypt with a single 1-RTT encryption key before initiating key update. Default: 274877906944"
zh: "在启动密钥更新之前,用单个 1-RTT 加密密钥加密的最大字节数。默认值274877906944"
}
label {
en: "Max bytes per key"
zh: "每个密钥的最大字节数"
}
}
fields_mqtt_quic_listener_handshake_idle_timeout_ms {
desc {
en: "How long a handshake can idle before it is discarded. Default: 10 000"
zh: "一个握手在被丢弃之前可以空闲多长时间。 默认值10 000"
}
label {
en: "Handshake idle timeout ms"
zh: "握手空闲超时毫秒"
}
}
fields_mqtt_quic_listener_tls_server_max_send_buffer {
desc {
en: "How much Server TLS data to buffer. Default: 8192"
zh: "缓冲多少TLS数据。 默认值8192"
}
label {
en: "TLS server max send buffer"
zh: "TLS 服务器最大发送缓冲区"
}
}
fields_mqtt_quic_listener_stream_recv_window_default {
desc {
en: "Initial stream receive window size. Default: 32678"
zh: "初始流接收窗口大小。 默认值32678"
}
label {
en: "Stream recv window default"
zh: "流接收窗口默认"
}
}
fields_mqtt_quic_listener_stream_recv_buffer_default {
desc {
en: "Stream initial buffer size. Default: 4096"
zh: "流的初始缓冲区大小。默认4096"
}
label {
en: "Stream recv buffer default"
zh: "流媒体接收缓冲区默认值"
}
}
fields_mqtt_quic_listener_conn_flow_control_window {
desc {
en: "Connection-wide flow control window. Default: 16777216"
zh: "连接的流控窗口。默认16777216"
}
label {
en: "Conn flow control window"
zh: "流控窗口"
}
}
fields_mqtt_quic_listener_max_stateless_operations {
desc {
en: "The maximum number of stateless operations that may be queued on a worker at any one time. Default: 16"
zh: "无状态操作的最大数量在任何时候都可以在一个工作者上排队。默认值16"
}
label {
en: "Max stateless operations"
zh: "最大无状态操作数"
}
}
fields_mqtt_quic_listener_initial_window_packets {
desc {
en: "The size (in packets) of the initial congestion window for a connection. Default: 10"
zh: "一个连接的初始拥堵窗口的大小以包为单位。默认值10"
}
label {
en: "Initial window packets"
zh: "初始窗口数据包"
}
}
fields_mqtt_quic_listener_send_idle_timeout_ms {
desc {
en: "Reset congestion control after being idle for amount of time. Default: 1000"
zh: "在闲置一定时间后重置拥堵控制。默认值1000"
}
label {
en: "Send idle timeout ms"
zh: "发送空闲超时毫秒"
}
}
fields_mqtt_quic_listener_initial_rtt_ms {
desc {
en: "Initial RTT estimate."
zh: "初始RTT估计"
}
label {
en: "Initial RTT ms"
zh: "Initial RTT 毫秒"
}
}
fields_mqtt_quic_listener_max_ack_delay_ms {
desc {
en: "How long to wait after receiving data before sending an ACK. Default: 25"
zh: "在收到数据后要等待多长时间才能发送一个ACK。默认值25"
}
label {
en: "Max ack delay ms"
zh: "最大应答延迟 毫秒"
}
}
fields_mqtt_quic_listener_disconnect_timeout_ms {
desc {
en: "How long to wait for an ACK before declaring a path dead and disconnecting. Default: 16000"
zh: "在判定路径无效和断开连接之前要等待多长时间的ACK。默认16000"
}
label {
en: "Disconnect timeout ms"
zh: "断开连接超时 毫秒"
}
}
fields_mqtt_quic_listener_idle_timeout_ms {
desc {
en: "How long a connection can go idle before it is gracefully shut down. 0 to disable timeout"
zh: "一个连接在被优雅地关闭之前可以空闲多长时间。0 表示禁用超时"
}
label {
en: "Idle timeout ms"
zh: "空闲超时 毫秒"
}
}
fields_mqtt_quic_listener_handshake_idle_timeout_ms {
desc {
en: "How long a handshake can idle before it is discarded"
zh: "一个握手在被丢弃之前可以空闲多长时间"
}
label {
en: "Handshake idle timeout ms"
zh: "握手空闲超时 毫秒"
}
}
fields_mqtt_quic_listener_keep_alive_interval_ms {
desc {
en: "How often to send PING frames to keep a connection alive."
zh: "多长时间发送一次PING帧以保活连接。"
}
label {
en: "Keep alive interval ms"
zh: "保持活着的时间间隔 毫秒"
}
}
fields_mqtt_quic_listener_peer_bidi_stream_count {
desc {
en: "Number of bidirectional streams to allow the peer to open."
zh: "允许对端打开的双向流的数量"
}
label {
en: "Peer bidi stream count"
zh: "对端双向流的数量"
}
}
fields_mqtt_quic_listener_peer_unidi_stream_count {
desc {
en: "Number of unidirectional streams to allow the peer to open."
zh: "允许对端打开的单向流的数量"
}
label {
en: "Peer unidi stream count"
zh: "对端单向流的数量"
}
}
fields_mqtt_quic_listener_retry_memory_limit {
desc {
en: "The percentage of available memory usable for handshake connections before stateless retry is used. Calculated as `N/65535`. Default: 65"
zh: "在使用无状态重试之前,可用于握手连接的可用内存的百分比。计算为`N/65535`。默认值65"
}
label {
en: "Retry memory limit"
zh: "重试内存限制"
}
}
fields_mqtt_quic_listener_load_balancing_mode {
desc {
en: "0: Disabled, 1: SERVER_ID_IP, 2: SERVER_ID_FIXED. default: 0"
zh: "0: 禁用, 1: SERVER_ID_IP, 2: SERVER_ID_FIXED. 默认: 0"
}
label {
en: "Load balancing mode"
zh: "负载平衡模式"
}
}
fields_mqtt_quic_listener_max_operations_per_drain {
desc {
en: "The maximum number of operations to drain per connection quantum. Default: 16"
zh: "每个连接操作的最大耗费操作数。默认16"
}
label {
en: "Max operations per drain"
zh: "每次操作最大操作数"
}
}
fields_mqtt_quic_listener_send_buffering_enabled {
desc {
en: "Buffer send data instead of holding application buffers until sent data is acknowledged. Default: 1 (Enabled)"
zh: "缓冲发送数据而不是保留应用缓冲区直到发送数据被确认。默认值1启用"
}
label {
en: "Send buffering enabled"
zh: "启用发送缓冲功能"
}
}
fields_mqtt_quic_listener_pacing_enabled {
desc {
en: "Pace sending to avoid overfilling buffers on the path. Default: 1 (Enabled)"
zh: "有节奏的发送以避免路径上的缓冲区过度填充。默认值1已启用"
}
label {
en: "Pacing enabled"
zh: "启用节奏发送"
}
}
fields_mqtt_quic_listener_migration_enabled {
desc {
en: "Enable clients to migrate IP addresses and tuples. Requires a cooperative load-balancer, or no load-balancer. Default: 1 (Enabled)"
zh: "开启客户端地址迁移功能。需要一个支持的负载平衡器或者没有负载平衡器。默认值1已启用"
}
label {
en: "Migration enabled"
zh: "启用地址迁移"
}
}
fields_mqtt_quic_listener_datagram_receive_enabled {
desc {
en: "Advertise support for QUIC datagram extension. Reserve for the future. Default 0 (FALSE)"
zh: "宣传对QUIC Datagram 扩展的支持。为将来保留。默认为0FALSE"
}
label {
en: "Datagram receive enabled"
zh: "启用 Datagram 接收"
}
}
fields_mqtt_quic_listener_server_resumption_level {
desc {
en: "Controls resumption tickets and/or 0-RTT server support. Default: 0 (No resumption)"
zh: "连接恢复 和/或 0-RTT 服务器支持。默认值0无恢复功能"
}
label {
en: "Server resumption level"
zh: "服务端连接恢复支持"
}
}
fields_mqtt_quic_listener_minimum_mtu {
desc {
en: "The minimum MTU supported by a connection. This will be used as the starting MTU. Default: 1248"
zh: "一个连接所支持的最小MTU。这将被作为起始MTU使用。默认值1248"
}
label {
en: "Minimum MTU"
zh: "最小 MTU"
}
}
fields_mqtt_quic_listener_maximum_mtu {
desc {
en: "The maximum MTU supported by a connection. This will be the maximum probed value. Default: 1500"
zh: "一个连接所支持的最大MTU。这将是最大的探测值。默认值1500"
}
label {
en: "Maximum MTU"
zh: "最大 MTU"
}
}
fields_mqtt_quic_listener_mtu_discovery_search_complete_timeout_us {
desc {
en: "The time in microseconds to wait before reattempting MTU probing if max was not reached. Default: 600000000"
zh: "如果没有达到 max ,在重新尝试 MTU 探测之前要等待的时间单位是微秒。默认值600000000"
}
label {
en: "MTU discovery search complete timeout us"
zh: ""
}
}
fields_mqtt_quic_listener_mtu_discovery_missing_probe_count {
desc {
en: "The maximum number of stateless operations that may be queued on a binding at any one time. Default: 3"
zh: "在任何时候都可以在一个绑定上排队的无状态操作的最大数量。默认值3"
}
label {
en: "MTU discovery missing probe count"
zh: "MTU发现丢失的探针数量"
}
}
fields_mqtt_quic_listener_max_binding_stateless_operations {
desc {
en: "The maximum number of stateless operations that may be queued on a binding at any one time. Default: 100"
zh: "在任何时候可以在一个绑定上排队的无状态操作的最大数量。默认值100"
}
label {
en: "Max binding stateless operations"
zh: "最大绑定无状态操作"
}
}
fields_mqtt_quic_listener_stateless_operation_expiration_ms {
desc {
en: "The time limit between operations for the same endpoint, in milliseconds. Default: 100"
zh: "同一个对端的操作之间的时间限制,单位是毫秒。 默认100"
}
label {
en: "Stateless operation expiration ms"
zh: "无状态操作过期 毫秒"
}
}
base_listener_max_connections {
desc {
en: """The maximum number of concurrent connections allowed by the listener."""

View File

@ -383,17 +383,18 @@ do_start_listener(quic, ListenerName, #{bind := Bind} = Opts) ->
{keep_alive_interval_ms, maps:get(keep_alive_interval, Opts, 0)},
{idle_timeout_ms, maps:get(idle_timeout, Opts, 0)},
{handshake_idle_timeout_ms, maps:get(handshake_idle_timeout, Opts, 10000)},
{server_resumption_level, 2},
{server_resumption_level, maps:get(server_resumption_level, Opts, 2)},
{verify, maps:get(verify, SSLOpts, verify_none)}
] ++
case maps:get(cacertfile, SSLOpts, undefined) of
undefined -> [];
CaCertFile -> [{cacertfile, binary_to_list(CaCertFile)}]
end,
end ++
optional_quic_listener_opts(Opts),
ConnectionOpts = #{
conn_callback => emqx_quic_connection,
peer_unidi_stream_count => 1,
peer_bidi_stream_count => 10,
peer_unidi_stream_count => maps:get(peer_unidi_stream_count, Opts, 1),
peer_bidi_stream_count => maps:get(peer_bidi_stream_count, Opts, 10),
zone => zone(Opts),
listener => {quic, ListenerName},
limiter => limiter(Opts)
@ -726,3 +727,61 @@ get_ssl_options(Conf) ->
error ->
maps:get(<<"ssl_options">>, Conf, undefined)
end.
%% @doc Get QUIC optional settings for low level tunings.
%% @see quicer:quic_settings()
-spec optional_quic_listener_opts(map()) -> proplists:proplist().
optional_quic_listener_opts(Conf) when is_map(Conf) ->
maps:to_list(
maps:filter(
fun(Name, _V) ->
lists:member(
Name,
quic_listener_optional_settings()
)
end,
Conf
)
).
-spec quic_listener_optional_settings() -> [atom()].
quic_listener_optional_settings() ->
[
max_bytes_per_key,
%% In conf schema we use handshake_idle_timeout
handshake_idle_timeout_ms,
%% In conf schema we use idle_timeout
idle_timeout_ms,
%% not use since we are server
%% tls_client_max_send_buffer,
tls_server_max_send_buffer,
stream_recv_window_default,
stream_recv_buffer_default,
conn_flow_control_window,
max_stateless_operations,
initial_window_packets,
send_idle_timeout_ms,
initial_rtt_ms,
max_ack_delay_ms,
disconnect_timeout_ms,
%% In conf schema, we use keep_alive_interval
keep_alive_interval_ms,
%% over written by conn opts
peer_bidi_stream_count,
%% over written by conn opts
peer_unidi_stream_count,
retry_memory_limit,
load_balancing_mode,
max_operations_per_drain,
send_buffering_enabled,
pacing_enabled,
migration_enabled,
datagram_receive_enabled,
server_resumption_level,
minimum_mtu,
maximum_mtu,
mtu_discovery_search_complete_timeout_us,
mtu_discovery_missing_probe_count,
max_binding_stateless_operations,
stateless_operation_expiration_ms
].

View File

@ -120,6 +120,9 @@
-elvis([{elvis_style, god_modules, disable}]).
-define(BIT(Bits), (1 bsl (Bits))).
-define(MAX_UINT(Bits), (?BIT(Bits) - 1)).
namespace() -> broker.
tags() ->
@ -862,6 +865,79 @@ fields("mqtt_quic_listener") ->
}
)},
{"ciphers", ciphers_schema(quic)},
{"max_bytes_per_key",
quic_lowlevel_settings_uint(
1,
?MAX_UINT(64),
?DESC(fields_mqtt_quic_listener_max_bytes_per_key)
)},
{"handshake_idle_timeout_ms",
quic_lowlevel_settings_uint(
1,
?MAX_UINT(64),
?DESC(fields_mqtt_quic_listener_handshake_idle_timeout)
)},
{"tls_server_max_send_buffer",
quic_lowlevel_settings_uint(
1,
?MAX_UINT(32),
?DESC(fields_mqtt_quic_listener_tls_server_max_send_buffer)
)},
{"stream_recv_window_default",
quic_lowlevel_settings_uint(
1,
?MAX_UINT(32),
?DESC(fields_mqtt_quic_listener_stream_recv_window_default)
)},
{"stream_recv_buffer_default",
quic_lowlevel_settings_uint(
1,
?MAX_UINT(32),
?DESC(fields_mqtt_quic_listener_stream_recv_buffer_default)
)},
{"conn_flow_control_window",
quic_lowlevel_settings_uint(
1,
?MAX_UINT(32),
?DESC(fields_mqtt_quic_listener_conn_flow_control_window)
)},
{"max_stateless_operations",
quic_lowlevel_settings_uint(
1,
?MAX_UINT(32),
?DESC(fields_mqtt_quic_listener_max_stateless_operations)
)},
{"initial_window_packets",
quic_lowlevel_settings_uint(
1,
?MAX_UINT(32),
?DESC(fields_mqtt_quic_listener_initial_window_packets)
)},
{"send_idle_timeout_ms",
quic_lowlevel_settings_uint(
1,
?MAX_UINT(32),
?DESC(fields_mqtt_quic_listener_send_idle_timeout_ms)
)},
{"initial_rtt_ms",
quic_lowlevel_settings_uint(
1,
?MAX_UINT(32),
?DESC(fields_mqtt_quic_listener_initial_rtt_ms)
)},
{"max_ack_delay_ms",
quic_lowlevel_settings_uint(
1,
?MAX_UINT(32),
?DESC(fields_mqtt_quic_listener_max_ack_delay_ms)
)},
{"disconnect_timeout_ms",
quic_lowlevel_settings_uint(
1,
?MAX_UINT(32),
?DESC(fields_mqtt_quic_listener_disconnect_timeout_ms)
)},
{"idle_timeout",
sc(
duration_ms(),
@ -870,6 +946,12 @@ fields("mqtt_quic_listener") ->
desc => ?DESC(fields_mqtt_quic_listener_idle_timeout)
}
)},
{"idle_timeout_ms",
quic_lowlevel_settings_uint(
0,
?MAX_UINT(64),
?DESC(fields_mqtt_quic_listener_idle_timeout_ms)
)},
{"handshake_idle_timeout",
sc(
duration_ms(),
@ -878,6 +960,12 @@ fields("mqtt_quic_listener") ->
desc => ?DESC(fields_mqtt_quic_listener_handshake_idle_timeout)
}
)},
{"handshake_idle_timeout_ms",
quic_lowlevel_settings_uint(
1,
?MAX_UINT(64),
?DESC(fields_mqtt_quic_listener_handshake_idle_timeout_ms)
)},
{"keep_alive_interval",
sc(
duration_ms(),
@ -886,6 +974,100 @@ fields("mqtt_quic_listener") ->
desc => ?DESC(fields_mqtt_quic_listener_keep_alive_interval)
}
)},
{"keep_alive_interval_ms",
quic_lowlevel_settings_uint(
0,
?MAX_UINT(32),
?DESC(fields_mqtt_quic_listener_keep_alive_interval_ms)
)},
{"peer_bidi_stream_count",
quic_lowlevel_settings_uint(
1,
?MAX_UINT(16),
?DESC(fields_mqtt_quic_listener_peer_bidi_stream_count)
)},
{"peer_unidi_stream_count",
quic_lowlevel_settings_uint(
0,
?MAX_UINT(16),
?DESC(fields_mqtt_quic_listener_peer_unidi_stream_count)
)},
{"retry_memory_limit",
quic_lowlevel_settings_uint(
0,
?MAX_UINT(16),
?DESC(fields_mqtt_quic_listener_retry_memory_limit)
)},
{"load_balancing_mode",
quic_lowlevel_settings_uint(
0,
?MAX_UINT(16),
?DESC(fields_mqtt_quic_listener_load_balancing_mode)
)},
{"max_operations_per_drain",
quic_lowlevel_settings_uint(
0,
?MAX_UINT(8),
?DESC(fields_mqtt_quic_listener_max_operations_per_drain)
)},
{"send_buffering_enabled",
quic_feature_toggle(
?DESC(fields_mqtt_quic_listener_send_buffering_enabled)
)},
{"pacing_enabled",
quic_feature_toggle(
?DESC(fields_mqtt_quic_listener_pacing_enabled)
)},
{"migration_enabled",
quic_feature_toggle(
?DESC(fields_mqtt_quic_listener_migration_enabled)
)},
{"datagram_receive_enabled",
quic_feature_toggle(
?DESC(fields_mqtt_quic_listener_datagram_receive_enabled)
)},
{"server_resumption_level",
quic_lowlevel_settings_uint(
0,
?MAX_UINT(8),
?DESC(fields_mqtt_quic_listener_server_resumption_level)
)},
{"minimum_mtu",
quic_lowlevel_settings_uint(
1,
?MAX_UINT(16),
?DESC(fields_mqtt_quic_listener_minimum_mtu)
)},
{"maximum_mtu",
quic_lowlevel_settings_uint(
1,
?MAX_UINT(16),
?DESC(fields_mqtt_quic_listener_maximum_mtu)
)},
{"mtu_discovery_search_complete_timeout_us",
quic_lowlevel_settings_uint(
0,
?MAX_UINT(64),
?DESC(fields_mqtt_quic_listener_mtu_discovery_search_complete_timeout_us)
)},
{"mtu_discovery_missing_probe_count",
quic_lowlevel_settings_uint(
1,
?MAX_UINT(8),
?DESC(fields_mqtt_quic_listener_mtu_discovery_missing_probe_count)
)},
{"max_binding_stateless_operations",
quic_lowlevel_settings_uint(
0,
?MAX_UINT(16),
?DESC(fields_mqtt_quic_listener_max_binding_stateless_operations)
)},
{"stateless_operation_expiration_ms",
quic_lowlevel_settings_uint(
0,
?MAX_UINT(16),
?DESC(fields_mqtt_quic_listener_stateless_operation_expiration_ms)
)},
{"ssl_options",
sc(
ref("listener_quic_ssl_opts"),
@ -2638,3 +2820,30 @@ parse_port(Port) ->
_:_ ->
throw("bad_port_number")
end.
quic_feature_toggle(Desc) ->
sc(
%% true, false are for user facing
%% 0, 1 are for internal represtation
typerefl:alias("boolean", typerefl:union([true, false, 0, 1])),
#{
desc => Desc,
hidden => true,
required => false,
converter => fun
(true) -> 1;
(false) -> 0;
(Other) -> Other
end
}
).
quic_lowlevel_settings_uint(Low, High, Desc) ->
sc(
range(Low, High),
#{
required => false,
hidden => true,
desc => Desc
}
).

View File

@ -44,6 +44,7 @@
client_ssl_twoway/1,
ensure_mnesia_stopped/0,
ensure_quic_listener/2,
ensure_quic_listener/3,
is_all_tcp_servers_available/1,
is_tcp_server_available/2,
is_tcp_server_available/3,
@ -511,6 +512,9 @@ ensure_dashboard_listeners_started(_App) ->
-spec ensure_quic_listener(Name :: atom(), UdpPort :: inet:port_number()) -> ok.
ensure_quic_listener(Name, UdpPort) ->
ensure_quic_listener(Name, UdpPort, #{}).
-spec ensure_quic_listener(Name :: atom(), UdpPort :: inet:port_number(), map()) -> ok.
ensure_quic_listener(Name, UdpPort, ExtraSettings) ->
application:ensure_all_started(quicer),
Conf = #{
acceptors => 16,
@ -533,7 +537,7 @@ ensure_quic_listener(Name, UdpPort) ->
mountpoint => <<>>,
zone => default
},
emqx_config:put([listeners, quic, Name], Conf),
emqx_config:put([listeners, quic, Name], maps:merge(Conf, ExtraSettings)),
case emqx_listeners:start_listener(quic, Name, Conf) of
ok -> ok;
{error, {already_started, _Pid}} -> ok

View File

@ -32,7 +32,8 @@ all() ->
[
{group, mstream},
{group, shutdown},
{group, misc}
{group, misc},
t_listener_with_lowlevel_settings
].
groups() ->
@ -1892,6 +1893,60 @@ t_multi_streams_sub_0_rtt_stream_data_cont(Config) ->
ok = emqtt:disconnect(C),
ok = emqtt:disconnect(C0).
t_listener_with_lowlevel_settings(_Config) ->
LPort = 24567,
LowLevelTunings = #{
max_bytes_per_key => 274877906,
%% In conf schema we use handshake_idle_timeout
handshake_idle_timeout_ms => 2000,
%% In conf schema we use idle_timeout
idle_timeout_ms => 20000,
%% not use since we are server
%% tls_client_max_send_buffer,
tls_server_max_send_buffer => 10240,
stream_recv_window_default => 1024,
stream_recv_buffer_default => 1024,
conn_flow_control_window => 1024,
max_stateless_operations => 16,
initial_window_packets => 1300,
send_idle_timeout_ms => 12000,
initial_rtt_ms => 300,
max_ack_delay_ms => 6000,
disconnect_timeout_ms => 60000,
%% In conf schema, we use keep_alive_interval
keep_alive_interval_ms => 12000,
%% over written by conn opts
peer_bidi_stream_count => 100,
%% over written by conn opts
peer_unidi_stream_count => 100,
retry_memory_limit => 640,
load_balancing_mode => 1,
max_operations_per_drain => 32,
send_buffering_enabled => 1,
pacing_enabled => 0,
migration_enabled => 0,
datagram_receive_enabled => 1,
server_resumption_level => 0,
minimum_mtu => 1250,
maximum_mtu => 1600,
mtu_discovery_search_complete_timeout_us => 500000000,
mtu_discovery_missing_probe_count => 6,
max_binding_stateless_operations => 200,
stateless_operation_expiration_ms => 200
},
?assertEqual(
ok, emqx_common_test_helpers:ensure_quic_listener(?FUNCTION_NAME, LPort, LowLevelTunings)
),
timer:sleep(1000),
{ok, C} = emqtt:start_link([{proto_ver, v5}, {port, LPort}]),
{ok, _} = emqtt:quic_connect(C),
{ok, _, _} = emqtt:subscribe(C, <<"test/1/2">>, qos2),
{ok, _, [_SubQos]} = emqtt:subscribe_via(C, {new_data_stream, []}, #{}, [
{<<"test/1/3">>, [{qos, 2}]}
]),
ok = emqtt:disconnect(C),
emqx_listeners:stop_listener(emqx_listeners:listener_id(quic, ?FUNCTION_NAME)).
%%--------------------------------------------------------------------
%% Helper functions
%%--------------------------------------------------------------------

View File

@ -0,0 +1 @@
Add low level tuning settings for QUIC listeners.

View File

@ -0,0 +1 @@
为 QUIC 侦听器添加更多底层调优选项。