diff --git a/libs/estdlib/src/gen_server.erl b/libs/estdlib/src/gen_server.erl index 022f86154..f8de330d0 100644 --- a/libs/estdlib/src/gen_server.erl +++ b/libs/estdlib/src/gen_server.erl @@ -68,29 +68,36 @@ -type init_result(StateType) :: {ok, State :: StateType} - | {ok, State :: StateType, timeout()} + | {ok, State :: StateType, timeout() | {continue, term()}} | {stop, Reason :: any()}. +-type handle_continue_result(StateType) :: + {noreply, NewState :: StateType} + | {noreply, NewState :: StateType, timeout() | {continue, term()}} + | {stop, Reason :: term(), NewState :: StateType}. + -type handle_call_result(StateType) :: {reply, Reply :: any(), NewState :: StateType} - | {reply, Reply :: any(), NewState :: StateType, timeout()} + | {reply, Reply :: any(), NewState :: StateType, timeout() | {continue, term()}} | {noreply, NewState :: StateType} - | {noreply, NewState :: StateType, timeout()} + | {noreply, NewState :: StateType, timeout() | {continue, term()}} | {stop, Reason :: any(), Reply :: any(), NewState :: StateType} | {stop, Reason :: any(), NewState :: StateType}. -type handle_cast_result(StateType) :: {noreply, NewState :: StateType} - | {noreply, NewState :: StateType, timeout()} + | {noreply, NewState :: StateType, timeout() | {continue, term()}} | {stop, Reason :: any(), NewState :: StateType}. -type handle_info(StateType) :: {noreply, NewState :: StateType} - | {noreply, NewState :: StateType, timeout()} + | {noreply, NewState :: StateType, timeout() | {continue, term()}} | {stop, Reason :: any(), NewState :: StateType}. -callback init(Args :: any()) -> init_result(any()). +-callback handle_continue(Continue :: term(), State :: StateType) -> + handle_continue_result(StateType). -callback handle_call(Request :: any(), From :: {pid(), Tag :: any()}, State :: StateType) -> handle_call_result(StateType). -callback handle_cast(Request :: any(), State :: StateType) -> @@ -154,6 +161,16 @@ init_it(Starter, Module, Args, Options) -> }, infinity }; + {ok, ModState, {continue, NewContinue}} -> + init_ack(Starter, ok), + { + #state{ + name = proplists:get_value(name, Options), + mod = Module, + mod_state = ModState + }, + {continue, NewContinue} + }; {ok, ModState, InitTimeout} -> init_ack(Starter, ok), { @@ -184,6 +201,7 @@ init_it(Starter, Module, Args, Options) -> end, case StateT of undefined -> ok; + {State, {continue, Continue}} -> loop(State, {continue, Continue}); {State, Timeout} -> loop(State, Timeout) end. @@ -434,6 +452,15 @@ reply({Pid, Ref}, Reply) -> %% %% @private +loop(#state{mod = Mod, mod_state = ModState} = State, {continue, Continue}) -> + case Mod:handle_continue(Continue, ModState) of + {noreply, NewModState} -> + loop(State#state{mod_state = NewModState}, infinity); + {noreply, NewModState, {continue, NewContinue}} -> + loop(State#state{mod_state = NewModState}, {continue, NewContinue}); + {stop, Reason, NewModState} -> + do_terminate(State, Reason, NewModState) + end; loop(#state{mod = Mod, mod_state = ModState} = State, Timeout) -> receive {'$call', {_Pid, _Ref} = From, Request} -> @@ -441,11 +468,16 @@ loop(#state{mod = Mod, mod_state = ModState} = State, Timeout) -> {reply, Reply, NewModState} -> ok = reply(From, Reply), loop(State#state{mod_state = NewModState}, infinity); + {reply, Reply, NewModState, {continue, Continue}} -> + ok = reply(From, Reply), + loop(State#state{mod_state = NewModState}, {continue, Continue}); {reply, Reply, NewModState, NewTimeout} -> ok = reply(From, Reply), loop(State#state{mod_state = NewModState}, NewTimeout); {noreply, NewModState} -> loop(State#state{mod_state = NewModState}, infinity); + {noreply, NewModState, {continue, Continue}} -> + loop(State#state{mod_state = NewModState}, {continue, Continue}); {noreply, NewModState, NewTimeout} -> loop(State#state{mod_state = NewModState}, NewTimeout); {stop, Reason, Reply, NewModState} -> @@ -460,6 +492,8 @@ loop(#state{mod = Mod, mod_state = ModState} = State, Timeout) -> case Mod:handle_cast(Request, ModState) of {noreply, NewModState} -> loop(State#state{mod_state = NewModState}, infinity); + {noreply, NewModState, {continue, Continue}} -> + loop(State#state{mod_state = NewModState}, {continue, Continue}); {noreply, NewModState, NewTimeout} -> loop(State#state{mod_state = NewModState}, NewTimeout); {stop, Reason, NewModState} -> diff --git a/tests/libs/estdlib/test_gen_server.erl b/tests/libs/estdlib/test_gen_server.erl index bf88d1b16..dd216b509 100644 --- a/tests/libs/estdlib/test_gen_server.erl +++ b/tests/libs/estdlib/test_gen_server.erl @@ -21,7 +21,7 @@ -module(test_gen_server). -export([test/0]). --export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2]). +-export([init/1, handle_continue/2, handle_call/3, handle_cast/2, handle_info/2, terminate/2]). -record(state, { num_casts = 0, @@ -36,6 +36,7 @@ test() -> ok = test_cast(), ok = test_info(), ok = test_start_link(), + ok = test_continue(), ok = test_init_exception(), ok = test_late_reply(), ok = test_concurrent_clients(), @@ -77,6 +78,50 @@ test_start_link() -> true = erlang:process_flag(trap_exit, false), ok. +test_continue() -> + {ok, Pid} = gen_server:start_link(?MODULE, {continue, self()}, []), + [{Pid, continue}, {Pid, after_continue}] = read_replies(Pid), + + gen_server:call(Pid, {continue_reply, self()}), + [{Pid, continue}, {Pid, after_continue}] = read_replies(Pid), + + gen_server:call(Pid, {continue_noreply, self()}), + [{Pid, continue}, {Pid, after_continue}] = read_replies(Pid), + + gen_server:cast(Pid, {continue_noreply, self()}), + [{Pid, continue}, {Pid, after_continue}] = read_replies(Pid), + + Pid ! {continue_noreply, self()}, + [{Pid, continue}, {Pid, after_continue}] = read_replies(Pid), + + Pid ! {continue_continue, self()}, + [{Pid, before_continue}, {Pid, continue}, {Pid, after_continue}] = read_replies(Pid), + + Ref = monitor(process, Pid), + Pid ! continue_stop, + verify_down_reason(Ref, Pid, normal). + +read_replies(Pid) -> + receive + {Pid, ack} -> read_replies() + after 1000 -> + error + end. + +read_replies() -> + receive + Msg -> [Msg | read_replies()] + after 0 -> [] + end. + +verify_down_reason(MRef, Server, Reason) -> + receive + {'DOWN', MRef, process, Server, Reason} -> + ok + after 5000 -> + error + end. + test_cast() -> {ok, Pid} = gen_server:start(?MODULE, [], []), @@ -353,11 +398,35 @@ test_stop_noproc() -> init(throwme) -> throw(throwme); +init({continue, Pid}) -> + io:format("init(continue) -> ~p~n", [Pid]), + self() ! {after_continue, Pid}, + {ok, [], {continue, {message, Pid}}}; init(_) -> {ok, #state{}}. +handle_continue({continue, Pid}, State) -> + Pid ! {self(), before_continue}, + self() ! {after_continue, Pid}, + {noreply, State, {continue, {message, Pid}}}; +handle_continue(stop, State) -> + {stop, normal, State}; +handle_continue({message, Pid}, State) -> + Pid ! {self(), continue}, + {noreply, State}; +handle_continue({message, Pid, From}, State) -> + Pid ! {self(), continue}, + gen_server:reply(From, ok), + {noreply, State}. + handle_call(ping, _From, State) -> {reply, pong, State}; +handle_call({continue_reply, Pid}, _From, State) -> + self() ! {after_continue, Pid}, + {reply, ok, State, {continue, {message, Pid}}}; +handle_call({continue_noreply, Pid}, From, State) -> + self() ! {after_continue, Pid}, + {noreply, State, {continue, {message, Pid, From}}}; handle_call(reply_ping, From, State) -> gen_server:reply(From, pong), {noreply, State}; @@ -392,6 +461,9 @@ handle_call(crash_me, _From, State) -> handle_call(crash_in_terminate, _From, State) -> {reply, ok, State#state{crash_in_terminate = true}}. +handle_cast({continue_noreply, Pid}, State) -> + self() ! {after_continue, Pid}, + {noreply, State, {continue, {message, Pid}}}; handle_cast(crash, _State) -> throw(test_crash); handle_cast(ping, #state{num_casts = NumCasts} = State) -> @@ -403,6 +475,17 @@ handle_cast({set_info_timeout, Timeout}, State) -> handle_cast(_Request, State) -> {noreply, State}. +handle_info({after_continue, Pid}, State) -> + Pid ! {self(), after_continue}, + Pid ! {self(), ack}, + {noreply, State}; +handle_info(continue_stop, State) -> + {noreply, State, {continue, stop}}; +handle_info({continue_noreply, Pid}, State) -> + self() ! {after_continue, Pid}, + {noreply, State, {continue, {message, Pid}}}; +handle_info({continue_continue, Pid}, State) -> + {noreply, State, {continue, {continue, Pid}}}; handle_info(ping, #state{num_infos = NumInfos, info_timeout = InfoTimeout} = State) -> NewState = State#state{num_infos = NumInfos + 1}, case InfoTimeout of