refactor(gateway): share shard helpers and test fixtures (#3381)

This commit is contained in:
Hampus authored and GitHub committed 2026-10-10 23:04:30 +02:00
1 parent f22261f218
commit e7b13fd19f
25 files changed
+101 -2021

No files matched your search

@@ -1,98 +0,0 @@
%% SPDX-License-Identifier: AGPL-3.0-or-later
-module(gateway_dispatch_relay_ordering_tests).
-typing([eqwalizer]).
-include_lib("eunit/include/eunit.hrl").
-define(STATE_KEY, {gateway_dispatch_relay, state}).
-define(EVENT_COUNT, 8).
dispatch_preserves_order_when_worker_is_backpressured_test_() ->
{timeout, 30, fun dispatch_preserves_order_when_worker_is_backpressured/0}.
dispatch_many_preserves_order_when_worker_is_backpressured_test_() ->
{timeout, 30, fun dispatch_many_preserves_order_when_worker_is_backpressured/0}.
dispatch_preserves_order_when_worker_is_backpressured() ->
assert_ordered_delivery(fun(SessionPid, N) ->
gateway_dispatch_relay:dispatch(SessionPid, relay_order_event, #{<<"n">> => N})
end).
dispatch_many_preserves_order_when_worker_is_backpressured() ->
assert_ordered_delivery(fun(SessionPid, N) ->
gateway_dispatch_relay:dispatch_many([SessionPid], relay_order_event, #{<<"n">> => N})
end).
assert_ordered_delivery(SendFun) ->
Worker = gateway_dispatch_relay_batch:start_worker(0),
try
with_relay_workers([Worker], fun() -> ordered_delivery(Worker, SendFun) end)
after
stop_worker(Worker)
end.
ordered_delivery(Worker, SendFun) ->
Ref = make_ref(),
Parent = self(),
SessionPid = spawn_link(fun() -> session_loop(Parent, Ref) end),
MaxQueue = gateway_dispatch_relay_batch:max_queue(),
ok = sys:suspend(Worker),
fill_queue(Worker, MaxQueue - ?EVENT_COUNT),
lists:foreach(fun(N) -> ok = SendFun(SessionPid, N) end, lists:seq(1, ?EVENT_COUNT)),
?assert(gateway_dispatch_relay_batch:message_queue_len(Worker) >= MaxQueue),
ok = sys:resume(Worker),
Observed = collect_observed(Ref, ?EVENT_COUNT, []),
SessionPid ! stop,
?assertEqual(lists:seq(1, ?EVENT_COUNT), Observed).
fill_queue(_Worker, 0) ->
ok;
fill_queue(Worker, Remaining) ->
Worker ! relay_order_filler,
fill_queue(Worker, Remaining - 1).
session_loop(Parent, Ref) ->
receive
{'$gen_cast', {dispatch, relay_order_event, #{<<"n">> := N}}} ->
Parent ! {relay_order_received, Ref, N},
session_loop(Parent, Ref);
stop ->
ok
after 30000 ->
ok
end.
collect_observed(_Ref, 0, Acc) ->
lists:reverse(Acc);
collect_observed(Ref, Remaining, Acc) ->
receive
{relay_order_received, Ref, N} -> collect_observed(Ref, Remaining - 1, [N | Acc])
after 10000 ->
?assert(false, {relay_order_timeout, Remaining, lists:reverse(Acc)})
end.
with_relay_workers(Workers, Fun) ->
Previous = persistent_term:get(?STATE_KEY, undefined),
persistent_term:put(?STATE_KEY, #{
workers => list_to_tuple(Workers),
shard_count => length(Workers)
}),
try
Fun()
after
restore_relay_state(Previous)
end.
restore_relay_state(undefined) ->
_ = persistent_term:erase(?STATE_KEY),
ok;
restore_relay_state(Previous) ->
persistent_term:put(?STATE_KEY, Previous).
stop_worker(Pid) ->
try gen_server:stop(Pid, normal, 5000) of
_ -> ok
catch
exit:_ -> ok
end.
@@ -1,131 +0,0 @@
%% SPDX-License-Identifier: AGPL-3.0-or-later
-module(session_manager_transfer_stress_tests).
-typing([eqwalizer]).
-include_lib("eunit/include/eunit.hrl").
aggregate_counts_many_shards_mixed_replies_test_() ->
{timeout, 15, fun aggregate_counts_many_shards_mixed_replies/0}.
aggregate_handoff_many_shards_mixed_replies_test_() ->
{timeout, 15, fun aggregate_handoff_many_shards_mixed_replies/0}.
aggregate_counts_many_shards_mixed_replies() ->
{Shards, LivePids, ExpectedCount} = build_count_shards(256),
State = #{shards => Shards, shard_count => 256},
try
{ExpectedCount, State} = session_manager_transfer:aggregate_counts(
reconnect_drain, State
)
after
stop_live_shards(LivePids)
end.
aggregate_handoff_many_shards_mixed_replies() ->
{Shards, LivePids, ExpectedResult} = build_handoff_shards(128),
State = #{shards => Shards, shard_count => 128},
try
{ExpectedResult, State} =
session_manager_transfer:aggregate_handoff_to_topology(['peer@host'], State)
after
stop_live_shards(LivePids)
end.
build_count_shards(Count) ->
lists:foldl(
fun(Index, {ShardAcc, PidAcc, ExpectedAcc}) ->
{Pid, MaybeLive, Expected} = count_shard(Index),
{
ShardAcc#{Index => #{pid => Pid, ref => make_ref()}},
add_live_pid(MaybeLive, Pid, PidAcc),
ExpectedAcc + Expected
}
end,
{#{}, [], 0},
lists:seq(1, Count)
).
count_shard(Index) ->
case Index rem 10 of
0 ->
dead_shard(0);
1 ->
{spawn_shard(invalid_reply), live, 0};
_ ->
{spawn_shard({ok, 1}), live, 1}
end.
build_handoff_shards(Count) ->
lists:foldl(
fun(Index, {ShardAcc, PidAcc, ExpectedAcc}) ->
{Pid, MaybeLive, Expected} = handoff_shard(Index),
{
ShardAcc#{Index => #{pid => Pid, ref => make_ref()}},
add_live_pid(MaybeLive, Pid, PidAcc),
sum_handoff(ExpectedAcc, Expected)
}
end,
{#{}, [], #{attempted => 0, handed_off => 0}},
lists:seq(1, Count)
).
handoff_shard(Index) ->
case Index rem 8 of
0 ->
dead_shard(#{attempted => 1, handed_off => 0});
1 ->
{spawn_shard(invalid_reply), live, #{attempted => 1, handed_off => 0}};
2 ->
{spawn_shard(2), live, #{attempted => 2, handed_off => 2}};
_ ->
{spawn_shard({ok, #{attempted => 3, handed_off => 2}}), live, #{
attempted => 3,
handed_off => 2
}}
end.
dead_shard(Expected) ->
Pid = spawn(fun() -> ok end),
wait_for_dead(Pid, 100),
{Pid, dead, Expected}.
spawn_shard(Reply) ->
spawn(fun() -> shard_loop(Reply) end).
shard_loop(Reply) ->
receive
{'$gen_call', From, _Request} ->
gen_server:reply(From, Reply),
shard_loop(Reply);
stop ->
ok
after 10000 ->
ok
end.
add_live_pid(live, Pid, Pids) ->
[Pid | Pids];
add_live_pid(dead, _Pid, Pids) ->
Pids.
stop_live_shards(Pids) ->
lists:foreach(fun(Pid) -> Pid ! stop end, Pids).
sum_handoff(Acc, Result) ->
#{
attempted => maps:get(attempted, Acc) + maps:get(attempted, Result),
handed_off => maps:get(handed_off, Acc) + maps:get(handed_off, Result)
}.
wait_for_dead(Pid, Attempts) ->
case is_process_alive(Pid) of
false ->
ok;
true when Attempts > 0 ->
timer:sleep(1),
wait_for_dead(Pid, Attempts - 1);
true ->
exit(Pid, kill),
ok
end.
File diff suppressed because it is too large. Load diff