Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 294 lines (257 sloc) 9.189 kb
6290da9 @andrewtj hello world
andrewtj authored
1 %% -------------------------------------------------------------------
2 %%
3 %% Copyright (c) 2012 Andrew Tunnell-Jones. All Rights Reserved.
4 %%
5 %% This file is provided to you under the Apache License,
6 %% Version 2.0 (the "License"); you may not use this file
7 %% except in compliance with the License. You may obtain
8 %% 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,
13 %% software distributed under the License is distributed on an
14 %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 %% KIND, either express or implied. See the License for the
16 %% specific language governing permissions and limitations
17 %% under the License.
18 %%
19 %% -------------------------------------------------------------------
20 %% @private
21 -module(sighandler_server).
22 -behaviour(gen_server).
23 -include("sighandler.hrl").
24
25 %% API
26 -export([start_link/0, stop/0, install/2, remove/1, remove/2, lookup_term/1]).
27
28 %% gen_server callbacks
29 -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
30 terminate/2, code_change/3]).
31
32 -record(state, {port :: port(), handlers = dict:new()}).
33
34 -define(SERVER, ?MODULE).
35 -define(TAB_TERM, sighandler_term).
36
37 %%%===================================================================
38 %%% API
39 %%%===================================================================
40
41 install(Sig, Fun) when ?SH_IS_SIG_FUN(Sig, Fun) ->
42 gen_server:call(?SERVER, {install, Sig, Fun}).
43
44 remove(FunRef) when ?SH_IS_FUNREF(FunRef) ->
45 gen_server:call(?SERVER, {remove, FunRef}).
46
47 remove(Sig, FunRef) when ?SH_IS_SIG_FUNREF(Sig, FunRef) ->
48 gen_server:call(?SERVER, {remove, Sig, FunRef}).
49
50 lookup_term(Term) when is_atom(Term) orelse is_integer(Term) ->
51 case (catch ets:lookup_element(?TAB_TERM, Term, 2)) of
52 {'EXIT', _} -> {error, undefined};
53 Other -> {ok, Other}
54 end.
55
56 start_link() ->
57 gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
58
59 stop() -> gen_server:call(?SERVER, stop).
60
61 %%%===================================================================
62 %%% gen_server callbacks
63 %%%===================================================================
64
65 init([]) ->
66 ok = setup_sig_tab(),
67 Port = sighandler_drv:open(),
68 {ok, #state{port = Port}}.
69
70 handle_call({install, Sig, Fun}, _From, #state{} = State) ->
71 Ref = make_ref(),
72 {Result, State0} = install(Sig, Fun, Ref, State),
73 Reply = case Result of
74 ok -> {ok, Ref};
75 {error, _Reason} = Error -> Error
76 end,
77 {reply, Reply, State0};
78 handle_call({remove, FunRef}, _From, #state{} = State) ->
79 {ok, State0} = find_and_remove(FunRef, State),
80 {reply, ok, State0};
81 handle_call({remove, Sig, Fun}, _From, #state{} = State) ->
82 {ok, State0} = remove(Sig, Fun, State),
83 {reply, ok, State0};
84 handle_call(stop, _From, State) -> {stop, normal, ok, State};
85 handle_call(Request, _From, State) ->
86 error_logger:info_msg(?MODULE_STRING " ~p ignoring call: ~p~n",
87 [self(), Request]),
88 {noreply, State}.
89
90 handle_cast(Msg, State) ->
91 error_logger:info_msg(?MODULE_STRING " ~p ignoring cast: ~p~n",
92 [self(), Msg]),
93 {noreply, State}.
94
95 handle_info({Port, Sig}, #state{port = Port} = State)
96 when is_integer(Sig) ->
97 {ok, State0} = fire(Sig, State),
98 {noreply, State0};
99 handle_info(Info, State) ->
100 error_logger:info_msg(?MODULE_STRING " ~p discarded message: ~p~n",
101 [self(), Info]),
102 {noreply, State}.
103
104 terminate(_Reason, #state{port = Port} = _State) ->
105 ok = sighandler_drv:close(Port).
106
107 code_change(_OldVsn, State, _Extra) -> {ok, State}.
108
109 %%%===================================================================
110 %%% Internal functions
111 %%%===================================================================
112
113 %% sig_tab
114
115 setup_sig_tab() ->
116 ?TAB_TERM = ets:new(?TAB_TERM, [named_table, protected]),
117 Fun = fun({Atom, 0}) when Atom =/= on_load andalso Atom =/= module_info ->
118 case sighandler_nif:Atom() of
119 Int when is_integer(Int) ->
120 Terms = [{Int, Atom}, {Atom, Int}],
121 true = ets:insert(?TAB_TERM, Terms);
122 undefined -> ok
123 end;
124 (_) -> ok end,
125 lists:foreach(Fun, sighandler_nif:module_info(exports)).
126
127 %% State management
128
129 install(Sig, Fun, Ref, #state{handlers = Handlers, port = Port} = State) ->
130 Installed = case sighandler_drv:status(Port, Sig) of
131 active -> installed;
8b06c34 @andrewtj Handle bad signals gracefully
andrewtj authored
132 inactive -> sighandler_drv:toggle(Port, Sig);
133 Other -> Other
6290da9 @andrewtj hello world
andrewtj authored
134 end,
135 case Installed of
136 installed ->
137 Handlers0 = dict:append(Sig, {Ref, Fun}, Handlers),
138 {ok, State#state{handlers = Handlers0}};
139 sig_err = Err -> {{error, Err}, State}
140 end.
141
142 remove(Sig, FunRef, #state{handlers = Handlers, port = Port} = State) ->
143 case dict:is_key(Sig, Handlers) of
144 true ->
145 Funs = dict:fetch(Sig, Handlers),
146 case lists:keydelete(FunRef, remove_pos(FunRef), Funs) of
147 [] ->
148 Handlers0 = dict:erase(Sig, Handlers),
149 removed = sighandler_drv:toggle(Port, Sig),
150 {ok, State#state{handlers = Handlers0}};
151 Funs -> {ok, State};
152 Funs0 ->
153 Handlers0 = dict:store(Sig, Funs0, Handlers),
154 {ok, State#state{handlers = Handlers0}}
155 end;
156 false -> {ok, State}
157 end.
158
159 find_and_remove(FunRef, #state{handlers = Handlers} = State) ->
160 Pos = remove_pos(FunRef),
161 Sigs = dict:fold(fun(Sig, Funs, Acc) ->
162 case lists:keymember(FunRef, Pos, Funs) of
163 true -> [Sig|Acc];
164 false -> Acc
165 end
166 end, [], Handlers),
167 find_and_remove(Sigs, FunRef, State).
168
169 find_and_remove([Sig|Sigs], FunRef, State) ->
170 {ok, State0} = remove(Sig, FunRef, State),
171 find_and_remove(Sigs, FunRef, State0);
172 find_and_remove([], _FunRef, State) -> {ok, State}.
173
174 remove_pos(Ref) when is_reference(Ref) -> 1;
175 remove_pos(Fun) when is_function(Fun) -> 2.
176
177 fire(Sig, #state{handlers = Handlers} = State) ->
178 case dict:is_key(Sig, Handlers) of
179 true ->
180 Funs = dict:fetch(Sig, Handlers),
181 fire(Funs, Sig, State);
182 false -> {ok, State}
183 end.
184
185 fire([{Ref, Fun}|Funs], Sig, #state{} = State) ->
186 Run = fun(F) when is_function(F, 0) -> catch F();
187 (F) when is_function(F, 1) -> catch F(Sig) end,
188 case Run(Fun) of
189 ok -> fire(Funs, Sig, State);
190 _ ->
191 {ok, State0} = remove(Sig, Ref, State),
192 fire(Funs, Sig, State0)
193 end;
194 fire([], _Sig, #state{} = State) -> {ok, State}.
195
196 %% tests
197
198 -ifdef(TEST).
199
200 fire_test() ->
201 ok = application:start(sighandler),
202 Sig = 1,
203 Tab = ets:new(sh_test, [public]),
204 Fun = fun(X) ->
205 io:format("fire_test ~p~n", [X]),
206 ets:insert(Tab, {X}),
207 ok
208 end,
209 {ok, Ref} = sighandler:install(Sig, Fun),
210 _ = os:cmd(io_lib:format("kill -~p ~s", [Sig, os:getpid()])),
211 ok = sighandler:remove(Sig, Ref),
212 Result = (catch ets:lookup(Tab, Sig)),
213 ets:delete(Tab),
214 ok = application:stop(sighandler),
215 ?assertEqual([{Sig}], Result).
216
217 fire_badfun_test() ->
218 ok = application:start(sighandler),
219 Sig = 1,
220 Cmd = io_lib:format("kill -~p ~s", [Sig, os:getpid()]),
221 Tab = ets:new(sh_test, [public]),
222 true = ets:insert(Tab, {Sig, 0}),
223 FunA = fun(X) ->
224 io:format("fire_badfun_test a ~p~n", [X]),
225 ets:update_counter(Tab, X, 1),
226 ok
227 end,
228 {ok, Ref0} = sighandler:install(Sig, FunA),
229 FunB = fun(X) ->
230 io:format("fire_badfun_test b ~p~n", [X]),
231 ets:update_counter(Tab, X, 1),
232 throw(bad)
233 end,
234 {ok, Ref1} = sighandler:install(Sig, FunB),
235 _ = os:cmd(Cmd),
236 _ = os:cmd(Cmd),
237 ok = sighandler:remove(Sig, Ref0),
238 ok = sighandler:remove(Sig, Ref1),
239 Result = (catch ets:lookup(Tab, Sig)),
240 ets:delete(Tab),
241 ok = application:stop(sighandler),
242 ?assertEqual([{Sig, 3}], Result).
243
244 find_and_remove_ref_test() ->
245 ok = application:start(sighandler),
246 Sig = 1,
247 Cmd = io_lib:format("kill -~p ~s", [Sig, os:getpid()]),
248 Tab = ets:new(sh_test, [public]),
249 true = ets:insert(Tab, {Sig, 0}),
250 Fun = fun(X) ->
251 io:format("find_and_remove_ref_test ~p~n", [X]),
252 ets:update_counter(Tab, X, 1),
253 ok
254 end,
255 {ok, Ref0} = sighandler:install(Sig, Fun),
256 {ok, Ref1} = sighandler:install(Sig, Fun),
257 ok = sighandler:remove(Sig, Ref0),
258 _ = os:cmd(Cmd),
259 _ = os:cmd(Cmd),
260 ok = sighandler:remove(Ref1),
261 Result = (catch ets:lookup(Tab, Sig)),
262 ets:delete(Tab),
263 ok = application:stop(sighandler),
264 ?assertEqual([{Sig, 2}], Result).
265
266 find_and_remove_fun_test() ->
267 ok = application:start(sighandler),
268 Sig = 1,
269 Cmd = io_lib:format("kill -~p ~s", [Sig, os:getpid()]),
270 Tab = ets:new(sh_test, [public]),
271 true = ets:insert(Tab, {Sig, 0}),
272 Fun0 = fun(X) ->
273 io:format("find_and_remove_fun_test a ~p~n", [X]),
274 ets:update_counter(Tab, X, 1),
275 ok
276 end,
277 Fun1 = fun(X) ->
278 io:format("find_and_remove_fun_test b ~p~n", [X]),
279 ets:update_counter(Tab, X, 1),
280 ok
281 end,
282 {ok, Ref0} = sighandler:install(Sig, Fun0),
283 {ok, _Ref1} = sighandler:install(Sig, Fun1),
284 _ = os:cmd(Cmd),
285 ok = sighandler:remove(Fun1),
286 _ = os:cmd(Cmd),
287 ok = sighandler:remove(Sig, Ref0),
288 Result = (catch ets:lookup(Tab, Sig)),
289 ets:delete(Tab),
290 ok = application:stop(sighandler),
291 ?assertEqual([{Sig, 3}], Result).
292
293 -endif.
Something went wrong with that request. Please try again.