Skip to content

Commit

Permalink
Implement erlang:exit/2
Browse files Browse the repository at this point in the history
Also add documentation for `erlang:exit/1`
Fix a bug where exit reason was incorrectly a tuple for exception of class exit
No longer dump crash logs when exit reason is normal

Signed-off-by: Paul Guyot <[email protected]>
  • Loading branch information
pguyot committed Aug 26, 2023
1 parent b38d161 commit 18f2aaa
Show file tree
Hide file tree
Showing 11 changed files with 557 additions and 8 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@ functions that default to `?ATOMVM_NVS_NS` are deprecated now).
- Added `unicode` module with `characters_to_list/1,2` and `characters_to_binary/1,2,3` functions
- Added support for `crypto:hash/2` (ESP32 and generic_unix with openssl)
- Added erlang:spawn_link/1,3
- Added erlang:exit/2
- Added links to process_info/2
- Added many missing documentation and specifications for available nifs

### Fixed
- Fixed issue with formatting integers with io:format() on STM32 platform
Expand All @@ -76,6 +78,8 @@ functions that default to `?ATOMVM_NVS_NS` are deprecated now).
- Fixed numerous bugs in memory allocations that could crash the VM
- Fixed SNTP support that had been broken in IDF 4.x builds
- Fixed `erlang:send/2` not sending to registered name
- Fixed incorrect exit reason for exceptions of class exit
- Fixed several incorrect type specifications

### Breaking Changes

Expand Down
49 changes: 49 additions & 0 deletions libs/estdlib/src/erlang.erl
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@
monitor/2,
demonitor/1,
demonitor/2,
exit/1,
exit/2,
open_port/2,
system_time/1,
group_leader/0,
Expand Down Expand Up @@ -929,6 +931,53 @@ demonitor(_Monitor) ->
demonitor(_Monitor, _Options) ->
erlang:nif_error(undefined).

%%-----------------------------------------------------------------------------
%% @param Reason reason for exit
%% @doc Raises an exception of class `exit' with reason `Reason'.
%% The exception can be caught. If it is not, the process exits.
%% If the exception is not caught the signal is sent to linked processes.
%% In this case, if `Reason' is `kill', it is not transformed into `killed' and
%% linked processes can trap it (unlike `exit/2').
%% @end
%%-----------------------------------------------------------------------------
-spec exit(Reason :: any()) -> no_return().
exit(_Reason) ->
erlang:nif_error(undefined).

%%-----------------------------------------------------------------------------
%% @param Process target process
%% @param Reason reason for exit
%% @returns `true'
%% @doc Send an exit signal to target process.
%% The consequences of the exit signal depends on `Reason', on whether
%% `Process' is self() or another process and whether target process is
%% trapping exit.
%% If `Reason' is not `kill' nor `normal':
%% <ul>
%% <li>If target process is not trapping exits, it exits with `Reason'</li>
%% <li>If traget process is trapping exits, it receives a message
%% ``{'EXIT', From, Reason}'' where `From' is the caller of `exit/2'.</li>
%% </ul>
%% If `Reason' is `kill', the target process exits with `Reason' changed to
%% `killed'.
%% If `Reason' is `normal' and `Process' is not `self()':
%% <ul>
%% <li>If target process is not trapping exits, nothing happens.</li>
%% <li>If traget process is trapping exits, it receives a message
%% ``{'EXIT', From, normal}'' where `From' is the caller of `exit/2'.</li>
%% </ul>
%% If `Reason' is `normal' and `Process' is `self()':
%% <ul>
%% <li>If target process is not trapping exits, it exits with `normal'.</li>
%% <li>If traget process is trapping exits, it receives a message
%% ``{'EXIT', From, normal}'' where `From' is the caller of `exit/2'.</li>
%% </ul>
%% @end
%%-----------------------------------------------------------------------------
-spec exit(Process :: pid(), Reason :: any()) -> true.
exit(_Process, _Reason) ->
erlang:nif_error(undefined).

%%-----------------------------------------------------------------------------
%% @param PortName Tuple {spawn, Name} identifying the port
%% @param Options Options, meaningful for the port
Expand Down
4 changes: 4 additions & 0 deletions src/libAtomVM/defaultatoms.c
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@ static const char *const exports_atom = "\x7" "exports";

static const char *const incomplete_atom = "\xA" "incomplete";

static const char *const kill_atom = "\x4" "kill";
static const char *const killed_atom = "\x6" "killed";
static const char *const links_atom = "\x5" "links";

void defaultatoms_init(GlobalContext *glb)
Expand Down Expand Up @@ -276,6 +278,8 @@ void defaultatoms_init(GlobalContext *glb)

ok &= globalcontext_insert_atom(glb, incomplete_atom) == INCOMPLETE_ATOM_INDEX;

ok &= globalcontext_insert_atom(glb, kill_atom) == KILL_ATOM_INDEX;
ok &= globalcontext_insert_atom(glb, killed_atom) == KILLED_ATOM_INDEX;
ok &= globalcontext_insert_atom(glb, links_atom) == LINKS_ATOM_INDEX;

if (!ok) {
Expand Down
8 changes: 6 additions & 2 deletions src/libAtomVM/defaultatoms.h
Original file line number Diff line number Diff line change
Expand Up @@ -154,9 +154,11 @@ extern "C" {

#define INCOMPLETE_ATOM_INDEX 99

#define LINKS_ATOM_INDEX 100
#define KILL_ATOM_INDEX 100
#define KILLED_ATOM_INDEX 101
#define LINKS_ATOM_INDEX 102

#define PLATFORM_ATOMS_BASE_INDEX 101
#define PLATFORM_ATOMS_BASE_INDEX 103

#define FALSE_ATOM TERM_FROM_ATOM_INDEX(FALSE_ATOM_INDEX)
#define TRUE_ATOM TERM_FROM_ATOM_INDEX(TRUE_ATOM_INDEX)
Expand Down Expand Up @@ -285,6 +287,8 @@ extern "C" {

#define INCOMPLETE_ATOM TERM_FROM_ATOM_INDEX(INCOMPLETE_ATOM_INDEX)

#define KILL_ATOM TERM_FROM_ATOM_INDEX(KILL_ATOM_INDEX)
#define KILLED_ATOM TERM_FROM_ATOM_INDEX(KILLED_ATOM_INDEX)
#define LINKS_ATOM TERM_FROM_ATOM_INDEX(LINKS_ATOM_INDEX)

void defaultatoms_init(GlobalContext *glb);
Expand Down
46 changes: 42 additions & 4 deletions src/libAtomVM/nifs.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
#include "defaultatoms.h"
#include "dictionary.h"
#include "externalterm.h"
#include "globalcontext.h"
#include "interop.h"
#include "mailbox.h"
#include "module.h"
Expand Down Expand Up @@ -3132,11 +3133,48 @@ static term nif_erlang_error(Context *ctx, int argc, term argv[])

static term nif_erlang_exit(Context *ctx, int argc, term argv[])
{
UNUSED(argc);
if (argc == 1) {
term reason = argv[0];
RAISE(LOWERCASE_EXIT_ATOM, reason);
} else {
term target_process = argv[0];
VALIDATE_VALUE(target_process, term_is_pid);
term reason = argv[1];
GlobalContext *glb = ctx->global;
Context *target = globalcontext_get_process_lock(glb, term_to_local_process_id(target_process));
bool self_is_signaled = false;
if (LIKELY(target)) {
if (reason == KILL_ATOM) {
mailbox_send_term_signal(target, KillSignal, KILLED_ATOM);
self_is_signaled = target == ctx;
} else {
if (target->trap_exit) {
if (UNLIKELY(memory_ensure_free(ctx, TUPLE_SIZE(3)) != MEMORY_GC_OK)) {
globalcontext_get_process_unlock(glb, target);
RAISE_ERROR(OUT_OF_MEMORY_ATOM);
}

term reason = argv[0];
ctx->exit_reason = reason;
RAISE(LOWERCASE_EXIT_ATOM, reason);
term info_tuple = term_alloc_tuple(3, &ctx->heap);
term_put_tuple_element(info_tuple, 0, EXIT_ATOM);
term_put_tuple_element(info_tuple, 1, term_from_local_process_id(ctx->process_id));
term_put_tuple_element(info_tuple, 2, reason);
mailbox_send(target, info_tuple);
} else if (ctx == target) {
mailbox_send_term_signal(target, KillSignal, reason);
self_is_signaled = target == ctx;
} else if (reason != NORMAL_ATOM){
mailbox_send_term_signal(target, KillSignal, reason);
self_is_signaled = target == ctx;
} // else there is no effect
}
globalcontext_get_process_unlock(glb, target);
}
if (self_is_signaled) {
context_update_flags(ctx, ~NoFlags, Trap);
return term_invalid_term();
}
return TRUE_ATOM;
}
}

static term nif_erlang_make_fun_3(Context *ctx, int argc, term argv[])
Expand Down
1 change: 1 addition & 0 deletions src/libAtomVM/nifs.gperf
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ erlang:erase/1, &erase_nif
erlang:error/1, &error_nif
erlang:error/2, &error_nif
erlang:exit/1, &exit_nif
erlang:exit/2, &exit_nif
erlang:display/1, &display_nif
erlang:float_to_binary/1, &float_to_binary_nif
erlang:float_to_binary/2, &float_to_binary_nif
Expand Down
10 changes: 8 additions & 2 deletions src/libAtomVM/opcodesswitch.h
Original file line number Diff line number Diff line change
Expand Up @@ -7163,9 +7163,14 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb)
}
}

dump(ctx);
// Do not print crash dump if reason is normal.
if (ctx->x[0] != LOWERCASE_EXIT_ATOM || ctx->x[1] != NORMAL_ATOM) {
dump(ctx);
}

{
if (ctx->x[0] == LOWERCASE_EXIT_ATOM) {
ctx->exit_reason = ctx->x[1];
} else {
bool throw = ctx->x[0] == THROW_ATOM;

int exit_reason_tuple_size = (throw ? TUPLE_SIZE(2) : 0) + TUPLE_SIZE(2);
Expand All @@ -7178,6 +7183,7 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb)
term_put_tuple_element(error_term, 0, NOCATCH_ATOM);
term_put_tuple_element(error_term, 1, ctx->x[1]);
} else {
// error
error_term = ctx->x[1];
}

Expand Down
4 changes: 4 additions & 0 deletions tests/erlang_tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,8 @@ compile_erlang(link_kill_parent)
compile_erlang(link_throw)
compile_erlang(unlink_error)
compile_erlang(trap_exit_flag)
compile_erlang(test_exit1)
compile_erlang(test_exit2)

compile_erlang(test_stacktrace)
compile_erlang(small_big_ext)
Expand Down Expand Up @@ -907,6 +909,8 @@ add_custom_target(erlang_test_modules DEPENDS
link_throw.beam
unlink_error.beam
trap_exit_flag.beam
test_exit1.beam
test_exit2.beam

test_stacktrace.beam

Expand Down
Loading

0 comments on commit 18f2aaa

Please sign in to comment.