Skip to content

Commit dc3e74e

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 0f4a060 commit dc3e74e

File tree

3 files changed

+142
-22
lines changed

3 files changed

+142
-22
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
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`
6464
- Added `proc_lib`
65+
- Added gen_server support for timeout tuples in callback return actions introduced in OTP-28.
6566

6667
### Changed
6768

libs/estdlib/src/gen_server.erl

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -73,30 +73,35 @@
7373

7474
-type init_result(StateType) ::
7575
{ok, State :: StateType}
76-
| {ok, State :: StateType, timeout() | {continue, term()}}
76+
| {ok, State :: StateType, timeout() | {timeout, timeout(), Msg :: any()} | {continue, term()}}
7777
| {stop, Reason :: any()}.
7878

7979
-type handle_continue_result(StateType) ::
8080
{noreply, NewState :: StateType}
81-
| {noreply, NewState :: StateType, timeout() | {continue, term()}}
81+
| {noreply, NewState :: StateType,
82+
timeout() | {timeout, timeout(), Msg :: any()} | {continue, term()}}
8283
| {stop, Reason :: term(), NewState :: StateType}.
8384

8485
-type handle_call_result(StateType) ::
8586
{reply, Reply :: any(), NewState :: StateType}
86-
| {reply, Reply :: any(), NewState :: StateType, timeout() | {continue, term()}}
87+
| {reply, Reply :: any(), NewState :: StateType,
88+
timeout() | {timeout, timeout(), Msg :: any()} | {continue, term()}}
8789
| {noreply, NewState :: StateType}
88-
| {noreply, NewState :: StateType, timeout() | {continue, term()}}
90+
| {noreply, NewState :: StateType,
91+
timeout() | {timeout, timeout(), Msg :: any()} | {continue, term()}}
8992
| {stop, Reason :: any(), Reply :: any(), NewState :: StateType}
9093
| {stop, Reason :: any(), NewState :: StateType}.
9194

9295
-type handle_cast_result(StateType) ::
9396
{noreply, NewState :: StateType}
94-
| {noreply, NewState :: StateType, timeout() | {continue, term()}}
97+
| {noreply, NewState :: StateType,
98+
timeout() | {timeout, timeout(), Msg :: any()} | {continue, term()}}
9599
| {stop, Reason :: any(), NewState :: StateType}.
96100

97101
-type handle_info(StateType) ::
98102
{noreply, NewState :: StateType}
99-
| {noreply, NewState :: StateType, timeout() | {continue, term()}}
103+
| {noreply, NewState :: StateType,
104+
timeout() | {timeout, timeout(), Msg :: any()} | {continue, term()}}
100105
| {stop, Reason :: any(), NewState :: StateType}.
101106

102107
-callback init(Args :: any()) ->
@@ -107,7 +112,7 @@
107112
handle_call_result(StateType).
108113
-callback handle_cast(Request :: any(), State :: StateType) ->
109114
handle_cast_result(StateType).
110-
-callback handle_info(Info :: timeout() | any(), State :: StateType) ->
115+
-callback handle_info(Info :: timeout() | {timeout, timeout(), any()} | any(), State :: StateType) ->
111116
handle_info(StateType).
112117
-callback terminate(Reason :: normal | any(), State :: any()) ->
113118
any().
@@ -504,11 +509,29 @@ loop(Parent, #state{mod = Mod, mod_state = ModState} = State, {continue, Continu
504509
loop(Parent, State#state{mod_state = NewModState}, infinity);
505510
{noreply, NewModState, {continue, NewContinue}} ->
506511
loop(Parent, State#state{mod_state = NewModState}, {continue, NewContinue});
512+
{noreply, NewModState, Timeout} ->
513+
loop(Parent, State#state{mod_state = NewModState}, Timeout);
507514
{stop, Reason, NewModState} ->
508515
do_terminate(State, Reason, NewModState)
509516
end;
510-
loop(Parent, #state{mod = Mod, mod_state = ModState} = State, Timeout) ->
517+
loop(Parent, State, {timeout, Timeout, Info}) ->
511518
receive
519+
Msg ->
520+
handle_msg(Msg, Parent, State)
521+
after Timeout ->
522+
handle_timeout(Parent, Info, State)
523+
end;
524+
loop(Parent, State, Timeout) ->
525+
receive
526+
Msg ->
527+
handle_msg(Msg, Parent, State)
528+
after Timeout ->
529+
handle_timeout(Parent, timeout, State)
530+
end.
531+
532+
%% @private
533+
handle_msg(Msg, Parent, #state{mod = Mod, mod_state = ModState} = State) ->
534+
case Msg of
512535
{'$gen_call', {_Pid, _Ref} = From, Request} ->
513536
case Mod:handle_call(Request, From, ModState) of
514537
{reply, Reply, NewModState} ->
@@ -562,20 +585,20 @@ loop(Parent, #state{mod = Mod, mod_state = ModState} = State, Timeout) ->
562585
_ ->
563586
do_terminate(State, {error, unexpected_reply}, ModState)
564587
end
565-
after Timeout ->
566-
case Mod:handle_info(timeout, ModState) of
567-
{noreply, NewModState} ->
568-
loop(Parent, State#state{mod_state = NewModState}, infinity);
569-
{noreply, NewModState, NewTimeout} ->
570-
loop(Parent, State#state{mod_state = NewModState}, NewTimeout);
571-
{stop, Reason, NewModState} ->
572-
do_terminate(State, Reason, NewModState);
573-
_ ->
574-
do_terminate(State, {error, unexpected_reply}, ModState)
575-
end
576588
end.
577589

578-
%% @private
590+
handle_timeout(Parent, Msg, #state{mod = Mod, mod_state = ModState} = State) ->
591+
case Mod:handle_info(Msg, ModState) of
592+
{noreply, NewModState} ->
593+
loop(Parent, State#state{mod_state = NewModState}, infinity);
594+
{noreply, NewModState, NewTimeout} ->
595+
loop(Parent, State#state{mod_state = NewModState}, NewTimeout);
596+
{stop, Reason, NewModState} ->
597+
do_terminate(State, Reason, NewModState);
598+
_ ->
599+
do_terminate(State, {error, unexpected_reply}, ModState)
600+
end.
601+
579602
do_terminate(#state{mod = Mod} = _State, Reason, ModState) ->
580603
case erlang:function_exported(Mod, terminate, 2) of
581604
true ->

tests/libs/estdlib/test_gen_server.erl

Lines changed: 98 additions & 2 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,73 @@ 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+
Self = self(),
324+
%% test timeout tuple in init
325+
{ok, Pid0} = gen_server:start(?MODULE, {timeout, Self}, []),
326+
ok =
327+
receive
328+
{reply_from_timeout, init} ->
329+
ok
330+
after 1000 ->
331+
{error, no_timeout_reply_after_init}
332+
end,
333+
gen_server:stop(Pid0),
334+
335+
%% test timeout tuple in continue, and proceed through callback tests
336+
{ok, Pid1} = gen_server:start(?MODULE, {continue_timeout, Self}, []),
337+
ok =
338+
receive
339+
{reply_from_timeout, continue} ->
340+
ok
341+
after 1000 ->
342+
{error, no_timeout_reply_after_continue}
343+
end,
344+
345+
%% Test handle_call
346+
{ok, Ref} = gen_server:call(Pid1, {req_timeout_reply, Self}),
347+
ok =
348+
receive
349+
{reply_from_timeout, Ref} ->
350+
ok
351+
after 2000 ->
352+
{error, no_timeout_reply_after_call}
353+
end,
354+
355+
%% Test handle_cast
356+
gen_server:cast(Pid1, {req_timeout_reply, Self}),
357+
ok =
358+
receive
359+
{reply_from_timeout, cast} ->
360+
ok
361+
after 2000 ->
362+
{error, no_timeout_reply_after_cast}
363+
end,
364+
365+
%% Test handle_info
366+
gen_server:cast(Pid1, {request_info_timeout, Self}),
367+
ok =
368+
receive
369+
{reply_from_timeout, info} ->
370+
ok
371+
after 2000 ->
372+
{error, no_info_timeout_reply}
373+
end,
374+
gen_server:stop(Pid1),
375+
376+
{ok, Pid2} = gen_server:start(?MODULE, [], []),
377+
0 = gen_server:call(Pid2, get_num_timeouts),
378+
ok = gen_server:cast(Pid2, {tuple_timeout, 10}),
379+
timer:sleep(100),
380+
10 = gen_server:call(Pid2, get_num_timeouts),
381+
382+
gen_server:stop(Pid2);
383+
_ ->
384+
ok
385+
end.
386+
319387
test_register() ->
320388
{ok, Pid} = gen_server:start({local, ?MODULE}, ?MODULE, [], []),
321389
Pid = whereis(?MODULE),
@@ -436,6 +504,10 @@ init({continue, Pid}) ->
436504
io:format("init(continue) -> ~p~n", [Pid]),
437505
self() ! {after_continue, Pid},
438506
{ok, [], {continue, {message, Pid}}};
507+
init({timeout, Pid}) ->
508+
{ok, [], {timeout, 1, {timeout_reply, Pid, init}}};
509+
init({continue_timeout, Pid}) ->
510+
{ok, [], {continue, {timeout_reply, Pid}}};
439511
init(_) ->
440512
{ok, #state{}}.
441513

@@ -451,7 +523,9 @@ handle_continue({message, Pid}, State) ->
451523
handle_continue({message, Pid, From}, State) ->
452524
Pid ! {self(), continue},
453525
gen_server:reply(From, ok),
454-
{noreply, State}.
526+
{noreply, State};
527+
handle_continue({timeout_reply, Pid}, State) ->
528+
{noreply, State, {timeout, 1, {timeout_reply, Pid, continue}}}.
455529

456530
handle_call(ping, _From, State) ->
457531
{reply, pong, State};
@@ -493,7 +567,10 @@ handle_call(crash_me, _From, State) ->
493567
exit(crash_me),
494568
{reply, noop, State};
495569
handle_call(crash_in_terminate, _From, State) ->
496-
{reply, ok, State#state{crash_in_terminate = true}}.
570+
{reply, ok, State#state{crash_in_terminate = true}};
571+
handle_call({req_timeout_reply, Replyto}, _From, State) ->
572+
Ref = make_ref(),
573+
{reply, {ok, Ref}, State, {timeout, 1, {timeout_reply, Replyto, Ref}}}.
497574

498575
handle_cast({continue_noreply, Pid}, State) ->
499576
self() ! {after_continue, Pid},
@@ -504,8 +581,14 @@ handle_cast(ping, #state{num_casts = NumCasts} = State) ->
504581
{noreply, State#state{num_casts = NumCasts + 1}};
505582
handle_cast({cast_timeout, Timeout}, State) ->
506583
{noreply, State, Timeout};
584+
handle_cast({tuple_timeout, Timeouts}, State) ->
585+
{noreply, State, {timeout, 1, {do_tuple_timeouts, Timeouts}}};
507586
handle_cast({set_info_timeout, Timeout}, State) ->
508587
{noreply, State#state{info_timeout = Timeout}};
588+
handle_cast({req_timeout_reply, Pid}, State) ->
589+
{noreply, State, {timeout, 1, {timeout_reply, Pid, cast}}};
590+
handle_cast({request_info_timeout, Pid}, State) ->
591+
{noreply, State, {timeout, 1, {request_info_timeout, Pid}}};
509592
handle_cast(_Request, State) ->
510593
{noreply, State}.
511594

@@ -536,6 +619,19 @@ handle_info(timeout, #state{num_timeouts = NumTimeouts, info_timeout = InfoTimeo
536619
Other ->
537620
{noreply, NewState, Other}
538621
end;
622+
handle_info({timeout_reply, From, Tag} = _Info, State) ->
623+
From ! {reply_from_timeout, Tag},
624+
{noreply, State};
625+
handle_info({request_info_timeout, From}, State) ->
626+
{noreply, State, {timeout, 1, {info_timeout_reply, From}}};
627+
handle_info({info_timeout_reply, From}, State) ->
628+
From ! {reply_from_timeout, info},
629+
{noreply, State};
630+
handle_info({do_tuple_timeouts, 0}, State) ->
631+
{noreply, State};
632+
handle_info({do_tuple_timeouts, Timeouts}, #state{num_timeouts = TimeoutCt} = State) ->
633+
{noreply, State#state{num_timeouts = TimeoutCt + 1},
634+
{timeout, 1, {do_tuple_timeouts, Timeouts - 1}}};
539635
handle_info(_Info, #state{info_timeout = InfoTimeout} = State) ->
540636
case InfoTimeout of
541637
none ->

0 commit comments

Comments
 (0)