Skip to content

Commit

Permalink
Merge pull request #1283 from bettio/gen_server-start_monitor
Browse files Browse the repository at this point in the history
Add support for `gen_server:start_monitor/3,4`

Similar to start_link but with `monitor` option.
Code for all "start with name" functions has been refactored to avoid
duplication.

These changes are made under both the "Apache 2.0" and the "GNU Lesser General
Public License 2.1 or later" license terms (dual license).

SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
  • Loading branch information
bettio committed Sep 24, 2024
2 parents 0c598ab + 11fbf91 commit 678fcd8
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 16 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
79 changes: 63 additions & 16 deletions libs/estdlib/src/gen_server.erl
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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.

Expand All @@ -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 ->
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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.
%%
%% <em><b>Note.</b> The Options argument is currently ignored.</em>
%% @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.
%%
%% <em><b>Note.</b> The Options argument is currently ignored.</em>
%% @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.
Expand Down
27 changes: 27 additions & 0 deletions tests/libs/estdlib/test_gen_server.erl
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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
%%
Expand Down

0 comments on commit 678fcd8

Please sign in to comment.