Skip to content

Commit abd425e

Browse files
committed
Add gen_server support for OTP-28 timeout tuple return actions
Adds support for timeout tuples in the form of `{timeout, Time :: timeout(), InfoMessage :: any()}` in gen_server callback return `action()`. These callback return actions were introduced in OTP-28. They work similarly to `timeout()` actions, except insead of `timeout` handle_info/2 will receive the `InfoMessage`. Signed-off-by: Winford <[email protected]>
1 parent 593b3ee commit abd425e

File tree

3 files changed

+86
-19
lines changed

3 files changed

+86
-19
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6161
- Added `lists:ukeysort/2`
6262
- Added support for big integers up to 256-bit (sign + 256-bit magnitude)
6363
- Added support for big integers in `binary_to_term/1` and `term_to_binary/1,2`
64+
- Added gen_server support for timeout tuples in callback return actions introduced in OTP-28.
6465

6566
### Changed
6667

libs/estdlib/src/gen_server.erl

Lines changed: 48 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -69,30 +69,35 @@
6969

7070
-type init_result(StateType) ::
7171
{ok, State :: StateType}
72-
| {ok, State :: StateType, timeout() | {continue, term()}}
72+
| {ok, State :: StateType, timeout() | {timeout, timeout(), Msg :: any()} | {continue, term()}}
7373
| {stop, Reason :: any()}.
7474

7575
-type handle_continue_result(StateType) ::
7676
{noreply, NewState :: StateType}
77-
| {noreply, NewState :: StateType, timeout() | {continue, term()}}
77+
| {noreply, NewState :: StateType,
78+
timeout() | {timeout, timeout(), Msg :: any()} | {continue, term()}}
7879
| {stop, Reason :: term(), NewState :: StateType}.
7980

8081
-type handle_call_result(StateType) ::
8182
{reply, Reply :: any(), NewState :: StateType}
82-
| {reply, Reply :: any(), NewState :: StateType, timeout() | {continue, term()}}
83+
| {reply, Reply :: any(), NewState :: StateType,
84+
timeout() | {timeout, timeout(), Msg :: any()} | {continue, term()}}
8385
| {noreply, NewState :: StateType}
84-
| {noreply, NewState :: StateType, timeout() | {continue, term()}}
86+
| {noreply, NewState :: StateType,
87+
timeout() | {timeout, timeout(), Msg :: any()} | {continue, term()}}
8588
| {stop, Reason :: any(), Reply :: any(), NewState :: StateType}
8689
| {stop, Reason :: any(), NewState :: StateType}.
8790

8891
-type handle_cast_result(StateType) ::
8992
{noreply, NewState :: StateType}
90-
| {noreply, NewState :: StateType, timeout() | {continue, term()}}
93+
| {noreply, NewState :: StateType,
94+
timeout() | {timeout, timeout(), Msg :: any()} | {continue, term()}}
9195
| {stop, Reason :: any(), NewState :: StateType}.
9296

9397
-type handle_info(StateType) ::
9498
{noreply, NewState :: StateType}
95-
| {noreply, NewState :: StateType, timeout() | {continue, term()}}
99+
| {noreply, NewState :: StateType,
100+
timeout() | {timeout, timeout(), Msg :: any()} | {continue, term()}}
96101
| {stop, Reason :: any(), NewState :: StateType}.
97102

98103
-callback init(Args :: any()) ->
@@ -103,7 +108,7 @@
103108
handle_call_result(StateType).
104109
-callback handle_cast(Request :: any(), State :: StateType) ->
105110
handle_cast_result(StateType).
106-
-callback handle_info(Info :: timeout() | any(), State :: StateType) ->
111+
-callback handle_info(Info :: timeout() | {timeout, timeout(), any()} | any(), State :: StateType) ->
107112
handle_info(StateType).
108113
-callback terminate(Reason :: normal | any(), State :: any()) ->
109114
any().
@@ -500,11 +505,47 @@ loop(Parent, #state{mod = Mod, mod_state = ModState} = State, {continue, Continu
500505
loop(Parent, State#state{mod_state = NewModState}, infinity);
501506
{noreply, NewModState, {continue, NewContinue}} ->
502507
loop(Parent, State#state{mod_state = NewModState}, {continue, NewContinue});
508+
{noreply, NewModState, Timeout} ->
509+
loop(Parent, State#state{mod_state = NewModState}, Timeout);
503510
{stop, Reason, NewModState} ->
504511
do_terminate(State, Reason, NewModState)
505512
end;
513+
loop(Parent, #state{mod = Mod, mod_state = ModState} = State, {timeout, Timeout, Info}) ->
514+
receive
515+
Msg ->
516+
handle_msg(Msg, Parent, State)
517+
after Timeout ->
518+
case Mod:handle_info(Info, ModState) of
519+
{noreply, NewModState} ->
520+
loop(Parent, State#state{mod_state = NewModState}, infinity);
521+
{noreply, NewModState, NewTimeout} ->
522+
loop(Parent, State#state{mod_state = NewModState}, NewTimeout);
523+
{stop, Reason, NewModState} ->
524+
do_terminate(State, Reason, NewModState);
525+
_ ->
526+
do_terminate(State, {error, unexpected_reply}, ModState)
527+
end
528+
end;
506529
loop(Parent, #state{mod = Mod, mod_state = ModState} = State, Timeout) ->
507530
receive
531+
Msg ->
532+
handle_msg(Msg, Parent, State)
533+
after Timeout ->
534+
case Mod:handle_info(timeout, ModState) of
535+
{noreply, NewModState} ->
536+
loop(Parent, State#state{mod_state = NewModState}, infinity);
537+
{noreply, NewModState, NewTimeout} ->
538+
loop(Parent, State#state{mod_state = NewModState}, NewTimeout);
539+
{stop, Reason, NewModState} ->
540+
do_terminate(State, Reason, NewModState);
541+
_ ->
542+
do_terminate(State, {error, unexpected_reply}, ModState)
543+
end
544+
end.
545+
546+
%% @private
547+
handle_msg(Msg, Parent, #state{mod = Mod, mod_state = ModState} = State) ->
548+
case Msg of
508549
{'$gen_call', {_Pid, _Ref} = From, Request} ->
509550
case Mod:handle_call(Request, From, ModState) of
510551
{reply, Reply, NewModState} ->
@@ -558,20 +599,8 @@ loop(Parent, #state{mod = Mod, mod_state = ModState} = State, Timeout) ->
558599
_ ->
559600
do_terminate(State, {error, unexpected_reply}, ModState)
560601
end
561-
after Timeout ->
562-
case Mod:handle_info(timeout, ModState) of
563-
{noreply, NewModState} ->
564-
loop(Parent, State#state{mod_state = NewModState}, infinity);
565-
{noreply, NewModState, NewTimeout} ->
566-
loop(Parent, State#state{mod_state = NewModState}, NewTimeout);
567-
{stop, Reason, NewModState} ->
568-
do_terminate(State, Reason, NewModState);
569-
_ ->
570-
do_terminate(State, {error, unexpected_reply}, ModState)
571-
end
572602
end.
573603

574-
%% @private
575604
do_terminate(#state{mod = Mod} = _State, Reason, ModState) ->
576605
case erlang:function_exported(Mod, terminate, 2) of
577606
true ->

tests/libs/estdlib/test_gen_server.erl

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ test() ->
4444
ok = test_timeout_call(),
4545
ok = test_timeout_cast(),
4646
ok = test_timeout_info(),
47+
ok = test_timeout_tuple_return(),
4748
ok = test_register(),
4849
ok = test_call_unregistered(),
4950
ok = test_cast_unregistered(),
@@ -316,6 +317,29 @@ test_timeout_info_repeats(Pid, Sleep) ->
316317
test_timeout_info_repeats(Pid, 2 * Sleep)
317318
end.
318319

320+
test_timeout_tuple_return() ->
321+
case get_otp_version() of
322+
Version when Version =:= atomvm orelse (is_integer(Version) andalso Version >= 28) ->
323+
{ok, Pid} = gen_server:start(?MODULE, [], []),
324+
gen_server:cast(Pid, {req_timeout_reply, self()}),
325+
ok =
326+
receive
327+
reply_from_timeout ->
328+
ok
329+
after 2000 ->
330+
{error, no_reply_after_timeout}
331+
end,
332+
333+
0 = gen_server:call(Pid, get_num_timeouts),
334+
ok = gen_server:cast(Pid, {tuple_timeout, 10}),
335+
timer:sleep(100),
336+
10 = gen_server:call(Pid, get_num_timeouts),
337+
338+
gen_server:stop(Pid);
339+
_ ->
340+
ok
341+
end.
342+
319343
test_register() ->
320344
{ok, Pid} = gen_server:start({local, ?MODULE}, ?MODULE, [], []),
321345
Pid = whereis(?MODULE),
@@ -504,8 +528,13 @@ handle_cast(ping, #state{num_casts = NumCasts} = State) ->
504528
{noreply, State#state{num_casts = NumCasts + 1}};
505529
handle_cast({cast_timeout, Timeout}, State) ->
506530
{noreply, State, Timeout};
531+
handle_cast({tuple_timeout, Timeouts}, State) ->
532+
{noreply, State, {timeout, 1, {do_tuple_timeouts, Timeouts}}};
507533
handle_cast({set_info_timeout, Timeout}, State) ->
508534
{noreply, State#state{info_timeout = Timeout}};
535+
handle_cast({req_timeout_reply, From}, State) ->
536+
Info = {timeout_reply, From},
537+
{noreply, State, {timeout, 0, Info}};
509538
handle_cast(_Request, State) ->
510539
{noreply, State}.
511540

@@ -536,6 +565,14 @@ handle_info(timeout, #state{num_timeouts = NumTimeouts, info_timeout = InfoTimeo
536565
Other ->
537566
{noreply, NewState, Other}
538567
end;
568+
handle_info({timeout_reply, From} = _Info, State) ->
569+
From ! reply_from_timeout,
570+
{noreply, State};
571+
handle_info({do_tuple_timeouts, 0}, State) ->
572+
{noreply, State};
573+
handle_info({do_tuple_timeouts, Timeouts}, #state{num_timeouts = TimeoutCt} = State) ->
574+
{noreply, State#state{num_timeouts = TimeoutCt + 1},
575+
{timeout, 1, {do_tuple_timeouts, Timeouts - 1}}};
539576
handle_info(_Info, #state{info_timeout = InfoTimeout} = State) ->
540577
case InfoTimeout of
541578
none ->

0 commit comments

Comments
 (0)