Add config:subscribe_for_changes/1 Add a new gen event handler which sends plain events to the Subscriber.
COUCHDB-3102 Project: http://git-wip-us.apache.org/repos/asf/couchdb-config/repo Commit: http://git-wip-us.apache.org/repos/asf/couchdb-config/commit/01c34c09 Tree: http://git-wip-us.apache.org/repos/asf/couchdb-config/tree/01c34c09 Diff: http://git-wip-us.apache.org/repos/asf/couchdb-config/diff/01c34c09 Branch: refs/heads/master Commit: 01c34c09aadb57b2a4cf375b145ef084a35314c0 Parents: 686d76b Author: ILYA Khlopotov <[email protected]> Authored: Thu Aug 18 12:58:04 2016 -0700 Committer: ILYA Khlopotov <[email protected]> Committed: Fri Aug 19 12:56:35 2016 -0700 ---------------------------------------------------------------------- src/config.erl | 4 ++ src/config_notifier.erl | 68 +++++++++++++++++++++++++++++++ test/config_tests.erl | 96 +++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 167 insertions(+), 1 deletion(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/couchdb-config/blob/01c34c09/src/config.erl ---------------------------------------------------------------------- diff --git a/src/config.erl b/src/config.erl index c0d0446..9449b1e 100644 --- a/src/config.erl +++ b/src/config.erl @@ -32,6 +32,7 @@ -export([get_boolean/3, set_boolean/3]). -export([listen_for_changes/2]). +-export([subscribe_for_changes/1]). -export([parse_ini_file/1]). -export([init/1, terminate/2, code_change/3]). @@ -185,6 +186,9 @@ delete(Section, Key, Persist, Reason) when is_list(Section), is_list(Key) -> listen_for_changes(CallbackModule, InitialState) -> config_listener_mon:subscribe(CallbackModule, InitialState). +subscribe_for_changes(Subscription) -> + config_notifier:subscribe(Subscription). + init(IniFiles) -> ets:new(?MODULE, [named_table, set, protected, {read_concurrency, true}]), http://git-wip-us.apache.org/repos/asf/couchdb-config/blob/01c34c09/src/config_notifier.erl ---------------------------------------------------------------------- diff --git a/src/config_notifier.erl b/src/config_notifier.erl new file mode 100644 index 0000000..c1844e7 --- /dev/null +++ b/src/config_notifier.erl @@ -0,0 +1,68 @@ +% Licensed under the Apache License, Version 2.0 (the "License"); you may not +% use this file except in compliance with the License. You may obtain a copy of +% the License at +% +% http://www.apache.org/licenses/LICENSE-2.0 +% +% Unless required by applicable law or agreed to in writing, software +% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +% License for the specific language governing permissions and limitations under +% the License. + +-module(config_notifier). + +-behaviour(gen_event). +-vsn(1). + +%% Public interface +-export([subscribe/1]). +-export([subscribe/2]). + +%% gen_event interface +-export([ + init/1, + handle_event/2, + handle_call/2, + handle_info/2, + terminate/2, + code_change/3 +]). + +subscribe(Subscription) -> + subscribe(self(), Subscription). + +subscribe(Subscriber, Subscription) -> + gen_event:add_sup_handler( + config_event, {?MODULE, Subscriber}, {Subscriber, Subscription}). + +init({Subscriber, Subscription}) -> + {ok, {Subscriber, Subscription}}. + +handle_event({config_change, _, _, _, _} = Event, {Subscriber, Subscription}) -> + maybe_notify(Event, Subscriber, Subscription). + +handle_call(_Request, St) -> + {ok, ignored, St}. + +handle_info(_Info, St) -> + {ok, St}. + +terminate(_Reason, {_Subscriber, _Subscription}) -> + ok. + +code_change(_OldVsn, St, _Extra) -> + {ok, St}. + +maybe_notify(Event, Subscriber, all) -> + Subscriber ! Event; +maybe_notify({config_change, Sec, Key, _, _} = Event, Subscriber, Subscription) -> + case should_notify(Sec, Key, Subscription) of + true -> + Subscriber ! Event; + false -> + ok + end. + +should_notify(Sec, Key, Subscription) -> + lists:any(fun(S) -> S =:= Sec orelse S =:= {Sec, Key} end, Subscription). http://git-wip-us.apache.org/repos/asf/couchdb-config/blob/01c34c09/test/config_tests.erl ---------------------------------------------------------------------- diff --git a/test/config_tests.erl b/test/config_tests.erl index c495bf7..c4a965b 100644 --- a/test/config_tests.erl +++ b/test/config_tests.erl @@ -80,6 +80,10 @@ setup_config_listener() -> setup(), spawn_config_listener(). +setup_config_notifier(Subscription) -> + setup(), + spawn_config_notifier(Subscription). + teardown(Pid) when is_pid(Pid) -> catch exit(Pid, kill), @@ -88,7 +92,9 @@ teardown(Pid) when is_pid(Pid) -> teardown(_) -> [application:stop(App) || App <- ?DEPS]. - +teardown(_, Pid) when is_pid(Pid) -> + catch exit(Pid, kill), + teardown(undefined); teardown(_, _) -> teardown(undefined). @@ -251,6 +257,25 @@ config_listener_behaviour_test_() -> } }. +config_notifier_behaviour_test_() -> + { + "Test config_notifier behaviour", + { + foreachx, + local, + fun setup_config_notifier/1, + fun teardown/2, + [ + {all, fun should_notify/2}, + {["section_foo"], fun should_notify/2}, + {[{"section_foo", "key_bar"}], fun should_notify/2}, + {["section_foo"], fun should_not_notify/2}, + {[{"section_foo", "key_bar"}], fun should_not_notify/2}, + {all, fun should_unsubscribe_when_subscriber_gone/2} + ] + } + }. + should_load_all_configs() -> ?assert(length(config:all()) > 0). @@ -516,6 +541,50 @@ should_stop_monitor_on_error(Pid) -> ?assertEqual(0, n_handlers()) end). +should_notify(Subscription, Pid) -> + {to_string(Subscription), ?_test(begin + ?assertEqual(ok, config:set("section_foo", "key_bar", "any", false)), + ?assertEqual({config_change,"section_foo", "key_bar", "any", false}, getmsg(Pid)), + ok + end)}. + +should_not_notify([{Section, _}] = Subscription, Pid) -> + {to_string(Subscription), ?_test(begin + ?assertEqual(ok, config:set(Section, "any", "any", false)), + ?assertError({timeout, config_msg}, getmsg(Pid)), + ok + end)}; +should_not_notify(Subscription, Pid) -> + {to_string(Subscription), ?_test(begin + ?assertEqual(ok, config:set("any", "any", "any", false)), + ?assertError({timeout, config_msg}, getmsg(Pid)), + ok + end)}. + +should_unsubscribe_when_subscriber_gone(_Subscription, Pid) -> + ?_test(begin + ?assertEqual(1, n_notifiers()), + + ?assert(is_process_alive(Pid)), + + % Monitor subscriber process + MonRef = erlang:monitor(process, Pid), + + exit(Pid, kill), + + % Wait for the subscriber process to exit + receive + {'DOWN', MonRef, _, _, _} -> ok + after ?TIMEOUT -> + erlang:error({timeout, config_notifier_shutdown}) + end, + + ?assertNot(is_process_alive(Pid)), + + ?assertEqual(0, n_notifiers()), + ok + end). + spawn_config_listener() -> Self = self(), @@ -531,11 +600,27 @@ spawn_config_listener() -> end, Pid. +spawn_config_notifier(Subscription) -> + Self = self(), + Pid = erlang:spawn(fun() -> + ok = config:subscribe_for_changes(Subscription), + Self ! registered, + loop(undefined) + end), + receive + registered -> ok + after ?TIMEOUT -> + erlang:error({timeout, config_handler_register}) + end, + Pid. + loop(undefined) -> receive {config_msg, _} = Msg -> loop(Msg); + {config_change, _, _, _, _} = Msg -> + loop({config_msg, Msg}); {get_msg, _, _} = Msg -> loop(Msg); Msg -> @@ -546,6 +631,8 @@ loop({get_msg, From, Ref}) -> receive {config_msg, _} = Msg -> From ! {Ref, Msg}; + {config_change, _, _, _, _} = Msg -> + From ! {Ref, Msg}; Msg -> erlang:error({invalid_message, Msg}) end, @@ -574,3 +661,10 @@ getmsg(Pid) -> n_handlers() -> Handlers = gen_event:which_handlers(config_event), length([Pid || {config_listener, {?MODULE, Pid}} <- Handlers]). + +n_notifiers() -> + Handlers = gen_event:which_handlers(config_event), + length([Pid || {config_notifier, Pid} <- Handlers]). + +to_string(Term) -> + lists:flatten(io_lib:format("~p", [Term])).
