diff --git a/CHANGELOG.md b/CHANGELOG.md
index da99ca8d2..9f84fc057 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -23,6 +23,7 @@ also non string parameters (e.g. `Enum.join([1, 2], ",")`
- Add support to Elixir for `Process.send/2` `Process.send_after/3/4` and `Process.cancel_timer/1`
- Add support for `handle_continue` callback in `gen_server`
- Support for Elixir `List.Chars` protocol
+- Support for `gen_server:start_monitor/3,4`
### Changed
diff --git a/libs/estdlib/src/gen_server.erl b/libs/estdlib/src/gen_server.erl
index f8de330d0..067eab8bc 100644
--- a/libs/estdlib/src/gen_server.erl
+++ b/libs/estdlib/src/gen_server.erl
@@ -43,6 +43,7 @@
-export([
start/3, start/4,
start_link/3, start_link/4,
+ start_monitor/3, start_monitor/4,
stop/1, stop/3,
call/2, call/3,
cast/2,
@@ -109,9 +110,9 @@
%% @private
do_spawn(Module, Args, Options, SpawnOpts) ->
- Pid = spawn_opt(?MODULE, init_it, [self(), Module, Args, Options], SpawnOpts),
- case wait_ack(Pid) of
- ok -> {ok, Pid};
+ PidOrMonRet = spawn_opt(?MODULE, init_it, [self(), Module, Args, Options], SpawnOpts),
+ case wait_ack(PidOrMonRet) of
+ ok -> {ok, PidOrMonRet};
{error, Reason} -> {error, Reason}
end.
@@ -123,6 +124,15 @@ do_spawn(Name, Module, Args, Options, SpawnOpts) ->
{error, Reason} -> {error, Reason}
end.
+%% @private
+spawn_if_not_registered(Name, Module, Args, Options, SpawnOpts) ->
+ case erlang:whereis(Name) of
+ undefined ->
+ do_spawn(Name, Module, Args, [{name, Name} | Options], SpawnOpts);
+ Pid ->
+ {error, {already_started, Pid}}
+ end.
+
init_it(Starter, Name, Module, Args, Options) ->
try erlang:register(Name, self()) of
true ->
@@ -209,7 +219,11 @@ init_ack(Parent, Return) ->
Parent ! {ack, self(), Return},
ok.
-wait_ack(Pid) ->
+wait_ack(Pid) when is_pid(Pid) ->
+ receive
+ {ack, Pid, Return} -> Return
+ end;
+wait_ack({Pid, _MonRef}) when is_pid(Pid) ->
receive
{ack, Pid, Return} -> Return
end.
@@ -246,12 +260,7 @@ crash_report(ErrStr, Parent, E, S) ->
Options :: options()
) -> {ok, pid()} | {error, Reason :: term()}.
start({local, Name}, Module, Args, Options) when is_atom(Name) ->
- case erlang:whereis(Name) of
- undefined ->
- do_spawn(Name, Module, Args, [{name, Name} | Options], []);
- Pid ->
- {error, {already_started, Pid}}
- end.
+ spawn_if_not_registered(Name, Module, Args, Options, []).
%%-----------------------------------------------------------------------------
%% @param Module the module in which the gen_server callbacks are defined
@@ -292,12 +301,7 @@ start(Module, Args, Options) ->
Options :: options()
) -> {ok, pid()} | {error, Reason :: term()}.
start_link({local, Name}, Module, Args, Options) when is_atom(Name) ->
- case erlang:whereis(Name) of
- undefined ->
- do_spawn(Name, Module, Args, [{name, Name} | Options], [link]);
- Pid ->
- {error, {already_started, Pid}}
- end.
+ spawn_if_not_registered(Name, Module, Args, Options, [link]).
%%-----------------------------------------------------------------------------
%% @param Module the module in which the gen_server callbacks are defined
@@ -316,6 +320,49 @@ start_link({local, Name}, Module, Args, Options) when is_atom(Name) ->
start_link(Module, Args, Options) ->
do_spawn(Module, Args, Options, [link]).
+%%-----------------------------------------------------------------------------
+%% @param Module the module in which the gen_server callbacks are defined
+%% @param Args the arguments to pass to the module's init callback
+%% @param Options the options used to create the gen_server
+%% @returns the gen_server pid and monitor reference tuple if successful;
+%% {error, Reason}, otherwise.
+%% @doc Start and monitor an un-named gen_server.
+%%
+%% This function will start a gen_server instance.
+%%
+%% Note. The Options argument is currently ignored.
+%% @end
+%%-----------------------------------------------------------------------------
+-spec start_monitor(Module :: module(), Args :: term(), Options :: options()) ->
+ {ok, {Pid :: pid(), MonRef :: reference()}} | {error, Reason :: term()}.
+start_monitor(Module, Args, Options) ->
+ do_spawn(Module, Args, Options, [monitor]).
+
+%%-----------------------------------------------------------------------------
+%% @param ServerName the name with which to register the gen_server
+%% @param Module the module in which the gen_server callbacks are defined
+%% @param Args the arguments to pass to the module's init callback
+%% @param Options the options used to create the gen_server
+%% @returns the gen_server pid and monitor reference tuple if successful;
+%% {error, Reason}, otherwise.
+%% @doc Start and monitor a named gen_server.
+%%
+%% This function will start a gen_server instance and register the
+%% newly created process with the process registry. Subsequent calls
+%% may use the gen_server name, in lieu of the process id.
+%%
+%% Note. The Options argument is currently ignored.
+%% @end
+%%-----------------------------------------------------------------------------
+-spec start_monitor(
+ ServerName :: {local, Name :: atom()},
+ Module :: module(),
+ Args :: term(),
+ Options :: options()
+) -> {ok, {Pid :: pid(), MonRef :: reference()}} | {error, Reason :: term()}.
+start_monitor({local, Name}, Module, Args, Options) when is_atom(Name) ->
+ spawn_if_not_registered(Name, Module, Args, Options, [monitor]).
+
%%-----------------------------------------------------------------------------
%% @equiv stop(ServerRef, normal, infinity)
%% @doc Stop a previously started gen_server instance.
diff --git a/tests/libs/estdlib/test_gen_server.erl b/tests/libs/estdlib/test_gen_server.erl
index dd216b509..14a6396af 100644
--- a/tests/libs/estdlib/test_gen_server.erl
+++ b/tests/libs/estdlib/test_gen_server.erl
@@ -36,6 +36,7 @@ test() ->
ok = test_cast(),
ok = test_info(),
ok = test_start_link(),
+ ok = test_start_monitor(),
ok = test_continue(),
ok = test_init_exception(),
ok = test_late_reply(),
@@ -78,6 +79,24 @@ test_start_link() ->
true = erlang:process_flag(trap_exit, false),
ok.
+test_start_monitor() ->
+ case get_otp_version() of
+ Version when Version =:= atomvm orelse (is_integer(Version) andalso Version >= 23) ->
+ {ok, {Pid, Ref}} = gen_server:start_monitor(?MODULE, [], []),
+
+ pong = gen_server:call(Pid, ping),
+ pong = gen_server:call(Pid, reply_ping),
+ ok = gen_server:cast(Pid, crash),
+ ok =
+ receive
+ {'DOWN', Ref, process, Pid, _Reason} -> ok
+ after 30000 -> timeout
+ end,
+ ok;
+ _ ->
+ ok
+ end.
+
test_continue() ->
{ok, Pid} = gen_server:start_link(?MODULE, {continue, self()}, []),
[{Pid, continue}, {Pid, after_continue}] = read_replies(Pid),
@@ -392,6 +411,14 @@ test_stop_noproc() ->
ok
end.
+get_otp_version() ->
+ case erlang:system_info(machine) of
+ "BEAM" ->
+ list_to_integer(erlang:system_info(otp_release));
+ _ ->
+ atomvm
+ end.
+
%%
%% callbacks
%%