mirror of
https://github.com/valitydev/woody_erlang.git
synced 2024-11-06 02:15:19 +00:00
4fab3f64af
It's usually needed earlier than the message itself. And may even be used to skip formatting altogether, with `msg_fun()` facility in Erlang logger. Simplify event handler interface which should make this module more usable down the line. Also use monotonic timestamps when measuring time intervals.
191 lines
6.3 KiB
Erlang
191 lines
6.3 KiB
Erlang
-module(woody_transport_opts_SUITE).
|
|
|
|
-include_lib("common_test/include/ct.hrl").
|
|
-include_lib("stdlib/include/assert.hrl").
|
|
|
|
-include("woody_test_thrift.hrl").
|
|
|
|
%% woody_server_thrift_handler callbacks
|
|
-behaviour(woody_server_thrift_handler).
|
|
|
|
-export([handle_function/4]).
|
|
|
|
-export([all/0]).
|
|
-export([init_per_suite/1]).
|
|
-export([end_per_suite/1]).
|
|
-export([init_per_testcase/2]).
|
|
|
|
-export([respects_max_connections/1]).
|
|
-export([shuts_down_gracefully/1]).
|
|
|
|
-type case_name() :: atom().
|
|
-type config() :: [{atom(), any()}].
|
|
|
|
-spec all() -> [case_name()].
|
|
-spec init_per_suite(config()) -> config().
|
|
-spec end_per_suite(config()) -> any().
|
|
-spec init_per_testcase(case_name(), config()) -> config().
|
|
|
|
-spec respects_max_connections(config()) -> any().
|
|
-spec shuts_down_gracefully(config()) -> any().
|
|
|
|
-spec handle_function(woody:func(), woody:args(), woody_context:ctx(), woody:options()) -> {ok, woody:result()}.
|
|
|
|
%%
|
|
|
|
all() ->
|
|
[
|
|
respects_max_connections,
|
|
shuts_down_gracefully
|
|
].
|
|
|
|
init_per_suite(C) ->
|
|
% dbg:tracer(), dbg:p(all, c),
|
|
% dbg:tpl({ranch_server, '_', '_'}, x),
|
|
{ok, Apps} = application:ensure_all_started(woody),
|
|
[{suite_apps, Apps} | C].
|
|
|
|
end_per_suite(C) ->
|
|
[application:stop(App) || App <- ?config(suite_apps, C)].
|
|
|
|
%%
|
|
|
|
init_per_testcase(Name, C) ->
|
|
Port = get_random_port(),
|
|
[
|
|
{client, #{
|
|
url => iolist_to_binary(["http://localhost:", integer_to_list(Port), "/"]),
|
|
event_handler => {woody_ct_event_h, {client, Name}}
|
|
}},
|
|
{server, #{
|
|
ip => {127, 0, 0, 1},
|
|
port => Port,
|
|
event_handler => [{woody_ct_event_h, {server, Name}}],
|
|
shutdown_timeout => 5000
|
|
}},
|
|
{testcase, Name}
|
|
| C
|
|
].
|
|
|
|
%%
|
|
|
|
respects_max_connections(C) ->
|
|
% (10; 20]
|
|
MaxConns = 10 + rand:uniform(10),
|
|
Table = ets:new(?MODULE, [public, {read_concurrency, true}, {write_concurrency, true}]),
|
|
true = ets:insert_new(Table, [{slot, 0}]),
|
|
Service = {woody_test_thrift, 'Weapons'},
|
|
Client = ?config(client, C),
|
|
Handler = {"/", {Service, {?MODULE, {?config(testcase, C), Table}}}},
|
|
% NOTE
|
|
% > https://github.com/ninenines/ranch/blob/1.3.2/src/ranch_conns_sup.erl#L184
|
|
% If we do not set `max_keepalive = 1` here then any request received by the server
|
|
% except for `MaxConns` first will wait in queue until the keepalive times out in one of these
|
|
% `MaxConns` connections, which results in that it exits cleanly. Unfortunately, the default keepalive
|
|
% timeout is 5000 ms which is the same as the default woody request timeout.
|
|
% I wonder how this behavior affects production traffic as most of woody servers there configured with
|
|
% keepalive timeout of 60 s.
|
|
TransportOpts = #{max_connections => MaxConns, num_acceptors => 1},
|
|
ProtocolOpts = #{max_keepalive => 1},
|
|
ReadBodyOpts = #{},
|
|
{ok, ServerPid} = start_woody_server(Handler, TransportOpts, ProtocolOpts, ReadBodyOpts, C),
|
|
Results = genlib_pmap:map(
|
|
fun(_) ->
|
|
woody_client:call({Service, 'get_weapon', {<<"BFG">>, <<>>}}, Client)
|
|
end,
|
|
lists:seq(1, MaxConns * 10)
|
|
),
|
|
Slots = lists:map(
|
|
fun({ok, #'Weapon'{slot_pos = Slot}}) -> Slot end,
|
|
Results
|
|
),
|
|
?assert(lists:max(Slots) =< MaxConns),
|
|
ok = stop_woody_server(ServerPid).
|
|
|
|
-define(receive_or_timeout(Msg, Timeout),
|
|
receive
|
|
Msg -> ok
|
|
after Timeout -> timeout
|
|
end
|
|
).
|
|
|
|
shuts_down_gracefully(C) ->
|
|
Client = ?config(client, C),
|
|
Handler = {"/", {{woody_test_thrift, 'Powerups'}, {?MODULE, {}}}},
|
|
TransportOpts = #{max_connections => 10},
|
|
ProtocolOpts = #{max_keepalive => 2, request_timeout => 1000},
|
|
{ok, ServerPid} = start_woody_server(Handler, TransportOpts, ProtocolOpts, #{}, C),
|
|
ServerMonitor = erlang:monitor(process, ServerPid),
|
|
true = unlink(ServerPid),
|
|
ParentPid = self(),
|
|
%% send a shutdown signal to the server in 1000ms
|
|
%% then try making a new connection with it, expect econnrefused
|
|
TestPid = spawn_link(fun() -> process_econnrefused_test(Client, ParentPid) end),
|
|
_ = spawn_link(fun() -> process_delayed_kill(ServerPid, TestPid, 1000) end),
|
|
%% fire some requests and expect them to finish successfuly
|
|
%% even when server is shutting down in the meantime
|
|
_ = genlib_pmap:map(
|
|
fun(_) ->
|
|
?assertEqual(
|
|
{ok, #'Powerup'{name = <<"Warbanner">>}},
|
|
get_powerup(Client, <<"Warbanner">>, <<>>)
|
|
)
|
|
end,
|
|
lists:seq(1, 10)
|
|
),
|
|
%% wait for the async test and server shutdown to finish
|
|
ok = ?receive_or_timeout(econnrefused_test_success, 0),
|
|
ok = ?receive_or_timeout({'DOWN', ServerMonitor, _, _, _}, 5000).
|
|
|
|
process_econnrefused_test(Client, ParentPid) ->
|
|
receive
|
|
listener_suspended ->
|
|
?assertError(
|
|
{woody_error, {internal, resource_unavailable, <<"econnrefused">>}},
|
|
get_powerup(Client, <<"Warbanner">>, <<>>)
|
|
),
|
|
erlang:send(ParentPid, econnrefused_test_success)
|
|
end.
|
|
|
|
process_delayed_kill(ServerPid, TestPid, Timeout) ->
|
|
timer:sleep(Timeout),
|
|
erlang:send_after(500, TestPid, listener_suspended),
|
|
ok = proc_lib:stop(ServerPid, shutdown, infinity).
|
|
|
|
%%
|
|
|
|
start_woody_server(Handler, TransportOpts, ProtocolOpts, ReadBodyOpts, C) ->
|
|
ServerOpts0 = ?config(server, C),
|
|
SupervisorOpts = woody_server:child_spec(
|
|
{?MODULE, ?config(testcase, C)},
|
|
ServerOpts0#{
|
|
handlers => [Handler],
|
|
read_body_opts => ReadBodyOpts,
|
|
transport_opts => TransportOpts,
|
|
protocol_opts => ProtocolOpts
|
|
}
|
|
),
|
|
genlib_adhoc_supervisor:start_link(#{}, [SupervisorOpts]).
|
|
|
|
stop_woody_server(Pid) ->
|
|
true = unlink(Pid),
|
|
true = exit(Pid, shutdown),
|
|
ok.
|
|
|
|
handle_function(get_weapon, {Name, _}, _Context, {respects_max_connections, Table}) ->
|
|
Slot = ets:update_counter(Table, slot, 1),
|
|
ok = timer:sleep(rand:uniform(10)),
|
|
_ = ets:update_counter(Table, slot, -1),
|
|
{ok, #'Weapon'{name = Name, slot_pos = Slot}};
|
|
handle_function(get_powerup, {Name, _}, _Context, _) ->
|
|
ok = timer:sleep(2000),
|
|
{ok, #'Powerup'{name = Name}}.
|
|
|
|
get_powerup(Client, Name, Arg) ->
|
|
woody_client:call({{woody_test_thrift, 'Powerups'}, 'get_powerup', {Name, Arg}}, Client).
|
|
|
|
%%
|
|
|
|
get_random_port() ->
|
|
32767 + rand:uniform(32000).
|