application_controller.erl
来自「OTP是开放电信平台的简称」· ERL 代码 · 共 1,925 行 · 第 1/5 页
ERL
1,925 行
%% ``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 via the world wide web 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.%% %% The Initial Developer of the Original Code is Ericsson Utvecklings AB.%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings%% AB. All Rights Reserved.''%% %% $Id$%%-module(application_controller).%% External exports-export([start/1, load_application/1, unload_application/1, start_application/2, start_boot_application/2, stop_application/1, control_application/1, change_application_data/2, prep_config_change/0, config_change/1, which_applications/0, which_applications/1, loaded_applications/0, info/0, get_pid_env/2, get_env/2, get_pid_all_env/1, get_all_env/1, get_pid_key/2, get_key/2, get_pid_all_key/1, get_all_key/1, get_master/1, get_application/1, get_application_module/1, start_type/1, permit_application/2, do_config_diff/2, set_env/3, set_env/4, unset_env/2, unset_env/3]).%% Internal exports-export([handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3, init_starter/4, get_loaded/1]).%% Test exports, only to be used from the test suites-export([test_change_apps/2]).-import(lists, [zf/2, map/2, foreach/2, foldl/3, keysearch/3, keydelete/3, keyreplace/4]).-include("application_master.hrl").-define(AC, ?MODULE). % Name of process%%%-----------------------------------------------------------------%%% The application_controller controls local applications only. A%%% local application can be loaded/started/stopped/unloaded and%%% changed. The control of distributed applications is taken care of%%% by another process (default is dist_ac).%%%%%% When an application has been started (by a call to application:start)%%% it can be running or not running (on this node). For example,%%% a distributed application must be started on all nodes, but%%% may be running on one node at the time.%%%%%% The external API to this module is in the module 'application'.%%% %%% The process that controls distributed applications (called dist%%% ac). calls application_controller:control_application(Name) to%%% take responsibility for an application. The interface between AC%%% and the dist_ac process is message-based:%%%%%% AC DIST AC%%% == =======%%% --> {ac_load_application_req, Name}%%% <-- {ac_load_application_reply, Name, LoadReply}%%% --> {ac_start_application_req, Name} (*)%%% <-- {ac_start_application_reply, Name, StartReply}%%% --> {ac_application_run, Name, Res}%%% --> {ac_application_not_run, Name, Res}%%% --> {ac_application_stopped, Name}%%% --> {ac_application_unloaded, Name}%%% <-- {ac_change_application_req, Name, Req} (**)%%%%%% Where LoadReply =%%% ok - App is loaded%%% {error, R} - An error occurred%%% And StartReply =%%% start_it - DIST AC decided that AC should start the app%%% {started, Node} - The app is started distributed at Node%%% not_started - The app should not be running at this time%%% {takeover, Node}- The app should takeover from Node%%% {error, R} - an error occurred%%% And Req =%%% start_it - DIST AC wants AC to start the app locally%%% stop_it - AC should stop the app.%%% {takeover, Node, RestartType}%%% - AC should start the app as a takeover%%% {failover, Node, RestartType}%%% - AC should start the app as a failover%%% {started, Node} - The app is started at Node%%% NOTE: The app must have been started at this node%%% before this request is sent!%%% And Res =%%% ok - Application is started locally%%% {error, R} - Start of application failed%%%%%% (*)%%% The call to application:start() doesn't return until the%%% ac_start_application_reply has been received by AC. AC%%% itself is not blocked however.%%% (**)%%% DIST AC gets ACK to its ac_change_application_req, but not as a%%% separate messgage. Instead the normal messages are used as:%%% start_it generates an ac_application_run%%% stop_it generates an ac_application_not_run%%% takeover generates an ac_application_run%%% started doesn't generate anything%%%%%% There is a distinction between application:stop and stop_it%%% from a dist ac process. The first one stops the application,%%% and resets the internal structures as they were before start was%%% called. stop_it stops the application, but just marks it as%%% not being running.%%%%%% When a dist ac process has taken control of an application, no%%% other process can take the control.%%%-----------------------------------------------------------------%%-----------------------------------------------------------------%% Naming conventions:%% App = appl_descr()%% Appl = #appl%% AppName = atom()%% Application = App | AppName%%------------------------------------------------------------------record(state, {loading = [], starting = [], start_p_false = [], running = [], control = [], started = [], start_req = [], conf_data}).%%-----------------------------------------------------------------%% loading = [{AppName, From}] - Load not yet finished%% starting = [{AppName, RestartType, Type, From}] - Start not%% yet finished%% start_p_false = [{AppName, RestartType, Type, From}] - Start not%% executed because permit == false%% running = [{AppName, Pid}] - running locally (Pid == application_master)%% [{AppName, {distributed, Node}}] - running on Node%% control = [{AppName, Controller}]%% started = [{AppName, RestartType}] - Names of all apps that%% have been started (but may not run because%% permission = false)%% conf_data = [{AppName, Env}]%% start_req = [{AppName, From}] - list of all start requests%% Id = AMPid | undefined | {distributed, Node}%% Env = [{Key, Value}]%%------------------------------------------------------------------record(appl, {name, appl_data, descr, id, vsn, restart_type, inc_apps, apps}).%%-----------------------------------------------------------------%% Func: start/1%% Args: KernelApp = appl_descr()%% appl_descr() = [{application, Name, [appl_opt()]}]%% appl_opt() = {description, string()} |%% {vsn, string()} |%% {id, string()}, |%% {modules, [Module|{Module,Vsn}]} |%% {registered, [atom()]} |%% {applications, [atom()]} |%% {included_applications, [atom()]} |%% {env, [{atom(), term()}]} |%% {start_phases, [{atom(), term()}]}|%% {maxT, integer()|infinity} |%% {maxP, integer()|infinity} |%% {mod, {Module, term()}}%% Module = atom()%% Vsn = term()%% Purpose: Starts the application_controller. This process starts all%% application masters for the applications.%% The kernel application is the only application that is%% treated specially. The reason for this is that the kernel%% starts user. This process is special because it should%% be group_leader for this process.%% Pre: All modules are loaded, or will be loaded on demand.%% Returns: {ok, Pid} | ReasonStr%%-----------------------------------------------------------------start(KernelApp) -> %% OTP-5811 Don't start as a gen_server to prevent crash report %% when (if) the process terminates Init = self(), AC = spawn_link(fun() -> init(Init, KernelApp) end), receive {ack, AC, ok} -> {ok, AC}; {ack, AC, {error, Reason}} -> to_string(Reason); % init doesn't want error tuple, only a reason {'EXIT', _Pid, Reason} -> to_string(Reason) end. %%-----------------------------------------------------------------%% Func: load_application/1%% Args: Application = appl_descr() | atom()%% Purpose: Loads an application. Currently just inserts the%% application's env.%% Returns: ok | {error, Reason}%%-----------------------------------------------------------------load_application(Application) -> gen_server:call(?AC, {load_application, Application}, infinity).unload_application(AppName) -> gen_server:call(?AC, {unload_application, AppName}, infinity).%%-----------------------------------------------------------------%% Func: start_application/2%% Args: Application = atom()%% RestartType = permanent | transient | temporary%% Purpose: Starts a new application.%% The RestartType specifies what should happen if the%% application dies:%% If it is permanent, all other applications are terminated,%% and the application_controller dies.%% If it is transient, and the application dies normally,%% this is reported and no other applications are terminated.%% If the application dies abnormally, all other applications%% are terminated, and the application_controller dies.%% If it is temporary and the application dies this is reported%% and no other applications are terminated. In this way,%% an application can run in test mode, without disturbing%% the other applications.%% The caller of this function is suspended until the application%% is started, either locally or distributed.%% Returns: ok | {error, Reason}%%-----------------------------------------------------------------start_application(AppName, RestartType) -> gen_server:call(?AC, {start_application, AppName, RestartType}, infinity).%%-----------------------------------------------------------------%% Func: start_boot_application/2%% The same as start_application/2 expect that this function is%% called from the boot script file. It mustnot be used by the operator.%% This function will cause a node crash if a permanent application %% fails to boot start%%-----------------------------------------------------------------start_boot_application(Application, RestartType) -> case {application:load(Application), RestartType} of {ok, _} -> AppName = get_appl_name(Application), gen_server:call(?AC, {start_application, AppName, RestartType}, infinity); {{error, {already_loaded, AppName}}, _} -> gen_server:call(?AC, {start_application, AppName, RestartType}, infinity); {{error,{bad_environment_value,Env}}, permanent} -> Txt = io_lib:format("Bad environment variable: ~p Application: ~p", [Env, Application]), exit({error, list_to_atom(lists:flatten(Txt))}); {Error, _} -> Error end.stop_application(AppName) -> gen_server:call(?AC, {stop_application, AppName}, infinity).%%-----------------------------------------------------------------%% Returns: [{Name, Descr, Vsn}]%%-----------------------------------------------------------------which_applications() -> gen_server:call(?AC, which_applications). which_applications(Timeout) -> gen_server:call(?AC, which_applications, Timeout).loaded_applications() -> ets:filter(ac_tab, fun([{{loaded, AppName}, #appl{descr = Descr, vsn = Vsn}}]) -> {true, {AppName, Descr, Vsn}}; (_) -> false end, []).%% Returns some debug infoinfo() -> gen_server:call(?AC, info). control_application(AppName) -> gen_server:call(?AC, {control_application, AppName}, infinity).%%-----------------------------------------------------------------%% Func: change_application_data/2%% Args: Applications = [appl_descr()]%% Config = [{AppName, [{Par,Val}]}]%% Purpose: Change all applications and their parameters on this node.%% This function should be used from a release handler, at%% the same time as the .app or start.boot file is%% introduced. Note that during some time the ACs may have%% different view of e.g. the distributed applications.%% This is solved by syncing the release installation.%% However, strange things may happen if a node crashes%% and two other nodes have different opinons about who's%% gonna start the applications. The release handler must%% shutdown each involved node in this case.%% Note that this function is used to change existing apps,%% adding new/deleting old isn't handled by this function.%% Changes an application's vsn, descr and env.%% Returns: ok | {error, Reason}%% If an error occurred, the situation may be inconsistent,%% so the release handler must restart the node. E.g. if%% some applicatation may have got new config data.%%-----------------------------------------------------------------change_application_data(Applications, Config) -> gen_server:call(?AC, {change_application_data, Applications, Config}, infinity).prep_config_change() -> gen_server:call(?AC, prep_config_change, infinity).config_change(EnvPrev) -> gen_server:call(?AC, {config_change, EnvPrev}, infinity).get_pid_env(Master, Key) -> case ets:match(ac_tab, {{application_master, '$1'}, Master}) of [[AppName]] -> get_env(AppName, Key); _ -> undefined end.get_env(AppName, Key) -> case ets:lookup(ac_tab, {env, AppName, Key}) of [{_, Val}] -> {ok, Val}; _ -> undefined end.get_pid_all_env(Master) -> case ets:match(ac_tab, {{application_master, '$1'}, Master}) of [[AppName]] -> get_all_env(AppName); _ -> [] end.get_all_env(AppName) -> map(fun([Key, Val]) -> {Key, Val} end, ets:match(ac_tab, {{env, AppName, '$1'}, '$2'})).get_pid_key(Master, Key) -> case ets:match(ac_tab, {{application_master, '$1'}, Master}) of [[AppName]] -> get_key(AppName, Key); _ -> undefined end.get_key(AppName, Key) -> case ets:lookup(ac_tab, {loaded, AppName}) of [{_, Appl}] -> case Key of description -> {ok, Appl#appl.descr}; id -> {ok, Appl#appl.id}; vsn -> {ok, Appl#appl.vsn}; modules -> {ok, (Appl#appl.appl_data)#appl_data.mods}; maxP -> {ok, (Appl#appl.appl_data)#appl_data.maxP}; maxT -> {ok, (Appl#appl.appl_data)#appl_data.maxT}; registered -> {ok, (Appl#appl.appl_data)#appl_data.regs}; included_applications -> {ok, Appl#appl.inc_apps}; applications -> {ok, Appl#appl.apps}; env -> {ok, get_all_env(AppName)}; mod -> {ok, (Appl#appl.appl_data)#appl_data.mod}; start_phases -> {ok, (Appl#appl.appl_data)#appl_data.phases}; _ -> undefined end; _ -> undefined end. get_pid_all_key(Master) -> case ets:match(ac_tab, {{application_master, '$1'}, Master}) of
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?