1

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?

Josh K
  • 79
  • 1
  • 12
  • Please, show the common test code and the crash too. Is the `trap_exit` in the CT code? – José M Jul 16 '21 at 22:02
  • @JoséM added the common test code. yes, I tried the `trap_exit` right before the `mod_bad_process:kill()`. I also tried wrapping it in a try catch – Josh K Jul 16 '21 at 22:12
  • Can you share the crash as reported by CT? Also, how are the 3 processes (test runner, monitor and BAD_PROC) linked together?. Also, it may be useful to you: you can check the links and the monitors a process has with `process_info(...[links|monitors|monitored_by])` – José M Jul 16 '21 at 22:37
  • @JoséM I updated my post to show the error and the stack trace. I'm not sure how the test runner is connected, but the monitor is monitoring mod_bad_process (as one would expect) and they both have links to unknown processes (possibly their supervisors, definitely not each other). I suppose it might be possible that the mod_bad_process gen_server is linked to the process running my test, but I think this is unlikely because I previously had code that killed the gen_server indirectly (basically by setting the bad proc's state to `kill` and waiting for it to be checked) and that worked fine. – Josh K Jul 16 '21 at 23:34
  • A `catch` in `mod_bad_process:kill` should prevent the error you are obtaining. Are you sure that the test was failing with the same error with the `catch`? (A crash event may still be logged because the monitored process terminates abnormally, but the test shouldn't be concerned about that). Considering that you only need the process to die during the call, I'd suggest using `gen_server:cast` or even better, `gen_server:stop` if possible. – José M Jul 17 '21 at 09:05
  • 1
    Your situation looks a lot like this one… https://medium.com/erlang-battleground/the-unstoppable-exception-9dfb009852f5 – Brujo Benavides Jul 19 '21 at 11:31

1 Answers1

1

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.
% ...
Josh K
  • 79
  • 1
  • 12