global.erl
来自「OTP是开放电信平台的简称」· ERL 代码 · 共 1,642 行 · 第 1/5 页
ERL
1,642 行
false=Reply when Retries =:= 0 -> Reply; false -> random_sleep(Times), set_lock(Id, Nodes, dec(Retries), Times+1) end.del_lock(Id) -> del_lock(Id, [node() | nodes()]).del_lock({ResourceId, LockRequesterId}, Nodes) -> Id = {ResourceId, LockRequesterId}, ?trace({del_lock, {me,self()}, {ResourceId,LockRequesterId}, {nodes,Nodes}}), gen_server:multi_call(Nodes, global_name_server, {del_lock, Id}), true.trans(Id, Fun) -> trans(Id, Fun, [node() | nodes()], infinity).trans(Id, Fun, Nodes) -> trans(Id, Fun, Nodes, infinity).trans(Id, Fun, Nodes, Retries) -> case set_lock(Id, Nodes, Retries) of true -> try Fun() after del_lock(Id, Nodes) end; false -> aborted end.info() -> gen_server:call(global_name_server, info, infinity).%%%-----------------------------------------------------------------%%% Call-back functions from gen_server%%%-----------------------------------------------------------------init([]) -> process_flag(trap_exit, true), _ = ets:new(global_locks, [set, named_table, protected]), _ = ets:new(global_names, [set, named_table, protected]), _ = ets:new(global_names_ext, [set, named_table, protected]), _ = ets:new(global_pid_names, [bag, named_table, protected]), _ = ets:new(global_pid_ids, [bag, named_table, protected]), %% This is for troubleshooting only. DoTrace = os:getenv("GLOBAL_HIGH_LEVEL_TRACE") =:= "TRUE", T0 = case DoTrace of true -> send_high_level_trace(), []; false -> no_trace end, S = #state{the_locker = start_the_locker(DoTrace), trace = T0, the_deleter = start_the_deleter(self()), the_registrar = start_the_registrar()}, S1 = trace_message(S, {init, node()}, []), case init:get_argument(connect_all) of {ok, [["false"]]} -> {ok, S1#state{connect_all = false}}; _ -> {ok, S1#state{connect_all = true}} end.%%-----------------------------------------------------------------%% Connection algorithm%% ====================%% This algorithm solves the problem with partitioned nets as well.%%%% The main idea in the algorithm is that when two nodes connect, they%% try to set a lock in their own partition (i.e. all nodes already%% known to them; partitions are not necessarily disjoint). When the%% lock is set in each partition, these two nodes send each other a%% list with all registered names in resp partition (*). If no conflict%% is found, the name tables are just updated. If a conflict is found,%% a resolve function is called once for each conflict. The result of%% the resolving is sent to the other node. When the names are%% exchanged, all other nodes in each partition are informed of the%% other nodes, and they ping each other to form a fully connected%% net.%%%% A few remarks:%% %% (*) When this information is being exchanged, no one is allowed to%% change the global register table. All calls to register etc are%% protected by a lock. If a registered process dies during this%% phase the name is unregistered on the local node immediately,%% but the unregistration on other nodes will take place when the%% deleter manages to acquire the lock. This is necessary to%% prevent names from spreading to nodes where they cannot be%% deleted.%%%% - It is assumed that nodeups and nodedowns arrive in an orderly%% fashion: for every node, nodeup is followed by nodedown, and vice%% versa. "Double" nodeups and nodedowns must never occur. It is%% the responsibility of net_kernel to assure this.%%%% - There is always a delay between the termination of a registered%% process and the removal of the name from Global's tables. This%% delay can sometimes be quite substantial. Global guarantees that%% the name will eventually be removed, but there is no%% synchronization between nodes; the name can be removed from some%% node(s) long before it is removed from other nodes. Using%% safe_whereis_name is no cure.%%%% - Global cannot handle problems with the distribution very well.%% Depending on the value of the kernel variable 'net_ticktime' long%% delays may occur. This does not affect the handling of locks but%% will block name registration.%% %% - Old synch session messages may linger on in the message queue of%% global_name_server after the sending node has died. The tags of%% such messages do not match the current tag (if there is one),%% which makes it possible to discard those messages and cancel the%% corresponding lock.%%%% Suppose nodes A and B connect, and C is connected to A.%% Here's the algorithm's flow:%%%% Node A%% ------%% << {nodeup, B}%% TheLocker ! {nodeup, ..., Node, ...} (there is one locker per node)%% B ! {init_connect, ..., {..., TheLockerAtA, ...}}%% << {init_connect, TheLockerAtB}%% [The lockers try to set the lock]%% << {lock_is_set, B, ...}%% [Now, lock is set in both partitions]%% B ! {exchange, A, Names, ...}%% << {exchange, B, Names, ...}%% [solve conflict]%% B ! {resolved, A, ResolvedA, KnownAtA, ...}%% << {resolved, B, ResolvedB, KnownAtB, ...}%% C ! {new_nodes, ResolvedAandB, [B]}%%%% Node C%% ------%% << {new_nodes, ResolvedOps, NewNodes}%% [insert Ops]%% ping(NewNodes)%% << {nodeup, B}%% <ignore this one>%%%% Several things can disturb this picture.%%%% First, the init_connect message may arrive _before_ the nodeup%% message due to delay in net_kernel. We handle this by keeping track%% of these messages in the pre_connect variable in our state.%%%% Of course we must handle that some node goes down during the%% connection.%%%%-----------------------------------------------------------------%% Messages in the protocol%% ========================%% 1. Between global_name_servers on connecting nodes%% {init_connect, Vsn, Node, InitMsg}%% InitMsg = {locker, _Unused, HisKnown, HisTheLocker}%% {exchange, Node, ListOfNames, _ListOfNamesExt, Tag}%% {resolved, Node, HisOps, HisKnown, _Unused, ListOfNamesExt, Tag}%% HisKnown = list of known nodes in Node's partition%% 2. Between lockers on connecting nodes%% {his_locker, Pid} (from our global)%% {lock, Bool} loop until both lockers have lock = true,%% then send to global_name_server {lock_is_set, Node, Tag}%% 3. Connecting node's global_name_server informs other nodes in the same %% partition about hitherto unknown nodes in the other partition%% {new_nodes, Node, Ops, ListOfNamesExt, NewNodes, ExtraInfo}%% 4. Between global_name_server and resolver%% {resolve, NameList, Node} to resolver%% {exchange_ops, Node, Tag, Ops, Resolved} from resolver%% 5. sync protocol, between global_name_servers in different partitions%% {in_sync, Node, IsKnown}%% sent by each node to all new nodes (Node becomes known to them)%%-----------------------------------------------------------------handle_call({whereis, Name}, From, S) -> do_whereis(Name, From), {noreply, S};handle_call({registrar, Fun}, From, S) -> S#state.the_registrar ! {trans_all_known, Fun, From}, {noreply, S};%% The pattern {register,'_','_','_'} is traced by the inviso%% application. Do not change.handle_call({register, Name, Pid, Method}, {FromPid, _Tag}, S0) -> S = ins_name(Name, Pid, Method, FromPid, [], S0), {reply, yes, S};handle_call({unregister, Name}, _From, S0) -> S = delete_global_name2(Name, S0), {reply, ok, S};handle_call({register_ext, Name, Pid, Method, RegNode}, {FromPid,_Tag}, S0) -> S = ins_name_ext(Name, Pid, Method, RegNode, FromPid, [], S0), {reply, yes, S};handle_call({set_lock, Lock}, {Pid, _Tag}, S0) -> {Reply, S} = handle_set_lock(Lock, Pid, S0), {reply, Reply, S};handle_call({del_lock, Lock}, {Pid, _Tag}, S0) -> S = handle_del_lock(Lock, Pid, S0), {reply, true, S};handle_call(get_known, _From, S) -> {reply, S#state.known, S};handle_call(get_synced, _From, S) -> {reply, S#state.synced, S};handle_call({sync, Nodes}, From, S) -> %% If we have several global groups, this won't work, since we will %% do start_sync on a nonempty list of nodes even if the system %% is quiet. Pid = start_sync(lists:delete(node(), Nodes) -- S#state.synced, From), {noreply, S#state{syncers = [Pid | S#state.syncers]}};handle_call(get_protocol_version, _From, S) -> {reply, ?vsn, S};handle_call(get_names_ext, _From, S) -> {reply, get_names_ext(), S};handle_call(info, _From, S) -> {reply, S, S};%% "High level trace". For troubleshooting only.handle_call(high_level_trace_start, _From, S) -> S#state.the_locker ! {do_trace, true}, send_high_level_trace(), {reply, ok, trace_message(S#state{trace = []}, {init, node()}, [])};handle_call(high_level_trace_stop, _From, S) -> #state{the_locker = TheLocker, trace = Trace} = S, TheLocker ! {do_trace, false}, wait_high_level_trace(), {reply, Trace, S#state{trace = no_trace}};handle_call(high_level_trace_get, _From, #state{trace = Trace}=S) -> {reply, Trace, S#state{trace = []}};handle_call(stop, _From, S) -> {stop, normal, stopped, S};handle_call(Request, From, S) -> error_logger:warning_msg("The global_name_server " "received an unexpected message:\n" "handle_call(~p, ~p, _)\n", [Request, From]), {noreply, S}.%%========================================================================%% init_connect%%%%========================================================================handle_cast({init_connect, Vsn, Node, InitMsg}, S) -> %% Sent from global_name_server at Node. ?trace({'####', init_connect, {vsn, Vsn}, {node,Node},{initmsg,InitMsg}}), case Vsn of %% It is always the responsibility of newer versions to understand %% older versions of the protocol. {HisVsn, HisTag} when HisVsn > ?vsn -> init_connect(?vsn, Node, InitMsg, HisTag, S#state.resolvers, S); {HisVsn, HisTag} -> init_connect(HisVsn, Node, InitMsg, HisTag, S#state.resolvers, S); %% To be future compatible Tuple when is_tuple(Tuple) -> List = tuple_to_list(Tuple), [_HisVsn, HisTag | _] = List, %% use own version handling if his is newer. init_connect(?vsn, Node, InitMsg, HisTag, S#state.resolvers, S); _ -> Txt = io_lib:format("Illegal global protocol version ~p Node: ~p\n", [Vsn, Node]), error_logger:info_report(lists:flatten(Txt)) end, {noreply, S};%%=======================================================================%% lock_is_set%%%% Ok, the lock is now set on both partitions. Send our names to other node.%%=======================================================================handle_cast({lock_is_set, Node, MyTag, LockId}, S) -> %% Sent from the_locker at node(). ?trace({'####', lock_is_set , {node,Node}}), case get({sync_tag_my, Node}) of MyTag -> lock_is_set(Node, S#state.resolvers, LockId), {noreply, S}; _ -> %% Illegal tag, delete the old sync session. NewS = cancel_locker(Node, S, MyTag), {noreply, NewS} end;%%========================================================================%% exchange%%%% Here the names are checked to detect name clashes.%%========================================================================handle_cast({exchange, Node, NameList, _NameExtList, MyTag}, S) -> %% Sent from global_name_server at Node. case get({sync_tag_my, Node}) of MyTag -> exchange(Node, NameList, S#state.resolvers), {noreply, S}; _ -> %% Illegal tag, delete the old sync session. NewS = cancel_locker(Node, S, MyTag), {noreply, NewS} end;%% {exchange_ops, ...} is sent by the resolver process (which then%% dies). It could happen that {resolved, ...} has already arrived%% from the other node. In that case we can go ahead and run the%% resolve operations. Otherwise we have to save the operations and%% wait for {resolve, ...}. This is very much like {lock_is_set, ...}%% and {exchange, ...}.handle_cast({exchange_ops, Node, MyTag, Ops, Resolved}, S0) -> %% Sent from the resolver for Node at node(). ?trace({exchange_ops, {node,Node}, {ops,Ops},{resolved,Resolved}, {mytag,MyTag}}), S = trace_message(S0, {exit_resolver, Node}, [MyTag]),
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?