1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2000-2020. All Rights Reserved.
5%%
6%% Licensed under the Apache License, Version 2.0 (the "License");
7%% you may not use this file except in compliance with the License.
8%% You may obtain a copy of the License at
9%%
10%%     http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing, software
13%% distributed under the License is distributed on an "AS IS" BASIS,
14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15%% See the License for the specific language governing permissions and
16%% limitations under the License.
17%%
18%% %CopyrightEnd%
19%%
20
21%%
22%%----------------------------------------------------------------------
23%% Purpose: Monitor connections and timers
24%%----------------------------------------------------------------------
25
26-module(megaco_monitor).
27
28-behaviour(gen_server).
29
30
31%%-----------------------------------------------------------------
32%% Include files
33%%-----------------------------------------------------------------
34
35-include_lib("megaco/src/app/megaco_internal.hrl").
36
37
38%% Application internal exports
39-export([
40	 start_link/0,
41	 stop/0,
42
43	 apply_after/4,
44	 apply_after/5,
45	 cancel_apply_after/1,
46
47	 lookup_request/1,
48	 lookup_request_field/2,
49	 match_requests/1,
50	 which_requests/1,
51	 insert_request/1,
52	 update_request_field/3, update_request_fields/2,
53	 delete_request/1,
54
55	 request_lockcnt_cre/1,
56	 request_lockcnt_del/1,
57	 request_lockcnt_inc/1,
58	 request_lockcnt_dec/1,
59
60	 lookup_reply/1,
61	 lookup_reply_field/2,
62	 match_replies/1,
63	 which_replies/1,
64	 insert_reply/1, insert_reply_new/1,
65	 update_reply_field/3, update_reply_fields/2,
66	 delete_reply/1,
67
68	 apply_at_exit/4,
69	 cancel_apply_at_exit/1
70	]).
71
72%% gen_server callbacks
73-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
74	 terminate/2, code_change/3]).
75
76-define(SERVER, ?MODULE).
77-record(state, {parent_pid}).
78-record(apply_at_exit, {ref, pid, module, function, arguments}).
79
80
81
82%%%----------------------------------------------------------------------
83%%% API
84%%%----------------------------------------------------------------------
85start_link() ->
86    ?d("start -> entry", []),
87    gen_server:start_link({local, ?SERVER}, ?MODULE, [self()], []).
88
89stop() ->
90    call(stop).
91
92lookup_request(Key) ->
93    ets:lookup(megaco_requests, Key).
94
95lookup_request_field(Key, Field) ->
96    try
97	begin
98	    {ok, ets:lookup_element(megaco_requests, Key, Field)}
99	end
100    catch
101	error:badarg ->
102	    {error, not_found}
103    end.
104
105match_requests(Pat) ->
106    ets:match_object(megaco_requests, Pat).
107
108which_requests(Pat) ->
109    Spec = [{Pat, [], ['$$']}],
110    ets:select(megaco_requests, Spec).
111
112insert_request(Rec) ->
113    ets:insert(megaco_requests, Rec).
114
115update_request_field(Key, Field, NewValue) ->
116    ets:update_element(megaco_requests, Key, {Field, NewValue}).
117
118update_request_fields(Key, NewFields) when is_list(NewFields) ->
119    ets:update_element(megaco_requests, Key, NewFields).
120
121delete_request(Key) ->
122    ets:delete(megaco_requests, Key).
123
124
125request_lockcnt_cre(TransId) ->
126    Key = {TransId, lockcnt},
127    ets:insert_new(megaco_requests, {Key, 1}).
128
129request_lockcnt_del(TransId) ->
130    Key = {TransId, lockcnt},
131    ets:delete(megaco_requests, Key).
132
133request_lockcnt_inc(TransId) ->
134    Key = {TransId, lockcnt},
135    (catch ets:update_counter(megaco_requests, Key, 1)).
136
137request_lockcnt_dec(TransId) ->
138    Key = {TransId, lockcnt},
139    (catch ets:update_counter(megaco_requests, Key, -1)).
140
141
142lookup_reply(Key) ->
143    ets:lookup(megaco_replies, Key).
144
145lookup_reply_field(Key, Field) ->
146    try
147	begin
148	    {ok, ets:lookup_element(megaco_replies, Key, Field)}
149	end
150    catch
151	error:badarg ->
152	    {error, not_found}
153    end.
154
155match_replies(Pat) ->
156    ets:match_object(megaco_replies, Pat).
157
158which_replies(Pat) ->
159    Spec = [{Pat, [], ['$$']}],
160    ets:select(megaco_replies, Spec).
161
162insert_reply(Rec) ->
163    ets:insert(megaco_replies, Rec).
164
165insert_reply_new(Rec) ->
166    ets:insert_new(megaco_replies, Rec).
167
168update_reply_field(Key, Field, NewValue) ->
169    ets:update_element(megaco_replies, Key, {Field, NewValue}).
170
171update_reply_fields(Key, NewFields) when is_list(NewFields) ->
172    ets:update_element(megaco_replies, Key, NewFields).
173
174delete_reply(Key) ->
175    ets:delete(megaco_replies, Key).
176
177apply_after(M, F, A, Time) ->
178    apply_after(spawn_method, M, F, A, Time).
179
180apply_after(Method, M, F, A, Time)
181  when is_atom(M) andalso is_atom(F) andalso is_list(A) ->
182    if
183	Time =:= infinity ->
184	    apply_after_infinity;
185	is_integer(Time) ->
186	    Msg = {apply_after, Method, M, F, A},
187	    Ref = erlang:send_after(Time, whereis(?SERVER), Msg),
188	    {apply_after, Ref}
189    end.
190
191cancel_apply_after({apply_after, Ref}) ->
192    case erlang:cancel_timer(Ref) of
193	TimeLeft when is_integer(TimeLeft) ->
194	    {ok, TimeLeft};
195	_ ->
196	    {error, {already_expired, Ref}}
197    end;
198cancel_apply_after(apply_after_infinity) ->
199    ok;
200cancel_apply_after(BadRef) ->
201    {error, {bad_ref, BadRef}}.
202
203%% Performs apply(M, F, [Reason | A]) when process Pid dies
204apply_at_exit(M, F, A, Pid)
205  when is_atom(M) andalso is_atom(F) andalso is_list(A) andalso is_pid(Pid) ->
206    Ref = call({apply_at_exit, M, F, A, Pid}),
207    {apply_at_exit, Ref}.
208
209cancel_apply_at_exit({apply_at_exit, Ref}) ->
210    cast({cancel_apply_at_exit, Ref});
211cancel_apply_at_exit(BadRef) ->
212    {error, {bad_ref, BadRef}}.
213
214call(Request) ->
215    gen_server:call(?SERVER, Request, infinity).
216
217cast(Msg) ->
218    ?SERVER ! Msg, ok.
219
220%%%----------------------------------------------------------------------
221%%% Callback functions from gen_server
222%%%----------------------------------------------------------------------
223
224%%----------------------------------------------------------------------
225%% Func: init/1
226%% Returns: {ok, State}          |
227%%          {ok, State, Timeout} |
228%%          ignore               |
229%%          {stop, Reason}
230%%----------------------------------------------------------------------
231
232init([Parent]) ->
233    ?d("init -> entry", []),
234    process_flag(trap_exit, true),
235    ets:new(megaco_requests, [public, named_table, {keypos, 2}]),
236    ets:new(megaco_replies,  [public, named_table, {keypos, 2}]),
237    ?d("init -> done", []),
238    {ok, #state{parent_pid = Parent}}.
239
240
241%%----------------------------------------------------------------------
242%% Func: handle_call/3
243%% Returns: {reply, Reply, State}          |
244%%          {reply, Reply, State, Timeout} |
245%%          {noreply, State}               |
246%%          {noreply, State, Timeout}      |
247%%          {stop, Reason, Reply, State}   | (terminate/2 is called)
248%%          {stop, Reason, State}            (terminate/2 is called)
249%%----------------------------------------------------------------------
250
251handle_call({apply_at_exit, M, F, A, Pid}, _From, S) ->
252    Ref = erlang:monitor(process, Pid),
253    AAE = #apply_at_exit{ref       = Ref,
254			 pid       = Pid,
255			 module    = M,
256			 function  = F,
257			 arguments = A},
258    put({?MODULE, Ref}, AAE),
259    Reply = Ref,
260    {reply, Reply, S};
261
262handle_call(stop, {Parent, _} = _From, #state{parent_pid = Parent} = S) ->
263    {stop, normal, ok, S};
264
265handle_call(Req, From, S) ->
266    warning_msg("received unexpected request from ~p: "
267		"~n~w",[From, Req]),
268    {reply, {error, {bad_request, Req}}, S}.
269
270
271%%----------------------------------------------------------------------
272%% Func: handle_cast/2
273%% Returns: {noreply, State}          |
274%%          {noreply, State, Timeout} |
275%%          {stop, Reason, State}            (terminate/2 is called)
276%%----------------------------------------------------------------------
277handle_cast(Msg, S) ->
278    warning_msg("received unexpected message: "
279		"~n~w", [Msg]),
280    {noreply, S}.
281
282%%----------------------------------------------------------------------
283%% Func: handle_info/2
284%% Returns: {noreply, State}          |
285%%          {noreply, State, Timeout} |
286%%          {stop, Reason, State}            (terminate/2 is called)
287%%----------------------------------------------------------------------
288
289handle_info({cancel_apply_at_exit, Ref}, S) ->
290    case erase({?MODULE, Ref}) of
291	undefined ->
292	    %% Reply = {error, {already_cancelled, {apply_at_exit, Ref}}},
293	    {noreply, S};
294	_AAE ->
295	    erlang:demonitor(Ref),
296	    {noreply, S}
297    end;
298
299handle_info({apply_after, Method, M, F, A}, S) ->
300    handle_apply(Method, M, F, A, apply_after),
301    {noreply, S};
302
303%% Handle the old format also...
304handle_info({apply_after, M, F, A}, S) ->
305    handle_apply(M, F, A, apply_after),
306    {noreply, S};
307
308handle_info({'DOWN', Ref, process, _Pid, Reason}, S) ->
309    case erase({?MODULE, Ref}) of
310	undefined ->
311	    {noreply, S};
312	AAE ->
313	    M = AAE#apply_at_exit.module,
314	    F = AAE#apply_at_exit.function,
315	    A = AAE#apply_at_exit.arguments,
316	    handle_apply(M, F, [Reason | A], apply_at_exit),
317	    {noreply, S}
318    end;
319
320handle_info({'EXIT', Pid, Reason}, S) when Pid == S#state.parent_pid ->
321    %% [megaco_messenger:disconnect(CH, {stopped, Reason})
322    %% 	|| CH <- megaco:lookup_system_info(connections)],
323    {stop, Reason, S};
324
325handle_info(Info, S) ->
326    warning_msg("received unknown info: "
327		"~n~w", [Info]),
328    {noreply, S}.
329
330
331%%----------------------------------------------------------------------
332%% Func: terminate/2
333%% Purpose: Shutdown the server
334%% Returns: any (ignored by gen_server)
335%%----------------------------------------------------------------------
336terminate(_Reason, _State) ->
337    ok.
338
339%%----------------------------------------------------------------------
340%% Func: code_change/3
341%% Purpose: Convert process state when code is changed
342%% Returns: {ok, NewState}
343%%----------------------------------------------------------------------
344code_change(_Vsn, S, _Extra) ->
345    {ok, S}.
346
347
348%%%----------------------------------------------------------------------
349%%% Internal functions
350%%%----------------------------------------------------------------------
351
352handle_apply(M, F, A, _ErrorTag) ->
353    spawn(M, F, A).
354
355handle_apply(spawn_method, M, F, A, _ErrorTag) ->
356    spawn(M, F, A);
357handle_apply(_Method, M, F, A, _ErrorTag) ->
358    (catch apply(M, F, A)).
359
360
361warning_msg(F, A) ->
362    ?megaco_warning("Monitor server: " ++ F, A).
363
364
365% d(F) ->
366%     d(F,[]).
367
368% d(F,A) ->
369%     %% d(true,F,A).
370%     d(get(dbg),F,A).
371
372% d(true,F,A) ->
373%     io:format("*** [~s] ~p:~p ***"
374% 	      "~n   " ++ F ++ "~n",
375% 	      [format_timestamp(now()), self(),?MODULE|A]);
376% d(_, _, _) ->
377%     ok.
378
379% format_timestamp(Now) ->
380%     {N1, N2, N3}   = Now,
381%     {Date, Time}   = calendar:now_to_datetime(Now),
382%     {YYYY,MM,DD}   = Date,
383%     {Hour,Min,Sec} = Time,
384%     FormatDate =
385%         io_lib:format("~.4w:~.2.0w:~.2.0w ~.2.0w:~.2.0w:~.2.0w 4~w",
386%                       [YYYY,MM,DD,Hour,Min,Sec,round(N3/1000)]),
387%     lists:flatten(FormatDate).
388
389
390