gen_server.erl
来自「OTP是开放电信平台的简称」· ERL 代码 · 共 814 行 · 第 1/2 页
ERL
814 行
%% ``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(gen_server).%%% ---------------------------------------------------%%%%%% The idea behind THIS server is that the user module%%% provides (different) functions to handle different%%% kind of inputs. %%% If the Parent process terminates the Module:terminate/2%%% function is called.%%%%%% The user module should export:%%%%%% init(Args) %%% ==> {ok, State}%%% {ok, State, Timeout}%%% ignore%%% {stop, Reason}%%%%%% handle_call(Msg, {From, Tag}, State)%%%%%% ==> {reply, Reply, State}%%% {reply, Reply, State, Timeout}%%% {noreply, State}%%% {noreply, State, Timeout}%%% {stop, Reason, Reply, State} %%% Reason = normal | shutdown | Term terminate(State) is called%%%%%% handle_cast(Msg, State)%%%%%% ==> {noreply, State}%%% {noreply, State, Timeout}%%% {stop, Reason, State} %%% Reason = normal | shutdown | Term terminate(State) is called%%%%%% handle_info(Info, State) Info is e.g. {'EXIT', P, R}, {nodedown, N}, ...%%%%%% ==> {noreply, State}%%% {noreply, State, Timeout}%%% {stop, Reason, State} %%% Reason = normal | shutdown | Term, terminate(State) is called%%%%%% terminate(Reason, State) Let the user module clean up%%% always called when server terminates%%%%%% ==> ok%%%%%%%%% The work flow (of the server) can be described as follows:%%%%%% User module Generic%%% ----------- -------%%% start -----> start%%% init <----- .%%%%%% loop%%% handle_call <----- .%%% -----> reply%%%%%% handle_cast <----- .%%%%%% handle_info <----- .%%%%%% terminate <----- .%%%%%% -----> reply%%%%%%%%% ---------------------------------------------------%% API-export([start/3, start/4, start_link/3, start_link/4, call/2, call/3, cast/2, reply/2, abcast/2, abcast/3, multi_call/2, multi_call/3, multi_call/4, enter_loop/3, enter_loop/4, enter_loop/5]).-export([behaviour_info/1]).%% System exports-export([system_continue/3, system_terminate/4, system_code_change/4, format_status/2]).%% Internal exports-export([init_it/6, print_event/3]).-import(error_logger, [format/2]).%%%=========================================================================%%% API%%%=========================================================================behaviour_info(callbacks) -> [{init,1},{handle_call,3},{handle_cast,2},{handle_info,2}, {terminate,2},{code_change,3}];behaviour_info(_Other) -> undefined.%%% -----------------------------------------------------------------%%% Starts a generic server.%%% start(Mod, Args, Options)%%% start(Name, Mod, Args, Options)%%% start_link(Mod, Args, Options)%%% start_link(Name, Mod, Args, Options) where:%%% Name ::= {local, atom()} | {global, atom()}%%% Mod ::= atom(), callback module implementing the 'real' server%%% Args ::= term(), init arguments (to Mod:init/1)%%% Options ::= [{timeout, Timeout} | {debug, [Flag]}]%%% Flag ::= trace | log | {logfile, File} | statistics | debug%%% (debug == log && statistics)%%% Returns: {ok, Pid} |%%% {error, {already_started, Pid}} |%%% {error, Reason}%%% -----------------------------------------------------------------start(Mod, Args, Options) -> gen:start(?MODULE, nolink, Mod, Args, Options).start(Name, Mod, Args, Options) -> gen:start(?MODULE, nolink, Name, Mod, Args, Options).start_link(Mod, Args, Options) -> gen:start(?MODULE, link, Mod, Args, Options).start_link(Name, Mod, Args, Options) -> gen:start(?MODULE, link, Name, Mod, Args, Options).%% -----------------------------------------------------------------%% Make a call to a generic server.%% If the server is located at another node, that node will%% be monitored.%% If the client is trapping exits and is linked server termination%% is handled here (? Shall we do that here (or rely on timeouts) ?).%% ----------------------------------------------------------------- call(Name, Request) -> case catch gen:call(Name, '$gen_call', Request) of {ok,Res} -> Res; {'EXIT',Reason} -> exit({Reason, {?MODULE, call, [Name, Request]}}) end.call(Name, Request, Timeout) -> case catch gen:call(Name, '$gen_call', Request, Timeout) of {ok,Res} -> Res; {'EXIT',Reason} -> exit({Reason, {?MODULE, call, [Name, Request, Timeout]}}) end.%% -----------------------------------------------------------------%% Make a cast to a generic server.%% -----------------------------------------------------------------cast({global,Name}, Request) -> catch global:send(Name, cast_msg(Request)), ok;cast({Name,Node}=Dest, Request) when is_atom(Name), is_atom(Node) -> do_cast(Dest, Request);cast(Dest, Request) when is_atom(Dest) -> do_cast(Dest, Request);cast(Dest, Request) when is_pid(Dest) -> do_cast(Dest, Request).do_cast(Dest, Request) -> do_send(Dest, cast_msg(Request)), ok. cast_msg(Request) -> {'$gen_cast',Request}.%% -----------------------------------------------------------------%% Send a reply to the client.%% -----------------------------------------------------------------reply({To, Tag}, Reply) -> catch To ! {Tag, Reply}.%% ----------------------------------------------------------------- %% Asyncronous broadcast, returns nothing, it's just send'n prey%%----------------------------------------------------------------- abcast(Name, Request) when is_atom(Name) -> do_abcast([node() | nodes()], Name, cast_msg(Request)).abcast(Nodes, Name, Request) when is_list(Nodes), is_atom(Name) -> do_abcast(Nodes, Name, cast_msg(Request)).do_abcast([Node|Nodes], Name, Msg) when is_atom(Node) -> do_send({Name,Node},Msg), do_abcast(Nodes, Name, Msg);do_abcast([], _,_) -> abcast.%%% -----------------------------------------------------------------%%% Make a call to servers at several nodes.%%% Returns: {[Replies],[BadNodes]}%%% A Timeout can be given%%% %%% A middleman process is used in case late answers arrives after%%% the timeout. If they would be allowed to glog the callers message%%% queue, it would probably become confused. Late answers will %%% now arrive to the terminated middleman and so be discarded.%%% -----------------------------------------------------------------multi_call(Name, Req) when is_atom(Name) -> do_multi_call([node() | nodes()], Name, Req, infinity).multi_call(Nodes, Name, Req) when is_list(Nodes), is_atom(Name) -> do_multi_call(Nodes, Name, Req, infinity).multi_call(Nodes, Name, Req, infinity) -> do_multi_call(Nodes, Name, Req, infinity);multi_call(Nodes, Name, Req, Timeout) when is_list(Nodes), is_atom(Name), is_integer(Timeout), Timeout >= 0 -> do_multi_call(Nodes, Name, Req, Timeout).%%-----------------------------------------------------------------%% enter_loop(Mod, Options, State, <ServerName>, <TimeOut>) ->_ %% %% Description: Makes an existing process into a gen_server. %% The calling process will enter the gen_server receive %% loop and become a gen_server process.%% The process *must* have been started using one of the %% start functions in proc_lib, see proc_lib(3). %% The user is responsible for any initialization of the %% process, including registering a name for it.%%-----------------------------------------------------------------enter_loop(Mod, Options, State) -> enter_loop(Mod, Options, State, self(), infinity).enter_loop(Mod, Options, State, ServerName = {_, _}) -> enter_loop(Mod, Options, State, ServerName, infinity);enter_loop(Mod, Options, State, Timeout) -> enter_loop(Mod, Options, State, self(), Timeout).enter_loop(Mod, Options, State, ServerName, Timeout) -> Name = get_proc_name(ServerName), Parent = get_parent(), Debug = debug_options(Name, Options), loop(Parent, Name, State, Mod, Timeout, Debug).%%%========================================================================%%% Gen-callback functions%%%========================================================================%%% ---------------------------------------------------%%% Initiate the new process.%%% Register the name using the Rfunc function%%% Calls the Mod:init/Args function.%%% Finally an acknowledge is sent to Parent and the main%%% loop is entered.%%% ---------------------------------------------------init_it(Starter, self, Name, Mod, Args, Options) -> init_it(Starter, self(), Name, Mod, Args, Options);init_it(Starter, Parent, Name, Mod, Args, Options) -> Debug = debug_options(Name, Options), case catch Mod:init(Args) of {ok, State} -> proc_lib:init_ack(Starter, {ok, self()}), loop(Parent, Name, State, Mod, infinity, Debug); {ok, State, Timeout} -> proc_lib:init_ack(Starter, {ok, self()}), loop(Parent, Name, State, Mod, Timeout, Debug); {stop, Reason} -> proc_lib:init_ack(Starter, {error, Reason}), exit(Reason); ignore -> proc_lib:init_ack(Starter, ignore), exit(normal); {'EXIT', Reason} -> proc_lib:init_ack(Starter, {error, Reason}), exit(Reason); Else -> Error = {bad_return_value, Else}, proc_lib:init_ack(Starter, {error, Error}), exit(Error) end.%%%========================================================================%%% Internal functions%%%========================================================================%%% ---------------------------------------------------%%% The MAIN loop.%%% ---------------------------------------------------loop(Parent, Name, State, Mod, Time, Debug) -> Msg = receive Input -> Input after Time -> timeout end, case Msg of {system, From, Req} -> sys:handle_system_msg(Req, From, Parent, ?MODULE, Debug, [Name, State, Mod, Time]); {'EXIT', Parent, Reason} -> terminate(Reason, Name, Msg, Mod, State, Debug); _Msg when Debug =:= [] -> handle_msg(Msg, Parent, Name, State, Mod, Time); _Msg -> Debug1 = sys:handle_debug(Debug, {?MODULE, print_event}, Name, {in, Msg}), handle_msg(Msg, Parent, Name, State, Mod, Time, Debug1) end.%%% ---------------------------------------------------%%% Send/recive functions%%% ---------------------------------------------------do_send(Dest, Msg) -> case catch erlang:send(Dest, Msg, [noconnect]) of noconnect -> spawn(erlang, send, [Dest,Msg]); Other -> Other end.do_multi_call(Nodes, Name, Req, infinity) -> Tag = make_ref(), Monitors = send_nodes(Nodes, Name, Tag, Req), rec_nodes(Tag, Monitors, Name, undefined);do_multi_call(Nodes, Name, Req, Timeout) -> Tag = make_ref(), Caller = self(), Receiver = spawn( fun() -> %% Middleman process. Should be unsensitive to regular %% exit signals. The sychronization is needed in case %% the receiver would exit before the caller started %% the monitor. process_flag(trap_exit, true), Mref = erlang:monitor(process, Caller), receive {Caller,Tag} -> Monitors = send_nodes(Nodes, Name, Tag, Req), TimerId = erlang:start_timer(Timeout, self(), ok), Result = rec_nodes(Tag, Monitors, Name, TimerId), exit({self(),Tag,Result}); {'DOWN',Mref,_,_,_} -> %% Caller died before sending us the go-ahead. %% Give up silently. exit(normal) end end), Mref = erlang:monitor(process, Receiver), Receiver ! {self(),Tag}, receive {'DOWN',Mref,_,_,{Receiver,Tag,Result}} -> Result; {'DOWN',Mref,_,_,Reason} -> %% The middleman code failed. Or someone did %% exit(_, kill) on the middleman process => Reason==killed exit(Reason) end.send_nodes(Nodes, Name, Tag, Req) -> send_nodes(Nodes, Name, Tag, Req, []).send_nodes([Node|Tail], Name, Tag, Req, Monitors) when is_atom(Node) -> Monitor = start_monitor(Node, Name), %% Handle non-existing names in rec_nodes. catch {Name, Node} ! {'$gen_call', {self(), {Tag, Node}}, Req}, send_nodes(Tail, Name, Tag, Req, [Monitor | Monitors]);send_nodes([_Node|Tail], Name, Tag, Req, Monitors) -> %% Skip non-atom Node send_nodes(Tail, Name, Tag, Req, Monitors);send_nodes([], _Name, _Tag, _Req, Monitors) -> Monitors.%% Against old nodes:%% If no reply has been delivered within 2 secs. (per node) check that%% the server really exists and wait for ever for the answer.%%%% Against contemporary nodes:%% Wait for reply, server 'DOWN', or timeout from TimerId.rec_nodes(Tag, Nodes, Name, TimerId) -> rec_nodes(Tag, Nodes, Name, [], [], 2000, TimerId).rec_nodes(Tag, [{N,R}|Tail], Name, Badnodes, Replies, Time, TimerId ) -> receive {'DOWN', R, _, _, _} -> rec_nodes(Tag, Tail, Name, [N|Badnodes], Replies, Time, TimerId); {{Tag, N}, Reply} -> %% Tag is bound !!! unmonitor(R), rec_nodes(Tag, Tail, Name, Badnodes, [{N,Reply}|Replies], Time, TimerId);
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?