%%%---------------------------------------------------------------------- %%% File : dist_server.erl %%% Author : Ulf Wiger %%% Purpose : %%% Created : 2 Dec 2002 by Ulf Wiger %%%---------------------------------------------------------------------- -module(dist_server). -author('ulf.wiger@ericsson.com'). -behaviour(gen_server). % see erl -man gen_server %% External exports -export([start_link/2]). %% API functions -export([get_value/0, set_value/1]). %% Control functions -- bonus example. These functions are called from %% dist_app.erl, from the context of an "application starter process" %% owned by the application controller. The starter process will live %% as long as the application does (we cheat and use this knowledge.) -export([takeover/1, go/1]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, code_change/3, terminate/2]). %% The server's state -record(state, {reg_function, value}). %%%---------------------------------------------------------------------- %%% API %%%---------------------------------------------------------------------- %% start_link(ServerName, CallbackModule, Arg, Options) %% I think it's good practice to use the module name as registered name. start_link(RegisterF, CheckF) -> gen_server:start_link({local, ?MODULE}, ?MODULE, _Arg = {RegisterF, CheckF}, _Options = []). get_value() -> gen_server:call({global, ?MODULE}, get_value). set_value(S) -> gen_server:call({global, ?MODULE}, {set_value, S}). takeover(FromNode) -> gen_server:call(?MODULE, {takeover, FromNode}). go(StartType) -> gen_server:call(?MODULE, {go, StartType}). %%%---------------------------------------------------------------------- %%% Callback functions from gen_server %%%---------------------------------------------------------------------- %%---------------------------------------------------------------------- %% Func: init/1 %% Returns: {ok, State} | %% {ok, State, Timeout} | %% ignore | %% {stop, Reason} %%---------------------------------------------------------------------- init({RegisterF, CheckF}) -> io:format("~p starting.~n", [?MODULE]), maybe_register_globally(CheckF), {ok, #state{reg_function = RegisterF}}. %%---------------------------------------------------------------------- %% Func: handle_call/3 %% Returns: {reply, Reply, State} | %% {reply, Reply, State, Timeout} | %% {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, Reply, State} | (terminate/2 is called) %% {stop, Reason, State} (terminate/2 is called) %%---------------------------------------------------------------------- handle_call(get_value, _From, #state{value = V} = State) -> {reply, V, State}; handle_call({set_value, V}, _From, #state{value = OldVal} = State) -> {reply, {ok, OldVal}, State#state{value = V}}; %% Bonus example: We implement takeover of the server state. %% ------ %% retiring side handle_call(takeover_state, {Pid,_}=From, #state{value = V} = _State) -> %% first, report your state (actually, only the value attribute, since %% the other parts of the state should be considered private.) gen_server:reply(From, {ok, V}), %% then, relay all messages that happen to come in. relay(Pid); %% starting side (the one that takes over) handle_call({takeover, FromNode}, _From, #state{reg_function = RegF} = State) -> %% First, claim the global name. This will cause new requests to come %% to you. We also call the mysterious registration function that adds %% a persistent identifier to signify that this node is where the active %% copy of this application runs. %% All this is run within a transaction covering the two involved nodes. %% This is to make sure that a restarting server on the retiring node %% doesn't steal back the globally registered name. Transaction = fun() -> RegF(), global:re_register_name(?MODULE, self()), %% Then, call the retiring server and retrieve state. %% This message will be handled _after_ the server has %% finished processed any requests that came in before %% you claimed the global name (since gen_server process %% messages in order.) gen_server:call({?MODULE, FromNode}, takeover_state) end, {ok, NewValue} = global:trans(lock_id(), Transaction, [node(), FromNode]), %% Now, we're ready to handle incoming requests. {reply, ok, State#state{value = NewValue}}; %% The "go" function. We register a global name here, meaning that we %% are now ready to handle calls. handle_call({go, {takeover,_}}, _From, State) -> {reply, ok, State}; handle_call({go, StartType}, _From, State) -> io:format("handle_call({go, ~p},...)~n", [StartType]), global:re_register_name(?MODULE, self()), {reply, ok, State}. %% ------ %% End of takeover code (see also relay/1 below) %%---------------------------------------------------------------------- %% Func: handle_cast/2 %% Returns: {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, State} (terminate/2 is called) %%---------------------------------------------------------------------- %% My recommendation: do not consume unknown casts. There should be no such %% thing. handle_cast(Msg, State) -> {stop, {unknown_cast, Msg}, State}. %%---------------------------------------------------------------------- %% Func: handle_info/2 %% Returns: {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, State} (terminate/2 is called) %%---------------------------------------------------------------------- %% My recommendation: You should probably consume unknown messages, but %% print them, or log them, or something; chances are it's an error. handle_info(Info, State) -> io:format("unknown msg (~p)~n", [Info]), {noreply, State}. %%---------------------------------------------------------------------- %% Func: code_change/3 %% Purpose: Transform state during code upgrade %% Returns: {ok, NewState}. %% %% Good rule of thumb: always include a code_change function that, at a %% minimum, does nothing (i.e. returns the old state). If you let the %% release_handler run through a full upgrade, a server that doesn't %% implement a working code_change() function will crash the upgrade %% (unless, of course, it's excluded from the upgrade or handled like an %% exception. This is harder to do than to implement the standard code_change %% callback below.) Note that the code_change function differs slightly for %% gen_server, gen_fsm, etc. You have to read the manual... %%---------------------------------------------------------------------- code_change(_OldVsn, State, _Extra) -> {ok, State}. %%---------------------------------------------------------------------- %% Func: terminate/2 %% Purpose: Shutdown the server %% Returns: any (ignored by gen_server) %%---------------------------------------------------------------------- terminate(_Reason, _State) -> ok. %%%---------------------------------------------------------------------- %%% Internal functions %%%---------------------------------------------------------------------- %% Here's the problem: If the globally registered server crashes and %% restarts, it must re-register itself _except_ if a takeover is in %% progress. The dreaded scenario is this: %% 1) Server Sa on node Na is supposed to migrate to node Nb. %% 2) A new instance Sb starts on Nb, and registers itself globally %% 3) Sb calls on Sa to transfer state; state is transfered %% 4) Sa crashes, and restarts before the application is terminated on Na %% 5) Upon restarting, it registers itself globally, and dies immediately %% afterwards. %% Now, nobody has the global name. %% One can get some protection from this by re-registering Sb globally right %% before acknowledging that the application is started on Nb, but this is %% not safe. %% %% We introduce a critical region. When the server is restarted, it calls %% a check function within a transaction. The check function looks for %% a globally registered name on this node, in this case. If it finds it, %% it will register globally -- otherwise not. The name it looks for is %% registered to the top supervisor of our application, because we know %% that it will live as long as the application is running. %% During takeover, this global name is moved over to the top supervisor %% of the new application instance. This is also done within a transaction %% using the same critical region. %% maybe_register_globally(CheckF) when is_function(CheckF) -> Transaction = fun() -> case CheckF() of true -> register_globally(); false -> false end end, global:trans(lock_id(), Transaction, [node()]). register_globally() -> %% We use global:re_register_name/2 because we want it to work %% even after a process restart, and during a takeover. %% global:register_name/2 will refuse to register the name if it has %% already been registered. global:re_register_name(?MODULE, self()). lock_id() -> {?MODULE, self()}. %% This is not really the Good way to do it. There should be a gen:relay/1 %% function that does the job. This is no big deal, since the process will %% die pretty quickly in most cases (immediately after takeover is done.) relay(Pid) -> receive Msg -> Pid ! Msg, relay(Pid) end.