summaryrefslogtreecommitdiff
path: root/lib/stdlib/test/gen_statem_SUITE.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/stdlib/test/gen_statem_SUITE.erl')
-rw-r--r--lib/stdlib/test/gen_statem_SUITE.erl600
1 files changed, 497 insertions, 103 deletions
diff --git a/lib/stdlib/test/gen_statem_SUITE.erl b/lib/stdlib/test/gen_statem_SUITE.erl
index b9084cc825..9b4ee9413f 100644
--- a/lib/stdlib/test/gen_statem_SUITE.erl
+++ b/lib/stdlib/test/gen_statem_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2016-2021. All Rights Reserved.
+%% Copyright Ericsson AB 2016-2022. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -43,7 +43,9 @@ all() ->
{group, sys},
hibernate, auto_hibernate, enter_loop, {group, undef_callbacks},
undef_in_terminate, {group, format_log},
- reply_by_alias_with_payload].
+ reply_by_alias_with_payload,
+ send_request_receive_reqid_collection, send_request_wait_reqid_collection,
+ send_request_check_reqid_collection].
groups() ->
[{start, [], tcs(start)},
@@ -53,6 +55,7 @@ groups() ->
{abnormal, [], tcs(abnormal)},
{abnormal_handle_event, [], tcs(abnormal)},
{sys, [], tcs(sys)},
+ {format_status, [], tcs(format_status)},
{sys_handle_event, [], tcs(sys)},
{undef_callbacks, [], tcs(undef_callbacks)},
{format_log, [], tcs(format_log)}].
@@ -66,9 +69,10 @@ tcs(abnormal) ->
[abnormal1, abnormal1clean, abnormal1dirty,
abnormal2, abnormal3, abnormal4];
tcs(sys) ->
- [sys1, call_format_status,
- error_format_status, terminate_crash_format,
- get_state, replace_state];
+ [sys1, {group, format_status}, get_state, replace_state];
+tcs(format_status) ->
+ [call_format_status, error_format_status, terminate_crash_format,
+ format_all_status];
tcs(undef_callbacks) ->
[undef_code_change, undef_terminate1, undef_terminate2,
pop_too_many];
@@ -88,7 +92,13 @@ init_per_group(GroupName, Config)
GroupName =:= sys_handle_event ->
[{callback_mode,handle_event_function}|Config];
init_per_group(undef_callbacks, Config) ->
- compile_oc_statem(Config),
+ try compile_oc_statem(Config)
+ catch Class : Reason : Stacktrace ->
+ {fail,{Class,Reason,Stacktrace}}
+ end,
+ Config;
+init_per_group(sys, Config) ->
+ compile_format_status_statem(Config),
Config;
init_per_group(_GroupName, Config) ->
Config.
@@ -119,6 +129,12 @@ compile_oc_statem(Config) ->
{ok, oc_statem} = compile:file(StatemPath),
ok.
+compile_format_status_statem(Config) ->
+ DataDir = ?config(data_dir, Config),
+ StatemPath = filename:join(DataDir, "format_status_statem.erl"),
+ {ok, format_status_statem} = compile:file(StatemPath),
+ ok.
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-define(EXPECT_FAILURE(Code, Reason),
try begin Code end of
@@ -452,76 +468,64 @@ stop7(Config) ->
%% Anonymous on remote node
stop8(Config) ->
- Node = gen_statem_stop8,
- {ok,NodeName} = ct_slave:start(Node),
- Statem =
- try
- Dir = filename:dirname(code:which(?MODULE)),
- rpc:block_call(NodeName, code, add_path, [Dir]),
- {ok,Pid} =
- rpc:block_call(
- NodeName, gen_statem,start,
- [?MODULE,start_arg(Config, []),[]]),
- ok = gen_statem:stop(Pid),
- false = rpc:block_call(NodeName, erlang, is_process_alive, [Pid]),
- noproc =
- ?EXPECT_FAILURE(gen_statem:stop(Pid), Reason1),
- Pid
- after
- {ok,NodeName} = ct_slave:stop(Node)
- end,
+ {ok,Peer,NodeName} = ?CT_PEER(),
+ Dir = filename:dirname(code:which(?MODULE)),
+ rpc:block_call(NodeName, code, add_path, [Dir]),
+ {ok,Pid} =
+ rpc:block_call(
+ NodeName, gen_statem,start,
+ [?MODULE,start_arg(Config, []),[]]),
+ ok = gen_statem:stop(Pid),
+ false = rpc:block_call(NodeName, erlang, is_process_alive, [Pid]),
+ noproc =
+ ?EXPECT_FAILURE(gen_statem:stop(Pid), Reason1),
+
+ peer:stop(Peer),
{{nodedown,NodeName},{sys,terminate,_}} =
- ?EXPECT_FAILURE(gen_statem:stop(Statem), Reason2),
+ ?EXPECT_FAILURE(gen_statem:stop(Pid), Reason2),
ok.
%% Registered name on remote node
stop9(Config) ->
Name = to_stop,
LocalSTM = {local,Name},
- Node = gen_statem__stop9,
- {ok,NodeName} = ct_slave:start(Node),
- Statem =
- try
- STM = {Name,NodeName},
- Dir = filename:dirname(code:which(?MODULE)),
- rpc:block_call(NodeName, code, add_path, [Dir]),
- {ok,Pid} =
- rpc:block_call(
- NodeName, gen_statem, start,
- [LocalSTM,?MODULE,start_arg(Config, []),[]]),
- ok = gen_statem:stop(STM),
- undefined = rpc:block_call(NodeName,erlang,whereis,[Name]),
- false = rpc:block_call(NodeName,erlang,is_process_alive,[Pid]),
- noproc =
- ?EXPECT_FAILURE(gen_statem:stop(STM), Reason1),
- STM
- after
- {ok,NodeName} = ct_slave:stop(Node)
- end,
+ {ok,Peer,NodeName} = ?CT_PEER(),
+
+ STM = {Name,NodeName},
+ Dir = filename:dirname(code:which(?MODULE)),
+ rpc:block_call(NodeName, code, add_path, [Dir]),
+ {ok,Pid} =
+ rpc:block_call(
+ NodeName, gen_statem, start,
+ [LocalSTM,?MODULE,start_arg(Config, []),[]]),
+ ok = gen_statem:stop(STM),
+ undefined = rpc:block_call(NodeName,erlang,whereis,[Name]),
+ false = rpc:block_call(NodeName,erlang,is_process_alive,[Pid]),
+ noproc =
+ ?EXPECT_FAILURE(gen_statem:stop(STM), Reason1),
+ peer:stop(Peer),
+
{{nodedown,NodeName},{sys,terminate,_}} =
- ?EXPECT_FAILURE(gen_statem:stop(Statem), Reason2),
+ ?EXPECT_FAILURE(gen_statem:stop(STM), Reason2),
ok.
%% Globally registered name on remote node
stop10(Config) ->
- Node = gen_statem_stop10,
STM = {global,to_stop},
- {ok,NodeName} = ct_slave:start(Node),
- try
- Dir = filename:dirname(code:which(?MODULE)),
- rpc:block_call(NodeName,code,add_path,[Dir]),
- {ok,Pid} =
- rpc:block_call(
- NodeName, gen_statem, start,
- [STM,?MODULE,start_arg(Config, []),[]]),
- global:sync(),
- ok = gen_statem:stop(STM),
- false = rpc:block_call(NodeName, erlang, is_process_alive, [Pid]),
- noproc =
- ?EXPECT_FAILURE(gen_statem:stop(STM), Reason1)
- after
- {ok,NodeName} = ct_slave:stop(Node)
- end,
+ {ok,Peer,NodeName} = ?CT_PEER(),
+ Dir = filename:dirname(code:which(?MODULE)),
+ rpc:block_call(NodeName,code,add_path,[Dir]),
+ {ok,Pid} =
+ rpc:block_call(
+ NodeName, gen_statem, start,
+ [STM,?MODULE,start_arg(Config, []),[]]),
+ global:sync(),
+ ok = gen_statem:stop(STM),
+ false = rpc:block_call(NodeName, erlang, is_process_alive, [Pid]),
+ noproc =
+ ?EXPECT_FAILURE(gen_statem:stop(STM), Reason1),
+ peer:stop(Peer),
+
noproc =
?EXPECT_FAILURE(gen_statem:stop(STM), Reason2),
ok.
@@ -1240,20 +1244,24 @@ code_change(_Config) ->
stop_it(Pid).
call_format_status(Config) ->
- {ok,Pid} = gen_statem:start(?MODULE, start_arg(Config, []), []),
+ call_format_status(Config,?MODULE,format_status_called),
+ call_format_status(Config, format_status_statem,
+ {data,[{"State",{format_status_called,format_data}}]}).
+call_format_status(Config, Module, Match) ->
+ {ok,Pid} = gen_statem:start(Module, start_arg(Config, []), []),
Status = sys:get_status(Pid),
{status,Pid,_Mod,[_PDict,running,_,_, Data]} = Status,
- [format_status_called|_] = lists:reverse(Data),
+ [Match|_] = lists:reverse(Data),
stop_it(Pid),
%% check that format_status can handle a name being an atom (pid is
%% already checked by the previous test)
{ok, Pid2} =
gen_statem:start(
- {local, gstm}, ?MODULE, start_arg(Config, []), []),
+ {local, gstm}, Module, start_arg(Config, []), []),
Status2 = sys:get_status(gstm),
{status,Pid2,Mod,[_PDict2,running,_,_,Data2]} = Status2,
- [format_status_called|_] = lists:reverse(Data2),
+ [Match|_] = lists:reverse(Data2),
stop_it(Pid2),
%% check that format_status can handle a name being a term other than a
@@ -1261,47 +1269,54 @@ call_format_status(Config) ->
GlobalName1 = {global,"CallFormatStatus"},
{ok,Pid3} =
gen_statem:start(
- GlobalName1, ?MODULE, start_arg(Config, []), []),
+ GlobalName1, Module, start_arg(Config, []), []),
Status3 = sys:get_status(GlobalName1),
{status,Pid3,Mod,[_PDict3,running,_,_,Data3]} = Status3,
- [format_status_called|_] = lists:reverse(Data3),
+ [Match|_] = lists:reverse(Data3),
stop_it(Pid3),
GlobalName2 = {global,{name, "term"}},
{ok,Pid4} =
gen_statem:start(
- GlobalName2, ?MODULE, start_arg(Config, []), []),
+ GlobalName2, Module, start_arg(Config, []), []),
Status4 = sys:get_status(GlobalName2),
{status,Pid4,Mod,[_PDict4,running,_,_, Data4]} = Status4,
- [format_status_called|_] = lists:reverse(Data4),
+ [Match|_] = lists:reverse(Data4),
stop_it(Pid4),
%% check that format_status can handle a name being a term other than a
%% pid or atom
dummy_via:reset(),
ViaName1 = {via,dummy_via,"CallFormatStatus"},
- {ok,Pid5} = gen_statem:start(ViaName1, ?MODULE, start_arg(Config, []), []),
+ {ok,Pid5} = gen_statem:start(ViaName1, Module, start_arg(Config, []), []),
Status5 = sys:get_status(ViaName1),
{status,Pid5,Mod, [_PDict5,running,_,_, Data5]} = Status5,
- [format_status_called|_] = lists:reverse(Data5),
+ [Match|_] = lists:reverse(Data5),
stop_it(Pid5),
ViaName2 = {via,dummy_via,{name,"term"}},
{ok, Pid6} =
gen_statem:start(
- ViaName2, ?MODULE, start_arg(Config, []), []),
+ ViaName2, Module, start_arg(Config, []), []),
Status6 = sys:get_status(ViaName2),
{status,Pid6,Mod,[_PDict6,running,_,_,Data6]} = Status6,
- [format_status_called|_] = lists:reverse(Data6),
+ [Match|_] = lists:reverse(Data6),
stop_it(Pid6).
-
-
error_format_status(Config) ->
error_logger_forwarder:register(),
OldFl = process_flag(trap_exit, true),
+ try
+ error_format_status(Config,?MODULE,{formatted,idle,"called format_status"}),
+ error_format_status(Config,format_status_statem,
+ {{formatted,idle},{formatted,"called format_status"}})
+ after
+ process_flag(trap_exit, OldFl),
+ error_logger_forwarder:unregister()
+ end.
+error_format_status(Config,Module,Match) ->
Data = "called format_status",
{ok,Pid} =
gen_statem:start(
- ?MODULE, start_arg(Config, {data,Data}), []),
+ Module, start_arg(Config, {data,Data}), []),
%% bad return value in the gen_statem loop
{{{bad_return_from_state_function,badreturn},_},_} =
?EXPECT_FAILURE(gen_statem:call(Pid, badreturn), Reason),
@@ -1310,18 +1325,15 @@ error_format_status(Config) ->
{Pid,
"** State machine"++_,
[Pid,{{call,_},badreturn},
- {formatted,idle,Data},
+ Match,
error,{bad_return_from_state_function,badreturn}|_]}} ->
ok;
Other when is_tuple(Other), element(1, Other) =:= error ->
- error_logger_forwarder:unregister(),
ct:fail({unexpected,Other})
after 1000 ->
- error_logger_forwarder:unregister(),
- ct:fail(timeout)
+ ct:fail({timeout,(fun F() -> receive M -> [M|F()] after 0 -> [] end end)()})
end,
- process_flag(trap_exit, OldFl),
- error_logger_forwarder:unregister(),
+
receive
%% Comes with SASL
{error_report,_,{Pid,crash_report,_}} ->
@@ -1332,12 +1344,24 @@ error_format_status(Config) ->
ok = verify_empty_msgq().
terminate_crash_format(Config) ->
+ dbg:tracer(),
error_logger_forwarder:register(),
OldFl = process_flag(trap_exit, true),
+ try
+ terminate_crash_format(Config,?MODULE,{formatted,idle,crash_terminate}),
+ terminate_crash_format(Config,format_status_statem,
+ {{formatted,idle},{formatted,crash_terminate}})
+ after
+ dbg:stop_clear(),
+ process_flag(trap_exit, OldFl),
+ error_logger_forwarder:unregister()
+ end.
+
+terminate_crash_format(Config, Module, Match) ->
Data = crash_terminate,
{ok,Pid} =
gen_statem:start(
- ?MODULE, start_arg(Config, {data,Data}), []),
+ Module, start_arg(Config, {data,Data}), []),
stop_it(Pid),
Self = self(),
receive
@@ -1346,18 +1370,14 @@ terminate_crash_format(Config) ->
"** State machine"++_,
[Pid,
{{call,{Self,_}},stop},
- {formatted,idle,Data},
- exit,{crash,terminate}|_]}} ->
+ Match,exit,{crash,terminate}|_]}} ->
ok;
Other when is_tuple(Other), element(1, Other) =:= error ->
- error_logger_forwarder:unregister(),
ct:fail({unexpected,Other})
after 1000 ->
- error_logger_forwarder:unregister(),
- ct:fail(timeout)
+ ct:fail({timeout,flush()})
end,
- process_flag(trap_exit, OldFl),
- error_logger_forwarder:unregister(),
+
receive
%% Comes with SASL
{error_report,_,{Pid,crash_report,_}} ->
@@ -1367,6 +1387,67 @@ terminate_crash_format(Config) ->
end,
ok = verify_empty_msgq().
+%% We test that all of the different status items can be
+%% formatted by the format_status/1 callback.
+format_all_status(Config) ->
+ error_logger_forwarder:register(),
+ OldFl = process_flag(trap_exit, true),
+
+ Data = fun(M) ->
+ maps:map(
+ fun(Key, Values) when Key =:= log;
+ Key =:= queue;
+ Key =:= postponed;
+ Key =:= timeouts ->
+ [{Key, Value} || Value <- Values];
+ (Key, Value) ->
+ {Key, Value}
+ end, M)
+ end,
+ {ok,Pid} =
+ gen_statem:start(
+ format_status_statem, start_arg(Config, {data,Data}), []),
+ sys:log(Pid, true),
+ ok = gen_statem:cast(Pid, postpone_event),
+ ok = gen_statem:cast(Pid, {timeout, 100000}),
+
+ {status,Pid, _, [_,_,_,_,Info]} = sys:get_status(Pid),
+ [{header, _Hdr},
+ {data, [_Status,_Parent,_Modules,
+ {"Time-outs",{1,[{timeouts,_}]}},
+ {"Logged Events",[{log,_}|_]},
+ {"Postponed",[{postponed,_}]}]},
+ {data, [{"State",{{state,idle},{data,Data}}}]}] = Info,
+
+ %% bad return value in the gen_statem loop
+ {{{bad_return_from_state_function,badreturn},_},_} =
+ ?EXPECT_FAILURE(gen_statem:call(Pid, badreturn), Reason),
+ Self = self(),
+ receive
+ {error,_GroupLeader,
+ {Pid,
+ "** State machine"++_,
+ [Pid,
+ {queue,{{call,{Self,_}},badreturn}},
+ {{state,idle},{data,Data}},error,
+ {reason,{bad_return_from_state_function,badreturn}},
+ __Modules,_StateFunctions,
+ [{postponed,{cast,postpone_event}}],
+ [_|_] = _Stacktrace,
+ {1,[{timeouts,{timeout,idle}}]},
+ [{log,_}|_] |_]}} ->
+ ok;
+ Other when is_tuple(Other), element(1, Other) =:= error ->
+ ct:fail({unexpected,Other})
+ after 1000 ->
+ ct:fail({timeout,flush()})
+ end,
+ receive
+ {error_report,_,_} -> ok
+ end,
+ error_logger_forwarder:unregister(),
+ process_flag(trap_exit, OldFl),
+ ok.
get_state(Config) ->
State = self(),
@@ -1851,12 +1932,14 @@ pop_too_many(_Config) ->
Machine =
#{init =>
fun () ->
- {ok,start,undefined}
+ {ok,state_1,undefined}
end,
- start =>
- fun ({call, From}, {change_callback_module, _Module} = Action,
- undefined = _Data) ->
- {keep_state_and_data,
+ state_1 =>
+ fun (enter, state_2, undefined) ->
+ {keep_state, enter}; % OTP-18239, should not be called
+ ({call, From}, {change_callback_module, _Module} = Action,
+ undefined = Data) ->
+ {next_state, state_2, Data,
[Action,
{reply,From,ok}]};
({call, From}, {verify, ?MODULE},
@@ -1864,8 +1947,8 @@ pop_too_many(_Config) ->
{keep_state_and_data,
[{reply,From,ok}]};
({call, From}, pop_callback_module = Action,
- undefined = _Data) ->
- {keep_state_and_data,
+ undefined = Data) ->
+ {next_state, state_2, Data,
[Action,
{reply,From,ok}]}
end},
@@ -1875,10 +1958,11 @@ pop_too_many(_Config) ->
{map_statem, Machine, []},
[{debug, [trace]}]),
- ok = gen_statem:call(STM, {change_callback_module, oc_statem}),
- ok = gen_statem:call(STM, {push_callback_module, ?MODULE}),
- ok = gen_statem:call(STM, {verify, ?MODULE}),
- ok = gen_statem:call(STM, pop_callback_module),
+ ok = gen_statem:call(STM, {change_callback_module, oc_statem}),
+ enter = gen_statem:call(STM, get_data), % OTP-18239
+ ok = gen_statem:call(STM, {push_callback_module, ?MODULE}),
+ ok = gen_statem:call(STM, {verify, ?MODULE}),
+ ok = gen_statem:call(STM, pop_callback_module),
BadAction = {bad_action_from_state_function, pop_callback_module},
{{BadAction, _},
{gen_statem,call,[STM,pop_callback_module,infinity]}} =
@@ -2229,6 +2313,311 @@ reply_by_alias_with_payload(Config) when is_list(Config) ->
ok
end.
+send_request_receive_reqid_collection(Config) when is_list(Config) ->
+ {ok,Pid1} = gen_statem:start(?MODULE, start_arg(Config, []), []),
+ {ok,Pid2} = gen_statem:start(?MODULE, start_arg(Config, []), []),
+ {ok,Pid3} = gen_statem:start(?MODULE, start_arg(Config, []), []),
+ send_request_receive_reqid_collection(Pid1, Pid2, Pid3),
+ send_request_receive_reqid_collection_timeout(Pid1, Pid2, Pid3),
+ send_request_receive_reqid_collection_error(Pid1, Pid2, Pid3),
+ stopped = gen_statem:call(Pid1, {stop,shutdown}),
+ stopped = gen_statem:call(Pid3, {stop,shutdown}),
+ check_stopped(Pid1),
+ check_stopped(Pid2),
+ check_stopped(Pid3),
+ ok.
+
+send_request_receive_reqid_collection(Pid1, Pid2, Pid3) ->
+
+ ReqId0 = gen_statem:send_request(Pid1, 'alive?'),
+
+ ReqIdC0 = gen_statem:reqids_new(),
+
+ ReqId1 = gen_statem:send_request(Pid1, {delayed_answer,400}),
+ ReqIdC1 = gen_statem:reqids_add(ReqId1, req1, ReqIdC0),
+ 1 = gen_statem:reqids_size(ReqIdC1),
+
+ ReqIdC2 = gen_statem:send_request(Pid2, {delayed_answer,1}, req2, ReqIdC1),
+ 2 = gen_statem:reqids_size(ReqIdC2),
+
+ ReqIdC3 = gen_statem:send_request(Pid3, {delayed_answer,200}, req3, ReqIdC2),
+ 3 = gen_statem:reqids_size(ReqIdC3),
+
+ {{reply, delayed}, req2, ReqIdC4} = gen_statem:receive_response(ReqIdC3, infinity, true),
+ 2 = gen_statem:reqids_size(ReqIdC4),
+
+ {{reply, delayed}, req3, ReqIdC5} = gen_statem:receive_response(ReqIdC4, 5678, true),
+ 1 = gen_statem:reqids_size(ReqIdC5),
+
+ {{reply, delayed}, req1, ReqIdC6} = gen_statem:receive_response(ReqIdC5, 5000, true),
+ 0 = gen_statem:reqids_size(ReqIdC6),
+
+ no_request = gen_statem:receive_response(ReqIdC6, 5000, true),
+
+ {reply, yes} = gen_statem:receive_response(ReqId0, infinity),
+
+ ok.
+
+send_request_receive_reqid_collection_timeout(Pid1, Pid2, Pid3) ->
+
+ ReqId0 = gen_statem:send_request(Pid1, 'alive?'),
+
+ ReqIdC0 = gen_statem:reqids_new(),
+
+ ReqId1 = gen_statem:send_request(Pid1, {delayed_answer,1000}),
+ ReqIdC1 = gen_statem:reqids_add(ReqId1, req1, ReqIdC0),
+
+ ReqIdC2 = gen_statem:send_request(Pid2, {delayed_answer,1}, req2, ReqIdC1),
+
+ ReqId3 = gen_statem:send_request(Pid3, {delayed_answer,500}),
+ ReqIdC3 = gen_statem:reqids_add(ReqId3, req3, ReqIdC2),
+
+ Deadline = erlang:monotonic_time(millisecond) + 100,
+
+ {{reply, delayed}, req2, ReqIdC4} = gen_statem:receive_response(ReqIdC3, {abs, Deadline}, true),
+ 2 = gen_statem:reqids_size(ReqIdC4),
+
+ timeout = gen_statem:receive_response(ReqIdC4, {abs, Deadline}, true),
+
+ Abandoned = lists:sort([{ReqId1, req1}, {ReqId3, req3}]),
+ Abandoned = lists:sort(gen_statem:reqids_to_list(ReqIdC4)),
+
+ %% Make sure requests were abandoned...
+ timeout = gen_statem:receive_response(ReqIdC4, {abs, Deadline+1000}, true),
+
+ {reply, yes} = gen_statem:receive_response(ReqId0, infinity),
+
+ ok.
+
+send_request_receive_reqid_collection_error(Pid1, Pid2, Pid3) ->
+
+ ReqId0 = gen_statem:send_request(Pid1, 'alive?'),
+
+ ReqIdC0 = gen_statem:reqids_new(),
+
+ ReqId1 = gen_statem:send_request(Pid1, {delayed_answer,400}),
+ ReqIdC1 = gen_statem:reqids_add(ReqId1, req1, ReqIdC0),
+ try
+ nope = gen_statem:reqids_add(ReqId1, req2, ReqIdC1)
+ catch
+ error:badarg -> ok
+ end,
+
+ unlink(Pid2),
+ exit(Pid2, kill),
+ ReqIdC2 = gen_statem:send_request(Pid2, {delayed_answer,1}, req2, ReqIdC1),
+ ReqIdC3 = gen_statem:send_request(Pid3, {delayed_answer,200}, req3, ReqIdC2),
+ 3 = gen_statem:reqids_size(ReqIdC3),
+
+ {{error, {noproc, _}}, req2, ReqIdC4} = gen_statem:receive_response(ReqIdC3, 2000, true),
+ 2 = gen_statem:reqids_size(ReqIdC4),
+
+ {{reply, delayed}, req3, ReqIdC4} = gen_statem:receive_response(ReqIdC4, infinity, false),
+
+ {{reply, delayed}, req1, ReqIdC4} = gen_statem:receive_response(ReqIdC4, infinity, false),
+
+ {reply, yes} = gen_statem:receive_response(ReqId0, infinity),
+
+ ok.
+
+send_request_wait_reqid_collection(Config) when is_list(Config) ->
+ {ok,Pid1} = gen_statem:start(?MODULE, start_arg(Config, []), []),
+ {ok,Pid2} = gen_statem:start(?MODULE, start_arg(Config, []), []),
+ {ok,Pid3} = gen_statem:start(?MODULE, start_arg(Config, []), []),
+ send_request_wait_reqid_collection(Pid1, Pid2, Pid3),
+ send_request_wait_reqid_collection_timeout(Pid1, Pid2, Pid3),
+ send_request_wait_reqid_collection_error(Pid1, Pid2, Pid3),
+ stopped = gen_statem:call(Pid1, {stop,shutdown}),
+ stopped = gen_statem:call(Pid3, {stop,shutdown}),
+ check_stopped(Pid1),
+ check_stopped(Pid2),
+ check_stopped(Pid3),
+ ok.
+
+send_request_wait_reqid_collection(Pid1, Pid2, Pid3) ->
+
+ ReqId0 = gen_statem:send_request(Pid1, 'alive?'),
+
+ ReqIdC0 = gen_statem:reqids_new(),
+
+ ReqId1 = gen_statem:send_request(Pid1, {delayed_answer,400}),
+ ReqIdC1 = gen_statem:reqids_add(ReqId1, req1, ReqIdC0),
+ 1 = gen_statem:reqids_size(ReqIdC1),
+
+ ReqIdC2 = gen_statem:send_request(Pid2, {delayed_answer,1}, req2, ReqIdC1),
+ 2 = gen_statem:reqids_size(ReqIdC2),
+
+ ReqIdC3 = gen_statem:send_request(Pid3, {delayed_answer,200}, req3, ReqIdC2),
+ 3 = gen_statem:reqids_size(ReqIdC3),
+
+ {{reply, delayed}, req2, ReqIdC4} = gen_statem:wait_response(ReqIdC3, infinity, true),
+ 2 = gen_statem:reqids_size(ReqIdC4),
+
+ {{reply, delayed}, req3, ReqIdC5} = gen_statem:wait_response(ReqIdC4, 5678, true),
+ 1 = gen_statem:reqids_size(ReqIdC5),
+
+ {{reply, delayed}, req1, ReqIdC6} = gen_statem:wait_response(ReqIdC5, 5000, true),
+ 0 = gen_statem:reqids_size(ReqIdC6),
+
+ no_request = gen_statem:wait_response(ReqIdC6, 5000, true),
+
+ {reply, yes} = gen_statem:receive_response(ReqId0, infinity),
+
+ ok.
+
+send_request_wait_reqid_collection_timeout(Pid1, Pid2, Pid3) ->
+
+ ReqId0 = gen_statem:send_request(Pid1, 'alive?'),
+
+ ReqIdC0 = gen_statem:reqids_new(),
+
+ ReqId1 = gen_statem:send_request(Pid1, {delayed_answer,1000}),
+ ReqIdC1 = gen_statem:reqids_add(ReqId1, req1, ReqIdC0),
+
+ ReqIdC2 = gen_statem:send_request(Pid2, {delayed_answer,1}, req2, ReqIdC1),
+
+ ReqId3 = gen_statem:send_request(Pid3, {delayed_answer,500}),
+ ReqIdC3 = gen_statem:reqids_add(ReqId3, req3, ReqIdC2),
+
+ Deadline = erlang:monotonic_time(millisecond) + 100,
+
+ {{reply, delayed}, req2, ReqIdC4} = gen_statem:wait_response(ReqIdC3, {abs, Deadline}, true),
+ 2 = gen_statem:reqids_size(ReqIdC4),
+
+ timeout = gen_statem:wait_response(ReqIdC4, {abs, Deadline}, true),
+
+ Unhandled = lists:sort([{ReqId1, req1}, {ReqId3, req3}]),
+ Unhandled = lists:sort(gen_statem:reqids_to_list(ReqIdC4)),
+
+ %% Make sure requests were not abandoned...
+ {{reply, delayed}, req3, ReqIdC4} = gen_statem:wait_response(ReqIdC4, {abs, Deadline+1500}, false),
+ {{reply, delayed}, req1, ReqIdC4} = gen_statem:wait_response(ReqIdC4, {abs, Deadline+1500}, false),
+
+ {reply, yes} = gen_statem:receive_response(ReqId0),
+
+ ok.
+
+send_request_wait_reqid_collection_error(Pid1, Pid2, Pid3) ->
+
+ ReqId0 = gen_statem:send_request(Pid1, 'alive?'),
+
+ ReqIdC0 = gen_statem:reqids_new(),
+
+ ReqId1 = gen_statem:send_request(Pid1, {delayed_answer,400}),
+ ReqIdC1 = gen_statem:reqids_add(ReqId1, req1, ReqIdC0),
+ try
+ nope = gen_statem:reqids_add(ReqId1, req2, ReqIdC1)
+ catch
+ error:badarg -> ok
+ end,
+
+ unlink(Pid2),
+ exit(Pid2, kill),
+ ReqIdC2 = gen_statem:send_request(Pid2, {delayed_answer,1}, req2, ReqIdC1),
+ ReqIdC3 = gen_statem:send_request(Pid3, {delayed_answer,200}, req3, ReqIdC2),
+ 3 = gen_statem:reqids_size(ReqIdC3),
+
+ {{error, {noproc, _}}, req2, ReqIdC4} = gen_statem:wait_response(ReqIdC3, 2000, true),
+ 2 = gen_statem:reqids_size(ReqIdC4),
+
+ {{reply, delayed}, req3, ReqIdC4} = gen_statem:wait_response(ReqIdC4, infinity, false),
+
+ {{reply, delayed}, req1, ReqIdC4} = gen_statem:wait_response(ReqIdC4, infinity, false),
+
+ {reply, yes} = gen_statem:receive_response(ReqId0, infinity),
+
+ ok.
+
+send_request_check_reqid_collection(Config) when is_list(Config) ->
+ {ok,Pid1} = gen_statem:start(?MODULE, start_arg(Config, []), []),
+ {ok,Pid2} = gen_statem:start(?MODULE, start_arg(Config, []), []),
+ {ok,Pid3} = gen_statem:start(?MODULE, start_arg(Config, []), []),
+ send_request_check_reqid_collection(Pid1, Pid2, Pid3),
+ send_request_check_reqid_collection_error(Pid1, Pid2, Pid3),
+ stopped = gen_statem:call(Pid1, {stop,shutdown}),
+ stopped = gen_statem:call(Pid3, {stop,shutdown}),
+ check_stopped(Pid1),
+ check_stopped(Pid2),
+ check_stopped(Pid3),
+ ok.
+
+send_request_check_reqid_collection(Pid1, Pid2, Pid3) ->
+
+ ReqId0 = gen_statem:send_request(Pid1, 'alive?'),
+
+ receive after 100 -> ok end,
+
+ ReqIdC0 = gen_statem:reqids_new(),
+
+ ReqIdC1 = gen_statem:send_request(Pid1, {delayed_answer,400}, req1, ReqIdC0),
+ 1 = gen_statem:reqids_size(ReqIdC1),
+
+ ReqId2 = gen_statem:send_request(Pid2, {delayed_answer,1}),
+ ReqIdC2 = gen_statem:reqids_add(ReqId2, req2, ReqIdC1),
+ 2 = gen_statem:reqids_size(ReqIdC2),
+
+ ReqIdC3 = gen_statem:send_request(Pid3, {delayed_answer,200}, req3, ReqIdC2),
+ 3 = gen_statem:reqids_size(ReqIdC3),
+
+ Msg0 = next_msg(),
+ no_reply = gen_statem:check_response(Msg0, ReqIdC3, true),
+
+ {{reply, delayed}, req2, ReqIdC4} = gen_statem:check_response(next_msg(), ReqIdC3, true),
+ 2 = gen_statem:reqids_size(ReqIdC4),
+
+ {{reply, delayed}, req3, ReqIdC5} = gen_statem:check_response(next_msg(), ReqIdC4, true),
+ 1 = gen_statem:reqids_size(ReqIdC5),
+
+ {{reply, delayed}, req1, ReqIdC6} = gen_statem:check_response(next_msg(), ReqIdC5, true),
+ 0 = gen_statem:reqids_size(ReqIdC6),
+
+ no_request = gen_statem:check_response(Msg0, ReqIdC6, true),
+
+ {reply, yes} = gen_statem:check_response(Msg0, ReqId0),
+
+ ok.
+
+send_request_check_reqid_collection_error(Pid1, Pid2, Pid3) ->
+
+ ReqId0 = gen_statem:send_request(Pid1, 'alive?'),
+
+ receive after 100 -> ok end,
+
+ ReqIdC0 = gen_statem:reqids_new(),
+
+ ReqId1 = gen_statem:send_request(Pid1, {delayed_answer,400}),
+ ReqIdC1 = gen_statem:reqids_add(ReqId1, req1, ReqIdC0),
+ try
+ nope = gen_statem:reqids_add(ReqId1, req2, ReqIdC1)
+ catch
+ error:badarg -> ok
+ end,
+
+ unlink(Pid2),
+ exit(Pid2, kill),
+ ReqIdC2 = gen_statem:send_request(Pid2, {delayed_answer,1}, req2, ReqIdC1),
+
+ ReqIdC3 = gen_statem:send_request(Pid3, {delayed_answer,200}, req3, ReqIdC2),
+ 3 = gen_statem:reqids_size(ReqIdC3),
+
+ Msg0 = next_msg(),
+
+ no_reply = gen_statem:check_response(Msg0, ReqIdC3, true),
+
+ {{error, {noproc, _}}, req2, ReqIdC4} = gen_statem:check_response(next_msg(), ReqIdC3, true),
+ 2 = gen_statem:reqids_size(ReqIdC4),
+
+ {{reply, delayed}, req3, ReqIdC4} = gen_statem:check_response(next_msg(), ReqIdC4, false),
+
+ {{reply, delayed}, req1, ReqIdC4} = gen_statem:check_response(next_msg(), ReqIdC4, false),
+
+ {reply, yes} = gen_statem:check_response(Msg0, ReqId0),
+
+ ok.
+
+next_msg() ->
+ receive M -> M end.
+
%%
%% Functionality check
%%
@@ -2473,6 +2862,11 @@ idle({call,From}, {timeout,Time}, _Data) ->
AbsTime = erlang:monotonic_time(millisecond) + Time,
{next_state,timeout,{From,Time},
{timeout,AbsTime,idle,[{abs,true}]}};
+idle(cast, {timeout,Time}, _Data) ->
+ AbsTime = erlang:monotonic_time(millisecond) + Time,
+ {keep_state_and_data,{timeout,AbsTime,idle,[{abs,true}]}};
+idle(cast, postpone_event, _Data) ->
+ {keep_state_and_data,postpone};
idle(cast, next_event, _Data) ->
{next_state,next_events,[a,b,c],
[{next_event,internal,a},