From 933b13f3fafc05db895555fbbf33bab2a12ec74a Mon Sep 17 00:00:00 2001 From: Hampus Date: Sun, 30 Aug 2026 23:23:26 +0200 Subject: [PATCH] perf(gateway): re-enable generational GC on hot processes (#2188) --- fluxer_gateway/src/guild/guild.erl | 4 +- .../src/guild/guild_broadcaster.erl | 2 +- fluxer_gateway/src/session/session.erl | 4 +- .../test/gateway_hot_process_gc_tests.erl | 96 +++++++++++++++++++ 4 files changed, 101 insertions(+), 5 deletions(-) create mode 100644 fluxer_gateway/test/gateway_hot_process_gc_tests.erl diff --git a/fluxer_gateway/src/guild/guild.erl b/fluxer_gateway/src/guild/guild.erl index 39967a921..fc0ab8c0d 100644 --- a/fluxer_gateway/src/guild/guild.erl +++ b/fluxer_gateway/src/guild/guild.erl @@ -30,7 +30,7 @@ update_counts(State) -> guild_maintenance:update_counts(State). -spec init(map()) -> {ok, guild_state(), timeout()}. init(GuildState) -> process_flag(trap_exit, true), - erlang:process_flag(fullsweep_after, 0), + erlang:process_flag(fullsweep_after, 10), State0 = guild_init:init_base_state(GuildState), State1 = guild_init:init_member_list(State0), State2 = guild_init:init_counts(State1), @@ -431,7 +431,7 @@ terminate(Reason, State) -> -spec code_change(term(), guild_state(), term()) -> {ok, guild_state()}. code_change(_OldVsn, State, _Extra) -> - erlang:process_flag(fullsweep_after, 0), + erlang:process_flag(fullsweep_after, 10), erlang:garbage_collect(), {ok, State}. diff --git a/fluxer_gateway/src/guild/guild_broadcaster.erl b/fluxer_gateway/src/guild/guild_broadcaster.erl index 129fb5c34..50f5ddeb7 100644 --- a/fluxer_gateway/src/guild/guild_broadcaster.erl +++ b/fluxer_gateway/src/guild/guild_broadcaster.erl @@ -71,7 +71,7 @@ maybe_cast(_BroadcasterPid, _Msg) -> -spec init([integer() | pid()]) -> {ok, map()}. init([GuildId, GuildPid]) when is_integer(GuildId), is_pid(GuildPid) -> - erlang:process_flag(fullsweep_after, 0), + erlang:process_flag(fullsweep_after, 10), _ = erlang:monitor(process, GuildPid), {ok, #{guild_id => GuildId, guild_pid => GuildPid}}. diff --git a/fluxer_gateway/src/session/session.erl b/fluxer_gateway/src/session/session.erl index 0b813608b..a70acc367 100644 --- a/fluxer_gateway/src/session/session.erl +++ b/fluxer_gateway/src/session/session.erl @@ -80,7 +80,7 @@ start_link(SessionData) -> -spec init(map()) -> {ok, session_state()}. init(SessionData) -> process_flag(trap_exit, true), - erlang:process_flag(fullsweep_after, 0), + erlang:process_flag(fullsweep_after, 10), State0 = session_init:build_state(SessionData), ScheduleStartedAt = gateway_timings:start(), session_init:schedule_timers(State0), @@ -318,7 +318,7 @@ terminate(Reason, State) -> -spec code_change(term(), session_state(), term()) -> {ok, session_state()}. code_change(_OldVsn, State, _Extra) -> - erlang:process_flag(fullsweep_after, 0), + erlang:process_flag(fullsweep_after, 10), erlang:garbage_collect(), {ok, State}. diff --git a/fluxer_gateway/test/gateway_hot_process_gc_tests.erl b/fluxer_gateway/test/gateway_hot_process_gc_tests.erl new file mode 100644 index 000000000..a659341c5 --- /dev/null +++ b/fluxer_gateway/test/gateway_hot_process_gc_tests.erl @@ -0,0 +1,96 @@ +%% SPDX-License-Identifier: AGPL-3.0-or-later + +-module(gateway_hot_process_gc_tests). +-typing([eqwalizer]). + +-include_lib("eunit/include/eunit.hrl"). + +-define(GENERATIONAL_FULLSWEEP_AFTER, 10). + +session_init_keeps_generational_gc_test() -> + ?assertEqual( + ?GENERATIONAL_FULLSWEEP_AFTER, + fullsweep_after_in(fun() -> session:init(session_data()) end) + ). + +session_code_change_keeps_generational_gc_test() -> + ?assertEqual( + ?GENERATIONAL_FULLSWEEP_AFTER, + fullsweep_after_in(fun() -> session:code_change(0, #{}, []) end) + ). + +guild_init_keeps_generational_gc_test() -> + ?assertEqual( + ?GENERATIONAL_FULLSWEEP_AFTER, + fullsweep_after_in(fun() -> guild:init(guild_data()) end) + ). + +guild_code_change_keeps_generational_gc_test() -> + ?assertEqual( + ?GENERATIONAL_FULLSWEEP_AFTER, + fullsweep_after_in(fun() -> guild:code_change(0, #{}, []) end) + ). + +guild_broadcaster_init_keeps_generational_gc_test() -> + Parent = self(), + ?assertEqual( + ?GENERATIONAL_FULLSWEEP_AFTER, + fullsweep_after_in(fun() -> guild_broadcaster:init([900301, Parent]) end) + ). + +session_data() -> + #{ + id => <<"gc-session">>, + user_id => 900401, + user_data => #{<<"username">> => <<"gc">>}, + version => 9, + token_hash => <<"token_hash">>, + auth_session_id_hash => <<"auth_hash">>, + properties => #{}, + status => online, + ready => undefined, + socket_pid => self(), + guilds => [] + }. + +guild_data() -> + #{ + id => 900300, + member_count => 0, + sessions => #{}, + data => #{ + <<"guild">> => #{ + <<"id">> => 900300, + <<"owner_id">> => 900401, + <<"features">> => [], + <<"member_count">> => 0 + }, + <<"roles">> => [], + <<"channels">> => [], + <<"members">> => [] + } + }. + +fullsweep_after_in(Fun) -> + Ref = make_ref(), + Parent = self(), + {Pid, MonRef} = spawn_monitor(fun() -> + erlang:process_flag(fullsweep_after, 0), + _ = Fun(), + Parent ! {Ref, fullsweep_after(self())} + end), + Result = + receive + {Ref, Value} -> Value; + {'DOWN', MonRef, process, Pid, Reason} -> {crashed, Reason} + after 10000 -> timeout + end, + erlang:demonitor(MonRef, [flush]), + exit(Pid, kill), + Result. + +fullsweep_after(Pid) -> + case erlang:process_info(Pid, garbage_collection) of + {garbage_collection, Info} -> proplists:get_value(fullsweep_after, Info); + _ -> undefined + end.