aboutsummaryrefslogtreecommitdiffstats
path: root/lib/kernel/src/heart.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/kernel/src/heart.erl')
-rw-r--r--lib/kernel/src/heart.erl271
1 files changed, 271 insertions, 0 deletions
diff --git a/lib/kernel/src/heart.erl b/lib/kernel/src/heart.erl
new file mode 100644
index 0000000000..bad0950fca
--- /dev/null
+++ b/lib/kernel/src/heart.erl
@@ -0,0 +1,271 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(heart).
+
+%%%--------------------------------------------------------------------
+%%% This is a rewrite of pre_heart from BS.3.
+%%%
+%%% The purpose of this process-module is to act as an supervisor
+%%% of the entire erlang-system. This 'heart' beats with a frequence
+%%% satisfying an external port program *not* reboot the entire
+%%% system. If however the erlang-emulator would hang, a reboot is
+%%% then needed.
+%%%
+%%% It recognizes the flag '-heart'
+%%%--------------------------------------------------------------------
+-export([start/0, init/2, set_cmd/1, clear_cmd/0, get_cmd/0, cycle/0]).
+
+-define(START_ACK, 1).
+-define(HEART_BEAT, 2).
+-define(SHUT_DOWN, 3).
+-define(SET_CMD, 4).
+-define(CLEAR_CMD, 5).
+-define(GET_CMD, 6).
+-define(HEART_CMD, 7).
+
+-define(TIMEOUT, 5000).
+-define(CYCLE_TIMEOUT, 10000).
+
+%%---------------------------------------------------------------------
+
+-spec start() -> 'ignore' | {'error', term()} | {'ok', pid()}.
+
+start() ->
+ case whereis(heart) of
+ undefined ->
+ %% As heart survives a init:restart/0 the Parent
+ %% of heart must be init.
+ %% The init process is responsible to create a link
+ %% to heart.
+ Pid = spawn(?MODULE, init, [self(), whereis(init)]),
+ wait_for_init_ack(Pid);
+ Pid ->
+ {ok, Pid}
+ end.
+
+wait_for_init_ack(From) ->
+ receive
+ {ok, From} ->
+ {ok, From};
+ {no_heart, From} ->
+ ignore;
+ {Error, From} ->
+ {error, Error}
+ end.
+
+-spec init(pid(), pid()) -> {'no_heart', pid()} | {'start_error', pid()}.
+
+init(Starter, Parent) ->
+ process_flag(trap_exit, true),
+ process_flag(priority, max),
+ register(heart, self()),
+ case catch start_portprogram() of
+ {ok, Port} ->
+ Starter ! {ok, self()},
+ loop(Parent, Port, "");
+ no_heart ->
+ Starter ! {no_heart, self()};
+ error ->
+ Starter ! {start_error, self()}
+ end.
+
+-spec set_cmd(string()) -> 'ok' | {'error', {'bad_cmd', string()}}.
+
+set_cmd(Cmd) ->
+ heart ! {self(), set_cmd, Cmd},
+ wait().
+
+-spec get_cmd() -> 'ok'.
+
+get_cmd() ->
+ heart ! {self(), get_cmd},
+ wait().
+
+-spec clear_cmd() -> {'ok', string()}.
+
+clear_cmd() ->
+ heart ! {self(), clear_cmd},
+ wait().
+
+
+%%% Should be used solely by the release handler!!!!!!!
+-spec cycle() -> 'ok' | {'error', term()}.
+
+cycle() ->
+ heart ! {self(), cycle},
+ wait().
+
+wait() ->
+ receive
+ {heart, Res} ->
+ Res
+ end.
+
+start_portprogram() ->
+ check_start_heart(),
+ HeartCmd = "heart -pid " ++ os:getpid() ++ " " ++
+ get_heart_timeouts(),
+ try open_port({spawn, HeartCmd}, [{packet, 2}]) of
+ Port when is_port(Port) ->
+ case wait_ack(Port) of
+ ok ->
+ {ok, Port};
+ {error, Reason} ->
+ report_problem({{port_problem, Reason},
+ {heart, start_portprogram, []}}),
+ error
+ end
+ catch
+ _:Reason ->
+ report_problem({{open_port, Reason},
+ {heart, start_portprogram, []}}),
+ error
+ end.
+
+get_heart_timeouts() ->
+ HeartOpts = case os:getenv("HEART_BEAT_TIMEOUT") of
+ false -> "";
+ H when is_list(H) ->
+ "-ht " ++ H
+ end,
+ HeartOpts ++ case os:getenv("HEART_BEAT_BOOT_DELAY") of
+ false -> "";
+ W when is_list(W) ->
+ " -wt " ++ W
+ end.
+
+check_start_heart() ->
+ case init:get_argument(heart) of
+ {ok, [[]]} ->
+ ok;
+ error ->
+ throw(no_heart);
+ {ok, [[X|_]|_]} ->
+ report_problem({{bad_heart_flag, list_to_atom(X)},
+ {heart, check_start_heart, []}}),
+ throw(error)
+ end.
+
+wait_ack(Port) ->
+ receive
+ {Port, {data, [?START_ACK]}} ->
+ ok;
+ {'EXIT', Port, badsig} -> % Since this is not synchronous, skip it!
+ wait_ack(Port);
+ {'EXIT', Port, Reason} -> % The port really terminated.
+ {error, Reason}
+ end.
+
+loop(Parent, Port, Cmd) ->
+ send_heart_beat(Port),
+ receive
+ {From, set_cmd, NewCmd} when is_list(NewCmd), length(NewCmd) < 2047 ->
+ send_heart_cmd(Port, NewCmd),
+ wait_ack(Port),
+ From ! {heart, ok},
+ loop(Parent, Port, NewCmd);
+ {From, set_cmd, NewCmd} ->
+ From ! {heart, {error, {bad_cmd, NewCmd}}},
+ loop(Parent, Port, Cmd);
+ {From, clear_cmd} ->
+ From ! {heart, ok},
+ send_heart_cmd(Port, ""),
+ wait_ack(Port),
+ loop(Parent, Port, "");
+ {From, get_cmd} ->
+ From ! {heart, get_heart_cmd(Port)},
+ loop(Parent, Port, Cmd);
+ {From, cycle} ->
+ %% Calls back to loop
+ do_cycle_port_program(From, Parent, Port, Cmd);
+ {'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);
+ {'EXIT', Port, _Reason} ->
+ exit({port_terminated, {heart, loop, [Parent, Port, Cmd]}});
+ _ ->
+ loop(Parent, Port, Cmd)
+ after
+ ?TIMEOUT ->
+ loop(Parent, Port, Cmd)
+ end.
+
+-spec no_reboot_shutdown(port()) -> no_return().
+
+no_reboot_shutdown(Port) ->
+ send_shutdown(Port),
+ receive
+ {'EXIT', Port, Reason} when Reason =/= badsig ->
+ exit(normal)
+ end.
+
+do_cycle_port_program(Caller, Parent, Port, Cmd) ->
+ 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)
+ 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)
+ end;
+ no_heart ->
+ Caller ! {heart, {error, no_heart}},
+ loop(Parent, Port, Cmd);
+ error ->
+ Caller ! {heart, {error, start_error}},
+ loop(Parent, Port, Cmd)
+ end.
+
+
+%% "Beates" the heart once.
+send_heart_beat(Port) -> Port ! {self(), {command, [?HEART_BEAT]}}.
+
+%% Set a new HEART_COMMAND.
+send_heart_cmd(Port, []) ->
+ Port ! {self(), {command, [?CLEAR_CMD]}};
+send_heart_cmd(Port, Cmd) ->
+ Port ! {self(), {command, [?SET_CMD|Cmd]}}.
+
+get_heart_cmd(Port) ->
+ Port ! {self(), {command, [?GET_CMD]}},
+ receive
+ {Port, {data, [?HEART_CMD | Cmd]}} ->
+ {ok, Cmd}
+ end.
+
+%% Sends shutdown command to the port.
+send_shutdown(Port) -> Port ! {self(), {command, [?SHUT_DOWN]}}.
+
+%% We must report using erlang:display/1 since we don't know whether
+%% there is an error_logger available or not.
+report_problem(Error) ->
+ erlang:display(Error).