Killing a gen_server without failing the Common Test - erlang

I implemented a module which is purposefully supposed to crash (to test the functionality of another module, which is monitoring it). The problem is, when this gen_server crashes it also causes the common test for it to fail. I've tried using try/catch and setting process_flag(trap_exit, true) but nothing seems to work.
Here is some relevant code:
-module(mod_bad_process).
% ...
%% ct calls this function directly
kill() ->
gen_server:call(?MODULE, {update_behavior, kill}).
% ...
handle_cast({update_behavior, Behavior}, _From, State) ->
case Behavior of
kill -> {stop, killed, State};
_ -> {reply, ok, State#{state := Behavior}}
end;
% ...
And the common test:
% ...
-define(BAD_PROC, mod_bad_process).
% ...
remonitor_test(_Conf) ->
InitialPid = whereis(?BAD_PROC),
true = undefined =/= InitialPid,
true = is_monitored_gen_server(?BAD_PROC),
mod_bad_process:kill(), % gen_server crashes
timer:sleep(?REMONITOR_DELAY_MS),
FinalPid = whereis(?BAD_PROC),
true = InitialPid =/= FinalPid,
true = undefined =/= FinalPid,
true = is_monitored_gen_server(?BAD_PROC).
% ...
And the resulting error from ct:
*** CT Error Notification 2021-07-16 16:08:20.791 ***
gen_server:call failed on line 238
Reason: {killed,{gen_server,call,...}}
=== Ended at 2021-07-16 16:08:20
=== Location: [{gen_server,call,238},
{mod_bad_process,kill,48},
{monitor_tests,remonitor_test,62},
{test_server,ts_tc,1784},
{test_server,run_test_case_eval1,1293},
{test_server,run_test_case_eval,1225}]
=== === Reason: {killed,{gen_server,call,
[mod_bad_process_global,
{update_behavior,kill}]}}
===
*** monitor_remonitor_test failed.
Skipping all other cases in sequence.
Any ideas on how to get this functionality without failing the common test?

The problem was that my try/catch attempts were not pattern matching to the actual error. Here is the fix:
-module(mod_bad_process).
% ...
kill() ->
try gen_server:call(?MODULE, {update_behavior, kill}) of
_ -> error(failed_to_kill)
catch
exit:{killed, _} -> ok
end.
% ...

Related

How to case switch in erlang using a function value?

This program is crashing despite seeming to work. I don't understand why though. I'm trying to accomplish a deep merge and need conditional logic.
Given the following list:
ManOne = #{ "Bob" => #{"Sagget" => #{}} }
ManTwo = #{ "Bob" => #{"Daniels" => #{}} }
I'm trying to compare them as follows, this function returns true as expected:
check_if_same(M1, M2) ->
{ok, lists:sort( maps:keys(M1) ) == lists:sort( maps:keys(M2) )}.
merger(M1, M2) ->
M1_Keys = maps:keys(M1),
M2_Keys = maps:keys(M2),
do_merge(M1, M2, M1_Keys).
do_merge(M1, M2, [Head|Tail]) ->
Check = check_if_same(M1, M2),
io:fwrite("Check is: ~p\n", [Check]),
case Check of
{ok, true} ->
io:fwrite("true\n");
{ok, false} ->
io:fwrite("false\n")
end,
do_merge(M1, M2, Tail);
do_merge(M1, M2, []) ->
ok.
check_if_same(M1, M2) ->
{ok, lists:sort( maps:keys(M1) ) == lists:sort( maps:keys(M2) )}.
gives the following output:
Check is: {ok,true}
true
{"init terminating in do_boot",{{badmap,ok},[{maps,keys,[ok],[]},{helloworld,merger,2,[{file,"helloworld.erl"},{line,9}]},{init,start_em,1,[]},{init,do_boot,3,[]}]}}
init terminating in do_boot ()
Crash dump is being written to: erl_crash.dump...done
Let's go over your question first because there's a few misconceptions and/or corrections that we can make.
Your Description
This program is crashing despite seeming to work. I don't understand why though. I'm trying to accomplish a deep merge and need conditional logic.
Given the following list:
ManOne = #{ "Bob" => #{"Sagget" => #{}} }
ManTwo = #{ "Bob" => #{"Daniels" => #{}} }
Note that the above are NOT lists, they are maps, which function entirely differently.
A map is, for all intents and purposes, a lookup table until it contains ~31 key/value pairs.
At this point, it becomes a HashMap (this can be seen by viewing the elements as they become unordered after the map becomes a HashMap).
I'm trying to compare them as follows, this function returns true as expected:
check_if_same(M1, M2) ->
{ok, lists:sort( maps:keys(M1) ) == lists:sort( maps:keys(M2) )}.
This is an incorrect way to assert equality; in erlang, it is suggested to not use == to check equality.
Instead, =:= should be used.
The reason for this is due to the fact that == does NOT check the type of the elements it is comparing and only takes a fuzzy value - i.e 1 == 1.0 will return true but 1 =:= 1.0 will return false.
Personally, I would recommend instead using Erlang's Pattern-Matching to check your values.
This could be implemented using the following snippet:
-spec check_if_same(M1 :: map(), M2 :: map()) -> boolean().
check_if_same(M1, M2) ->
SortedKeys1 = lists:sort(maps:keys(M1)),
SortedKeys2 = lists:sort(maps:keys(M2)),
%% We hide the implementation of the function in
%% a function with the same name suffixed with an
%% underscore. This allows us to have a public api
%% but keep the implementation internal which allows
%% the code to be a bit cleaner.
check_if_same_(SortedKeys1, SortedKeys2).
%% If they're both empty then we've gone through
%% every key meaning that they must be identical
check_if_same_([], []) ->
true;
%% If the current Key on both heads is the same
%% then recurse and check the next and so on
check_if_same_([Key|Tail1], [Key|Tail2]) ->
check_if_same_(Tail1, Tail2);
%% If we get anything else, e.g more keys in
%% one than the other or the keys don't match,
%% then we'll fall in to this case.
%% As we know anything that falls in to this
%% case doesn't match, we just return false
check_if_same_(Keys1, Keys2) when is_list(Keys1), is_list(Keys2) ->
false.
Note that in the above snippet, I only ever returned true or false - my recommendation for cleaner code would be to keep to the following formats;
ok - This is typically for functions where you care about the effect and not the return
true | false - This is typically for comparison functions, i.e is_binary/1, is_function/1
{ok, Value} - This would typically be for any function where you care about the value returned
{error, Reason} - This would be used whenever you expect an error so that you can bubble the error back up the chain with an easy-to-match format
Your Code Snippet
merger(M1, M2) ->
M1_Keys = maps:keys(M1),
%% Note that you don't use the M2Keys here so you don't need to do the work to get them
M2_Keys = maps:keys(M2),
do_merge(M1, M2, M1_Keys).
do_merge(M1, M2, [Head|Tail]) ->
Check = check_if_same(M1, M2),
%% It's generally recommended to stick to io:format/2 rather than io:fwrite/2
io:fwrite("Check is: ~p\n", [Check]),
case Check of
{ok, true} ->
io:fwrite("true\n");
{ok, false} ->
io:fwrite("false\n")
end,
do_merge(M1, M2, Tail);
do_merge(M1, M2, []) ->
ok.
check_if_same(M1, M2) ->
{ok, lists:sort( maps:keys(M1) ) == lists:sort( maps:keys(M2) )}.
Now, the above snippet (other than being a bit inefficient) is perfectly okay erlang and will work as expected
gives the following output:
Check is: {ok,true}
true
{"init terminating in do_boot",{{badmap,ok},[{maps,keys,[ok],[]},{helloworld,merger,2,[{file,"helloworld.erl"},{line,9}]},{init,start_em,1,[]},{init,do_boot,3,[]}]}}
init terminating in do_boot ()
Crash dump is being written to: erl_crash.dump...done
This crash dump is where the real issue is;
Check is: {ok,true}
true
From this we can tell that we
Hit the io:fwrite/2 (io:fwrite("Check is: ~p\n", [Check]))
Entered the {ok, true} path in the case (io:fwrite("true\n"))
The next line is where we see the actual problem, let's break it down:
"init terminating in do_boot" - We failed when starting up, this might be when running an escript or starting an app
Now let's break down that tuple:
{
{badmap,ok}, %% The function we called expected a map and we passed in 'ok'
[
{maps,keys,[ok],[]}, %% We called maps:keys/1 with 'ok' as an arg
{helloworld,merger,2,[{file,"helloworld.erl"},{line,9}]}, %% This was called at helloworld:merger/2 (helloworld.erl:9)
{init,start_em,1,[]},{init,do_boot,3,[]} %% We failed on start up
]
}
What we can take away from this is that you're calling merger in your code with an invalid value of ok on line 9 of helloworld.erl
There is some missing information. Although this code looks like a first draft or step, it works as expected. I tested it in the shell and got this:
-module (merger).
-compile(export_all).
merger(M1, M2) ->
M1_Keys = maps:keys(M1),
M2_Keys = maps:keys(M2),
do_merge(M1, M2, M1_Keys).
do_merge(M1, M2, [Head|Tail]) ->
Check = check_if_same(M1, M2),
io:fwrite("Check is: ~p\n", [Check]),
case Check of
{ok, true} ->
io:fwrite("true\n");
{ok, false} ->
io:fwrite("false\n")
end,
do_merge(M1, M2, Tail);
do_merge(M1, M2, []) ->
ok.
check_if_same(M1, M2) ->
{ok, lists:sort( maps:keys(M1) ) == lists:sort( maps:keys(M2) )}.
test() ->
merger(#{ "Bob" => #{"Sagget" => #{}} },#{ "Bob" => #{"Daniels" => #{}} }).
which gives:
8> c(merger).
merger.erl:3: Warning: export_all flag enabled - all functions will be exported
merger.erl:7: Warning: variable 'M2_Keys' is unused
merger.erl:9: Warning: variable 'Head' is unused
merger.erl:19: Warning: variable 'M1' is unused
merger.erl:19: Warning: variable 'M2' is unused
{ok,merger}
9> merger:test().
Check is: {ok,true}
true
ok
10>
Maybe you could also tell us what is the expected result of merging ManOne and ManTwo

Erlang error: no function clause matching io:request

I'm an experienced programmer new to Erlang and I'm stuck on the following:
myread() ->
{_, MyData } = file:read_file( "hands.txt" ),
io:format( "hands-out.txt", "~w", MyData ).
yields, when myread() is invoked from the shell:
** exception error: no function clause matching io:request("hands-out.txt",
{format,"~w", <<"3h 5h 7h 8h 3h 5h 7h 8h q"...>>})
(io.erl, line 556) in function io:o_request/3 (io.erl, line 63)
Any help would be appreciated.
Two things:
"hands-out.txt", "~w" needs to be one string: "hands-out.txt: ~w"
and the data that's replacing the ~w needs to be a list. So:
io:format( "hands-out.txt: ~w", [MyData] ).
See http://erlang.org/doc/man/io.html#format-2
Also, you should pattern match on the status value in the return from file:read_file/1. In your version, an error, which would be returned as {error, Reason} would match here, since you're using _, and you'd print the error reason rather than the file, which might be confusing.
So either make it {ok, MyData } = file:read_file( "hands.txt" ) if you want to crash on read error, or something like the following if you want to handle that case:
myread() ->
case file:read_file( "hands.txt" ) of
{ok, MyData } ->
io:format( "hands-out.txt: ~w", [MyData] );
{error, Error} ->
io:format("Error: ~w~n", [Error])
end.

Is there a way to tell whether a function has a matching clause without calling it in Erlang?

E.g. suppose I have a module named caller, and one of the functions defined therein contains this expression:
Callee:some_function(foo, Bar)
caller could try to catch function_clause, but how would caller know that it comes directly from Callee:some_function as opposed to some other function call (e.g. a call that Callee:some_function itself makes)?
You could catch function_clause errors with try-catch, and check if the stacktrace matches:
-module(foo).
-compile(export_all).
maybe_apply(Mod, Fun, Args) ->
try apply(Mod, Fun, Args)
catch
error:function_clause ->
case erlang:get_stacktrace() of
[{Mod, Fun, Args} | _] ->
{error, function_clause};
[{Mod, Fun, Args, _LineNumber} | _] ->
{error, function_clause};
Stacktrace ->
{error, other_function_clause, Stacktrace}
end
end.
Here is an example that shows how it distinguishes between a function clause error in lists:filter itself, and a function clause in a function called by lists:filter:
> foo:maybe_apply(lists, filter, [x, [1,2,3]]).
{error,function_clause}
> foo:maybe_apply(lists, filter, [fun(x) -> true end, [1,2,3]]).
{error,other_function_clause,
[{erl_eval,'-inside-an-interpreted-fun-',[1],[]},
{erl_eval,expr,3,[]}]}
You can use catch (callee:some_function(foo, Bar)) and analyse the error message if any:
1> catch (lists:filter(5,[1,2,3])).
{'EXIT',{function_clause,[{lists,filter,
[5,[1,2,3]],
[{file,"lists.erl"},{line,1283}]},
{erl_eval,do_apply,6,
[{file,"erl_eval.erl"},{line,661}]},
{erl_eval,expr,5,[{file,"erl_eval.erl"},{line,434}]},
{shell,exprs,7,[{file,"shell.erl"},{line,684}]},
{shell,eval_exprs,7,[{file,"shell.erl"},{line,639}]},
{shell,eval_loop,3,
[{file,"shell.erl"},{line,624}]}]}}
2> catch(lists:map(fun ({X,Y}) -> X + Y end, [{1,2},{3,4}])).
[3,7]
3> catch(lists:map(fun ({X,Y}) -> X + Y end, [{1,2},{3,4,5}])).
{'EXIT',{function_clause,[{erl_eval,'-inside-an-interpreted-fun-',
[{3,4,5}],
[]},
{erl_eval,expr,3,[]}]}}
4>
4> catch(lists:map(fun ({X,Y}) -> X / Y end, [{1,2},{3,0}])).
{'EXIT',{badarith,[{erlang,'/',[3,0],[]},
{lists,map,2,[{file,"lists.erl"},{line,1237}]},
{lists,map,2,[{file,"lists.erl"},{line,1237}]},
{erl_eval,do_apply,6,[{file,"erl_eval.erl"},{line,661}]},
{erl_eval,expr,5,[{file,"erl_eval.erl"},{line,434}]},
{shell,exprs,7,[{file,"shell.erl"},{line,684}]},
{shell,eval_exprs,7,[{file,"shell.erl"},{line,639}]},
{shell,eval_loop,3,[{file,"shell.erl"},{line,624}]}]}}
5>
you can recognize the case that you are looking for because it has the form:
{'EXIT',{function_clause,[{callee,some_function,
[foo, Bar],
[{file,"callee.erl"},{line,LineNumber}]}|Stack]}}

Erlang: supervisor:start_child/2 error has me baffled

I'm making slight modifications to Logan/Merritt/Carlson's simple cache, Chapter 6, pp 149-169, Erlang and OTP in Action. So far, no code changes, just renaming the modules.
I start the application:
application:start(gridz).
ok
I insert an item:
gridz_maker:insert(blip, blop).
I get this error:
** exception error: no match of right hand side value
{error,
{function_clause,
[{gridz_edit,init,
[{blop,86400}],
[{file,"src/gridz_edit.erl"},{line,51}]},
{gen_server,init_it,6,
[{file,"gen_server.erl"},{line,304}]},
{proc_lib,init_p_do_apply,3,
[{file,"proc_lib.erl"},{line,227}]}]}}
in function gridz_maker:insert/2 (src/gridz_maker.erl, line 15)
Here's the code:
insert(Key, Value) ->
case gridz_store:lookup(Key) of
{ok, Pid} -> gridz_edit:replace(Pid, Value);
{error, _} -> {ok, Pid} = gridz_edit:create(Value), %% line 15
gridz_store:insert(Key, Pid)
end.
I look at line 15:
{error, _} -> {ok, Pid} = gridz_edit:create(Value),
I expect the error because this is a new item. gridz:edit is a gen_server (sc_element in Logan et/al.) Here's the code for create/1:
create(Value) ->
create(Value, ?DEFAULT_LEASE_TIME).
create(Value, LeaseTime) ->
gridz_sup:start_child(Value, LeaseTime).
And here's the code for gridz_sup:start_child/2:
start_child(Value, LeaseTime) ->
supervisor:start_child(?SERVER, [Value, LeaseTime]).
init([]) ->
Grid = {gridz_edit, {gridz_edit, start_link, []},
temporary, brutal_kill, worker, [gridz_edit]},
Children = [Grid],
RestartStrategy = {simple_one_for_one, 0, 1},
{ok, {RestartStrategy, Children}}.
If I execute supervisor:start_child/2 directly, here's what I get:
{error,{function_clause,[{gridz_edit,init,
[{blop,50400}],
[{file,"src/gridz_edit.erl"},{line,51}]},
{gen_server,init_it,6,
[{file,"gen_server.erl"},{line,304}]},
{proc_lib,init_p_do_apply,3,
[{file,"proc_lib.erl"},{line,227}]}]}}
Line 51 in gridz_edit is an init function:
init([Value, LeaseTime]) ->
Now = calendar:local_time(),
StartTime = calendar:datetime_to_gregorian_seconds(Now),
{ok,
#state{value = Value,
lease_time = LeaseTime,
start_time = StartTime},
time_left(StartTime, LeaseTime)}.
If I execute it directly, it works:
120> gridz_edit:init([blop, (60 * 60 * 24)]).
{ok,{state,blop,86400,63537666408},86400000}
So now I'm baffled. What am I missing? Why does supervisor:start_child/2 throw an error?
Thanks,
LRP
The error says you are passing in a tuple with 2 members, {blop,86400}, when you seem to be expecting a list of 2 members: [Value, LeaseTime]. In your direct execution, you are also using a list, so it works. You should figure out where the tuple is being created, and create a list instead.

why process_flag trap exit not work here?

When I try to use "process_flag" for catching children's error report, The log shows trap_exit doesn't work. The problem make trouble for me for 20 hours.
The following log doesn't contain my trap log which should be shown here, like " lager:info("loop_1_0,~p,~p",[From,Reason]); %%<---should show this line."
(emacs#yus-iMac.local)20> 05:18:26.048 [info] test_a_1
05:18:26.249 [info] levi_simulate_init_1,false
05:18:26.250 [error] gen_server pid_simulate_reader_user terminated with reason: no match of right hand value 3 in levi_simulate:handle_call/3 line 471
05:18:26.250 [error] CRASH REPORT Process pid_simulate_reader_user with 0 neighbours exited with reason: no match of right hand value 3 in levi_simulate:handle_call/3 line 471 in gen_server:terminate/6 line 747
==The following is my example codes:
1. `levi_simulate_tests.erl` main content
error_test_a()->
close_server(?PID_SIMULATE_READER_USER),
timer:sleep(200),
lager:info("test_a_1"),
spawn_trap_exit(fun crash_test_a/0),
pass.
spawn_trap_exit(Fun)->
_Pid = spawn(fun()->
process_flag(trap_exit,true),
Fun(),
loop(),
lager:info("receive_after loop")
end).
loop()->
receive
{'EXIT',From,Reason}->
lager:info("loop_1_0,~p,~p",[From,Reason]); %%<---should show this line.
X ->
lager:info("loop_2,~p",[X]),
loop()
after 3000 ->
ok
end.
crash_test_a()->
close_server(?PID_SIMULATE_READER_USER),
timer:sleep(200),
{ok,Pid} = levi_simulate:start_link(false,?PID_SIMULATE_READER_USER,true,[]),
Id = 1,
gen_server:call(Pid,{test_only}),
ok.
close_server(Server)->
try
Pid = whereis(Server),
case is_process_alive(Pid) of
true ->
exit(Pid,shut);
false ->
ok
end
catch
_:_->
ok
end.
===
2. levi_simulate.erl main content
-module(levi_simulate).
-compile([{parse_transform, lager_transform}]).
-behaviour(gen_server).
start_link(Need_link_ui_pid,Server_name,Connection_condition,
Tag_id_list) ->
gen_server:start_link({local,Server_name},?MODULE,
[Need_link_ui_pid,Server_name,
Connection_condition,Tag_id_list], []).
init([Need_link_ui_pid,Server_name,Connection_condition,Tag_id_list]) ->
case Need_link_ui_pid of
true ->
true = erlang:link(whereis(?PID_UI));
false ->
lager:info("levi_simulate_init_1,false"),
ok
end,
%% A = 2,
%% A = 3,
ok = levi_tag:init(Tag_id_list),
{ok, #state{connection_condition=Connection_condition,
pid_symbol = Server_name}}.
handle_call(Request, From, State) ->
A = 2,
A = 3, %% <------ create exit here
{reply,ok,State}.
One reason could be that when you generate an error in the handle_call/3 it will cause the gen_server:call/2 to generate an exception and crash the calling process as well. In which you will never enter the loop/0 function. And easy way to test this would be to replace the gen_server call with
catch gen_server:call(Pid,{test_only}),
and see what happens.

Resources