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 + -
显示快捷键?