From 471a50ef1391f6399664f8b992da7a67c32c8b86 Mon Sep 17 00:00:00 2001 From: Raimo Niskanen Date: Tue, 26 Apr 2016 16:49:47 +0200 Subject: Fix hibernation subtlety --- lib/stdlib/doc/src/gen_statem.xml | 35 ++++++++++++++++++++++------------- lib/stdlib/src/gen_statem.erl | 30 ++++++++++++++++++++---------- 2 files changed, 42 insertions(+), 23 deletions(-) (limited to 'lib') diff --git a/lib/stdlib/doc/src/gen_statem.xml b/lib/stdlib/doc/src/gen_statem.xml index 91332fbdde..ec7f267c64 100644 --- a/lib/stdlib/doc/src/gen_statem.xml +++ b/lib/stdlib/doc/src/gen_statem.xml @@ -216,19 +216,23 @@ erlang:'!' -----> Module:StateName/3 if bad arguments are given.

- The gen_statem process can go into hibernation (see - - erlang:hibernate/3 - ) if a + The gen_statem process can go into hibernation; see + + proc_lib:hibernate/3. + + It is done when a state function or Module:init/1 specifies hibernate in the returned Actions - list. This might be useful if the server is expected to be idle - for a long time. However use this feature with care - since hibernation implies at least two garbage collections - (when hibernating and shortly after waking up) and that is not - something you would want to do between each event on a busy server. + list. This feature might be useful to reclaim process heap memory + while the server is expected to be idle for a long time. + However, use this feature with care + since hibernation can be too costly + to use after every event; see + + erlang:hibernate/3. +

@@ -619,7 +623,8 @@ handle_event(_, _, State, Data) -> to wait for the next message. In hibernation the next non-system event awakens the gen_statem, or rather the next incoming message awakens the gen_statem - but if it is a system event it goes back into hibernation. + but if it is a system event + it goes right back into hibernation. @@ -645,9 +650,13 @@ handle_event(_, _, State, Data) -> before going into receive to wait for a new external event. - If there are enqueued events the hibernate - is ignored as if an event just arrived and awakened - the gen_statem. + If there are enqueued events, + to prevent receiving any new event; a + + garbage_collect/0 + is done instead to simulate + that the gen_statem entered hibernation + and immediately got awakened by the oldest enqueued event.

diff --git a/lib/stdlib/src/gen_statem.erl b/lib/stdlib/src/gen_statem.erl index c85c521d8e..f9e2e5f7d2 100644 --- a/lib/stdlib/src/gen_statem.erl +++ b/lib/stdlib/src/gen_statem.erl @@ -723,8 +723,8 @@ wakeup_from_hibernate(Parent, Debug, S) -> %% and some detours through sys and proc_lib %% Entry point for system_continue/3 -loop(Parent, Debug, #{hibernate := Hib} = S) -> - case Hib of +loop(Parent, Debug, #{hibernate := Hibernate} = S) -> + case Hibernate of true -> %% Does not return but restarts process at %% wakeup_from_hibernate/3 that jumps to loop_receive/3 @@ -754,8 +754,7 @@ loop_receive(Parent, Debug, #{timer := Timer} = S) -> %% but this will stand out in the crash report... ?TERMINATE(exit, Reason, Debug, S, [EXIT]); {timeout,Timer,Content} when Timer =/= undefined -> - loop_event( - Parent, Debug, S, {timeout,Content}); + loop_event(Parent, Debug, S, {timeout,Content}); _ -> %% Cancel Timer if running case Timer of @@ -788,15 +787,15 @@ loop_receive(Parent, Debug, #{timer := Timer} = S) -> end. loop_event(Parent, Debug, S, Event) -> - %% The timer field in S is now invalid and ignored - %% until we get back to loop/3 + %% The timer field and the hibernate flag in S + %% are now invalid and ignored until we get back to loop/3 NewDebug = sys_debug(Debug, S, {in,Event}), %% Here the queue of not yet processed events is created - loop_events(Parent, NewDebug, S, [Event]). + loop_events(Parent, NewDebug, S, [Event], false). %% Process first the event queue, or if it is empty %% loop back to receive a new event -loop_events(Parent, Debug, S, []) -> +loop_events(Parent, Debug, S, [], _Hibernate) -> loop(Parent, Debug, S); loop_events( Parent, Debug, @@ -804,7 +803,16 @@ loop_events( module := Module, state := State, data := Data} = S, - [{Type,Content} = Event|Events] = Q) -> + [{Type,Content} = Event|Events] = Q, + Hibernate) -> + %% If the Hibernate flag is true here it can only be + %% because it was set from an event action + %% and we did not go into hibernation since there + %% were events in queue, so we do what the user + %% might depend on i.e collect garbage which + %% would have happened if we actually hibernated + %% and immediately was awakened + Hibernate andalso garbage_collect(), try case CallbackMode of state_functions -> @@ -869,6 +877,8 @@ loop_event_result( Parent, Debug, #{state := State, data := Data} = S, Events, Event, Result) -> + %% From now until we loop back to the loop_events/4 + %% the state and data fields in S are old case Result of stop -> ?TERMINATE(exit, normal, Debug, S, [Event|Events]); @@ -1094,7 +1104,7 @@ loop_event_actions( timer := Timer, postponed := P, hibernate := Hibernate}, - Q). + Q, Hibernate). %%--------------------------------------------------------------------------- %% Server helpers -- cgit v1.2.3