1 |
|
%% Generates a unique ID on the node start. |
2 |
|
%% Registers the ID on all other nodes. |
3 |
|
%% Used in ejabberd_local to find to which node to route IQ responses. |
4 |
|
-module(mongoose_start_node_id). |
5 |
|
-behaviour(gen_server). |
6 |
|
|
7 |
|
%% API |
8 |
|
-export([start_link/0]). |
9 |
|
-export([node_id/0, node_id_to_name/1]). |
10 |
|
-export([register_on_remote_node_rpc/3]). |
11 |
|
|
12 |
|
%% gen_server callbacks |
13 |
|
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, |
14 |
|
terminate/2, code_change/3]). |
15 |
|
|
16 |
|
-ignore_xref([start_link/0, register_on_remote_node_rpc/3]). |
17 |
|
|
18 |
|
-include("mongoose.hrl"). |
19 |
|
-include("mongoose_logger.hrl"). |
20 |
|
|
21 |
|
-type id() :: binary(). |
22 |
|
|
23 |
|
-record(state, {start_id :: id(), mon_ref_to_start_id :: map()}). |
24 |
|
-define(KEY, ?MODULE). |
25 |
|
|
26 |
|
-spec node_id() -> id(). |
27 |
|
node_id() -> |
28 |
3 |
persistent_term:get(?KEY). |
29 |
|
|
30 |
|
-spec node_id_to_name(id()) -> {ok, node()} | {error, unknown_id}. |
31 |
|
node_id_to_name(ID) -> |
32 |
4 |
case persistent_term:get({?KEY, ID}, undefined) of |
33 |
|
undefined -> |
34 |
1 |
{error, unknown_id}; |
35 |
|
Name -> |
36 |
3 |
{ok, Name} |
37 |
|
end. |
38 |
|
|
39 |
|
-spec start_link() -> {ok, pid()}. |
40 |
|
start_link() -> |
41 |
92 |
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). |
42 |
|
|
43 |
|
init(_) -> |
44 |
92 |
net_kernel:monitor_nodes(true), |
45 |
92 |
StartId = mongoose_bin:gen_from_crypto(), |
46 |
92 |
persistent_term:put(mongoose_start_node_id, StartId), |
47 |
92 |
[register_on_remote_node(RemoteNode, StartId) |
48 |
92 |
|| RemoteNode <- [node()|nodes()]], |
49 |
92 |
{ok, #state{start_id = StartId, mon_ref_to_start_id = #{}}}. |
50 |
|
|
51 |
|
handle_call(_Request, _From, State) -> |
52 |
:-( |
{reply, ok, State}. |
53 |
|
|
54 |
|
handle_cast({register_cleaning_task, StartId, RemotePid}, |
55 |
|
State = #state{mon_ref_to_start_id = Mon2StartId}) -> |
56 |
217 |
MonRef = erlang:monitor(process, RemotePid), |
57 |
217 |
Mon2StartId2 = maps:put(MonRef, StartId, Mon2StartId), |
58 |
217 |
{noreply, State#state{mon_ref_to_start_id = Mon2StartId2}}; |
59 |
|
handle_cast(_Msg, State) -> |
60 |
:-( |
{noreply, State}. |
61 |
|
|
62 |
|
handle_info({nodeup, RemoteNode}, State = #state{start_id = StartId}) -> |
63 |
10 |
register_on_remote_node(RemoteNode, StartId), |
64 |
10 |
{noreply, State}; |
65 |
|
handle_info({'DOWN', MonRef, process, RemotePid, Reason}, |
66 |
|
State = #state{mon_ref_to_start_id = Mon2StartId}) -> |
67 |
85 |
case maps:get(MonRef, Mon2StartId, undefined) of |
68 |
|
undefined -> |
69 |
:-( |
?LOG_ERROR(#{what => node_id_unexpected_monitor, |
70 |
|
reason => Reason, |
71 |
|
monitor_ref => MonRef, |
72 |
|
remote_pid => RemotePid, |
73 |
:-( |
remote_node => node(RemotePid)}); |
74 |
|
StartId -> |
75 |
85 |
persistent_term:erase({?KEY, StartId}), |
76 |
85 |
?LOG_WARNING(#{what => node_id_node_down, |
77 |
|
reason => Reason, |
78 |
|
monitor_ref => MonRef, |
79 |
|
remote_pid => RemotePid, |
80 |
:-( |
remote_node => node(RemotePid)}) |
81 |
|
end, |
82 |
|
%% We use pid monitors instead of node monitors to avoid cleaning |
83 |
|
%% start id when a node is restarting and reappearing very quicky. |
84 |
|
%% I.e. node name could be reused by a newly started node, while Refs - not. |
85 |
|
%% Pids could be also reused, but collisions are rare. |
86 |
85 |
{noreply, State#state{mon_ref_to_start_id = maps:remove(MonRef, Mon2StartId)}}; |
87 |
|
handle_info(_Info, State) -> |
88 |
8 |
{noreply, State}. |
89 |
|
|
90 |
|
terminate(_Reason, _State) -> |
91 |
:-( |
ok. |
92 |
|
code_change(_OldVsn, State, _Extra) -> |
93 |
:-( |
{ok, State}. |
94 |
|
|
95 |
|
register_on_remote_node(RemoteNode, StartId) -> |
96 |
214 |
Res = rpc:call(RemoteNode, ?MODULE, register_on_remote_node_rpc, |
97 |
|
[node(), StartId, self()]), |
98 |
214 |
case Res of |
99 |
|
ok -> |
100 |
214 |
ok; |
101 |
|
_ -> |
102 |
:-( |
?LOG_ERROR(#{what => node_id_register_on_remote_node_failed, |
103 |
:-( |
remote_node => RemoteNode, reason => Res}) |
104 |
|
end. |
105 |
|
|
106 |
|
register_on_remote_node_rpc(RemoteNode, StartId, RemotePid) -> |
107 |
217 |
persistent_term:put({?KEY, StartId}, RemoteNode), |
108 |
217 |
gen_server:cast(?MODULE, {register_cleaning_task, StartId, RemotePid}), |
109 |
217 |
ok. |