diff options
author | Björn-Egil Dahlberg <[email protected]> | 2016-02-25 15:27:29 +0100 |
---|---|---|
committer | Björn-Egil Dahlberg <[email protected]> | 2016-02-25 15:27:29 +0100 |
commit | 93d2038e91854d4bc4fd75dac7005719a79949cd (patch) | |
tree | 6e56bd00815e289a2792bbc2d21ca02ea4791ee7 /lib/kernel/src/heart.erl | |
parent | 87ed3fe4d7ddbc3c64d182032e9fe054600cf5ba (diff) | |
parent | 0ca09ee90d6384e74d1b18ab0e05f2c05fc03905 (diff) | |
download | otp-93d2038e91854d4bc4fd75dac7005719a79949cd.tar.gz otp-93d2038e91854d4bc4fd75dac7005719a79949cd.tar.bz2 otp-93d2038e91854d4bc4fd75dac7005719a79949cd.zip |
Merge branch 'egil/heart-callback/OTP-13250' into maint
Diffstat (limited to 'lib/kernel/src/heart.erl')
-rw-r--r-- | lib/kernel/src/heart.erl | 182 |
1 files changed, 146 insertions, 36 deletions
diff --git a/lib/kernel/src/heart.erl b/lib/kernel/src/heart.erl index 137fad706f..eea78aabdf 100644 --- a/lib/kernel/src/heart.erl +++ b/lib/kernel/src/heart.erl @@ -34,7 +34,11 @@ %%% %%% It recognizes the flag '-heart' %%%-------------------------------------------------------------------- --export([start/0, init/2, set_cmd/1, clear_cmd/0, get_cmd/0, cycle/0]). +-export([start/0, init/2, + set_cmd/1, clear_cmd/0, get_cmd/0, + set_callback/2, clear_callback/0, get_callback/0, + set_options/1, get_options/0, + cycle/0]). -define(START_ACK, 1). -define(HEART_BEAT, 2). @@ -49,6 +53,16 @@ -define(CYCLE_TIMEOUT, 10000). -define(HEART_PORT_NAME, heart_port). +%% valid heart options +-define(SCHEDULER_CHECK_OPT, check_schedulers). + +-type heart_option() :: ?SCHEDULER_CHECK_OPT. + +-record(state,{port :: port(), + cmd :: [] | binary(), + options :: [heart_option()], + callback :: 'undefined' | {atom(), atom()}}). + %%--------------------------------------------------------------------- -spec start() -> 'ignore' | {'error', term()} | {'ok', pid()}. @@ -81,11 +95,11 @@ wait_for_init_ack(From) -> init(Starter, Parent) -> process_flag(trap_exit, true), process_flag(priority, max), - register(heart, self()), + register(?MODULE, self()), case catch start_portprogram() of {ok, Port} -> Starter ! {ok, self()}, - loop(Parent, Port, ""); + loop(Parent, #state{port=Port, cmd=[], options=[]}); no_heart -> Starter ! {no_heart, self()}; error -> @@ -96,33 +110,68 @@ init(Starter, Parent) -> Cmd :: string(). set_cmd(Cmd) -> - heart ! {self(), set_cmd, Cmd}, + ?MODULE ! {self(), set_cmd, Cmd}, wait(). -spec get_cmd() -> {ok, Cmd} when Cmd :: string(). get_cmd() -> - heart ! {self(), get_cmd}, + ?MODULE ! {self(), get_cmd}, wait(). -spec clear_cmd() -> ok. clear_cmd() -> - heart ! {self(), clear_cmd}, + ?MODULE ! {self(), clear_cmd}, + wait(). + +-spec set_callback(Module,Function) -> 'ok' | {'error', {'bad_callback', {Module, Function}}} when + Module :: atom(), + Function :: atom(). + +set_callback(Module, Function) -> + ?MODULE ! {self(), set_callback, {Module,Function}}, + wait(). + +-spec get_callback() -> {'ok', {Module, Function}} | 'none' when + Module :: atom(), + Function :: atom(). + +get_callback() -> + ?MODULE ! {self(), get_callback}, + wait(). + +-spec clear_callback() -> ok. + +clear_callback() -> + ?MODULE ! {self(), clear_callback}, + wait(). + +-spec set_options(Options) -> 'ok' | {'error', {'bad_options', Options}} when + Options :: [heart_option()]. + +set_options(Options) -> + ?MODULE ! {self(), set_options, Options}, wait(). +-spec get_options() -> {'ok', Options} | 'none' when + Options :: [atom()]. + +get_options() -> + ?MODULE ! {self(), get_options}, + wait(). %%% Should be used solely by the release handler!!!!!!! -spec cycle() -> 'ok' | {'error', term()}. cycle() -> - heart ! {self(), cycle}, + ?MODULE ! {self(), cycle}, wait(). wait() -> receive - {heart, Res} -> + {?MODULE, Res} -> Res end. @@ -182,8 +231,8 @@ wait_ack(Port) -> {error, Reason} end. -loop(Parent, Port, Cmd) -> - _ = send_heart_beat(Port), +loop(Parent, #state{port=Port}=S) -> + _ = send_heart_beat(S), receive {From, set_cmd, NewCmd0} -> Enc = file:native_name_encoding(), @@ -191,37 +240,72 @@ loop(Parent, Port, Cmd) -> NewCmd when is_binary(NewCmd), byte_size(NewCmd) < 2047 -> _ = send_heart_cmd(Port, NewCmd), _ = wait_ack(Port), - From ! {heart, ok}, - loop(Parent, Port, NewCmd); + From ! {?MODULE, ok}, + loop(Parent, S#state{cmd=NewCmd}); _ -> - From ! {heart, {error, {bad_cmd, NewCmd0}}}, - loop(Parent, Port, Cmd) + From ! {?MODULE, {error, {bad_cmd, NewCmd0}}}, + loop(Parent, S) end; {From, clear_cmd} -> - From ! {heart, ok}, - _ = send_heart_cmd(Port, ""), + From ! {?MODULE, ok}, + _ = send_heart_cmd(Port, []), _ = wait_ack(Port), - loop(Parent, Port, ""); + loop(Parent, S#state{cmd = []}); {From, get_cmd} -> - From ! {heart, get_heart_cmd(Port)}, - loop(Parent, Port, Cmd); + From ! {?MODULE, get_heart_cmd(Port)}, + loop(Parent, S); + {From, set_callback, Callback} -> + case Callback of + {M,F} when is_atom(M), is_atom(F) -> + From ! {?MODULE, ok}, + loop(Parent, S#state{callback=Callback}); + _ -> + From ! {?MODULE, {error, {bad_callback, Callback}}}, + loop(Parent, S) + end; + {From, get_callback} -> + Res = case S#state.callback of + undefined -> none; + Cb -> {ok, Cb} + end, + From ! {?MODULE, Res}, + loop(Parent, S); + {From, clear_callback} -> + From ! {?MODULE, ok}, + loop(Parent, S#state{callback=undefined}); + {From, set_options, Options} -> + case validate_options(Options) of + Validated when is_list(Validated) -> + From ! {?MODULE, ok}, + loop(Parent, S#state{options=Validated}); + _ -> + From ! {?MODULE, {error, {bad_options, Options}}}, + loop(Parent, S) + end; + {From, get_options} -> + Res = case S#state.options of + [] -> none; + Cb -> {ok, Cb} + end, + From ! {?MODULE, Res}, + loop(Parent, S); {From, cycle} -> %% Calls back to loop - do_cycle_port_program(From, Parent, Port, Cmd); + do_cycle_port_program(From, Parent, S); {'EXIT', Parent, shutdown} -> no_reboot_shutdown(Port); {'EXIT', Parent, Reason} -> exit(Port, Reason), exit(Reason); {'EXIT', Port, badsig} -> % we can ignore badsig-messages! - loop(Parent, Port, Cmd); + loop(Parent, S); {'EXIT', Port, _Reason} -> - exit({port_terminated, {heart, loop, [Parent, Port, Cmd]}}); + exit({port_terminated, {?MODULE, loop, [Parent, S]}}); _ -> - loop(Parent, Port, Cmd) + loop(Parent, S) after ?TIMEOUT -> - loop(Parent, Port, Cmd) + loop(Parent, S) end. -spec no_reboot_shutdown(port()) -> no_return(). @@ -233,36 +317,44 @@ no_reboot_shutdown(Port) -> exit(normal) end. -do_cycle_port_program(Caller, Parent, Port, Cmd) -> +validate_options(Opts) -> validate_options(Opts,[]). +validate_options([],Res) -> Res; +validate_options([?SCHEDULER_CHECK_OPT=Opt|Opts],Res) -> validate_options(Opts,[Opt|Res]); +validate_options(_,_) -> error. + +do_cycle_port_program(Caller, Parent, #state{port=Port} = S) -> unregister(?HEART_PORT_NAME), case catch start_portprogram() of {ok, NewPort} -> _ = send_shutdown(Port), receive {'EXIT', Port, _Reason} -> - _ = send_heart_cmd(NewPort, Cmd), - Caller ! {heart, ok}, - loop(Parent, NewPort, Cmd) + _ = send_heart_cmd(NewPort, S#state.cmd), + Caller ! {?MODULE, ok}, + loop(Parent, S#state{port=NewPort}) after ?CYCLE_TIMEOUT -> %% Huh! Two heart port programs running... %% well, the old one has to be sick not to respond %% so we'll settle for the new one... - _ = send_heart_cmd(NewPort, Cmd), - Caller ! {heart, {error, stop_error}}, - loop(Parent, NewPort, Cmd) + _ = send_heart_cmd(NewPort, S#state.cmd), + Caller ! {?MODULE, {error, stop_error}}, + loop(Parent, S#state{port=NewPort}) end; no_heart -> - Caller ! {heart, {error, no_heart}}, - loop(Parent, Port, Cmd); + Caller ! {?MODULE, {error, no_heart}}, + loop(Parent, S); error -> - Caller ! {heart, {error, start_error}}, - loop(Parent, Port, Cmd) + Caller ! {?MODULE, {error, start_error}}, + loop(Parent, S) end. %% "Beates" the heart once. -send_heart_beat(Port) -> Port ! {self(), {command, [?HEART_BEAT]}}. +send_heart_beat(#state{port=Port, callback=Cb, options=Opts}) -> + ok = check_system(Opts), + ok = check_callback(Cb), + Port ! {self(), {command, [?HEART_BEAT]}}. %% Set a new HEART_COMMAND. -dialyzer({no_improper_lists, send_heart_cmd/2}). @@ -278,6 +370,24 @@ get_heart_cmd(Port) -> {ok, Cmd} end. +check_system([]) -> ok; +check_system([?SCHEDULER_CHECK_OPT|Opts]) -> + ok = erts_internal:system_check(schedulers), + check_system(Opts). + +%% validate system by performing a check before the heartbeat +%% return 'ok' if everything is alright. +%% Terminate if with reason if something is a miss. +%% It is fine to timeout in the callback, in fact that is the intention +%% if something goes wront -> no heartbeat. + +check_callback(Callback) -> + case Callback of + undefined -> ok; + {M,F} -> + erlang:apply(M,F,[]) + end. + %% Sends shutdown command to the port. send_shutdown(Port) -> Port ! {self(), {command, [?SHUT_DOWN]}}. |