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 %%