fix(gateway): stop rate limit tables dying with their creator (#2299)

This commit is contained in:
Hampus
2026-09-01 00:16:26 +02:00
committed by GitHub
parent 662f4ac93b
commit dcd5f88d65
3 changed files with 87 additions and 15 deletions
@@ -320,18 +320,17 @@ create_counter_table(Table) ->
-spec create_table(atom()) -> created | exists.
create_table(Table) ->
try
_ = ets:new(Table, [
named_table,
public,
set,
{write_concurrency, true},
{read_concurrency, true}
]),
_ = ets:new(Table, rate_table_options()),
created
catch
error:badarg -> exists
end.
-spec rate_table_options() -> list().
rate_table_options() ->
[named_table, public, set, {write_concurrency, true}, {read_concurrency, true}] ++
guild_ets_utils:heir_options().
-spec rate_limits_disabled() -> boolean().
rate_limits_disabled() ->
case os:getenv("FLUXER_DISABLE_RATE_LIMITS") of
@@ -502,6 +501,41 @@ await_table_size(Table, Max, Attempts) ->
await_table_size(Table, Max, Attempts - 1)
end.
shared_ip_bucket_survives_creating_process_death_test() ->
with_rate_limits_enabled(fun assert_shared_ip_bucket_survives_creator/0).
assert_shared_ip_bucket_survives_creator() ->
drop_table(?SHARED_IP_RATE_TABLE),
{ok, _} = guild_ets_owner:start_link(),
try
assert_shared_ip_bucket_outlives_creator()
after
gen_server:stop(guild_ets_owner)
end.
assert_shared_ip_bucket_outlives_creator() ->
IP = <<"198.51.100.70">>,
stop_table_owner(start_shared_ip_bucket_creator(IP)),
?assertNotEqual(undefined, ets:whereis(?SHARED_IP_RATE_TABLE)),
Key = {IP, erlang:system_time(millisecond) div ?SHARED_IP_RATE_WINDOW_MS},
?assertEqual([{Key, 1}], ets:lookup(?SHARED_IP_RATE_TABLE, Key)).
start_shared_ip_bucket_creator(IP) ->
Parent = self(),
Pid = spawn(fun() -> create_shared_ip_bucket(Parent, IP) end),
receive
{owner_ready, Pid} -> Pid
after 1000 -> error(owner_start_timeout)
end.
create_shared_ip_bucket(Parent, IP) ->
ok = check_shared_ip_rate(IP),
Parent ! {owner_ready, self()},
receive
stop -> ok
after 30000 -> ok
end.
window_cleanup_loops_do_not_outlive_their_table_test() ->
with_rate_limits_enabled(fun() ->
with_fast_cleanup_timer(fun assert_window_cleanup_loops_do_not_leak/0)
@@ -151,18 +151,17 @@ create_session_user_counts_table() ->
-spec create_rate_table(atom()) -> created | exists.
create_rate_table(Table) ->
try
_ = ets:new(Table, [
named_table,
public,
set,
{write_concurrency, true},
{read_concurrency, true}
]),
_ = ets:new(Table, rate_table_options()),
created
catch
error:badarg -> exists
end.
-spec rate_table_options() -> list().
rate_table_options() ->
[named_table, public, set, {write_concurrency, true}, {read_concurrency, true}] ++
guild_ets_utils:heir_options().
-spec ensure_identify_table() -> ok.
ensure_identify_table() ->
case ets:whereis(?IDENTIFY_TABLE) of
@@ -313,6 +312,38 @@ prune_old_identify_entries_keeps_recent_buckets_test() ->
?assertNotEqual([], ets:lookup(?IDENTIFY_TABLE, RecentKey)),
ets:delete(?IDENTIFY_TABLE, RecentKey).
identify_bucket_survives_creating_process_death_test() ->
drop_identify_table(),
{ok, _} = guild_ets_owner:start_link(),
try
assert_identify_bucket_outlives_creator()
after
gen_server:stop(guild_ets_owner)
end.
assert_identify_bucket_outlives_creator() ->
IP = <<"192.0.2.220">>,
stop_table_owner(start_identify_bucket_creator(IP)),
?assertNotEqual(undefined, ets:whereis(?IDENTIFY_TABLE)),
Key = {IP, erlang:system_time(second) div ?IDENTIFY_WINDOW_SECS},
?assertEqual([{Key, 1}], ets:lookup(?IDENTIFY_TABLE, Key)).
start_identify_bucket_creator(IP) ->
Parent = self(),
Pid = spawn(fun() -> create_identify_bucket(Parent, IP) end),
receive
{owner_ready, Pid} -> Pid
after 1000 -> error(owner_start_timeout)
end.
create_identify_bucket(Parent, IP) ->
ok = check_identify_rate(IP),
Parent ! {owner_ready, self()},
receive
stop -> ok
after 30000 -> ok
end.
identify_cleanup_loops_do_not_outlive_their_table_test() ->
meck:new(gateway_retry_timer, [passthrough]),
meck:expect(gateway_retry_timer, wait, fun(_) -> timer:sleep(5) end),
+8 -1
View File
@@ -3,7 +3,7 @@
-module(guild_ets_utils).
-typing([eqwalizer]).
-export([ensure_table/2]).
-export([ensure_table/2, heir_options/0]).
-spec ensure_table(atom(), list()) -> ok.
ensure_table(TableName, Options) ->
@@ -14,6 +14,13 @@ ensure_table(TableName, Options) ->
ensure_table_local(TableName, Options)
end.
-spec heir_options() -> list().
heir_options() ->
case whereis(guild_ets_owner) of
Pid when is_pid(Pid), Pid =/= self() -> [{heir, Pid, inherited}];
_ -> []
end.
-spec ensure_table_local(atom(), list()) -> ok.
ensure_table_local(TableName, Options) ->
case ets:whereis(TableName) of