diff --git a/src/chttpd/src/chttpd_auth_cache.erl b/src/chttpd/src/chttpd_auth_cache.erl index 638d8c74874..483eb8bb767 100644 --- a/src/chttpd/src/chttpd_auth_cache.erl +++ b/src/chttpd/src/chttpd_auth_cache.erl @@ -100,7 +100,7 @@ maybe_increment_auth_cache_miss(UserName) -> %% gen_server callbacks init([]) -> - self() ! {start_listener, 0}, + self() ! {maybe_start_listener, 0}, {ok, #state{}}. handle_call(_Call, _From, State) -> @@ -110,17 +110,24 @@ handle_cast(_Msg, State) -> {noreply, State}. handle_info({'DOWN', _, _, Pid, Reason}, #state{changes_pid=Pid} = State) -> - Seq = case Reason of - {seq, EndSeq} -> + Seq = case {Reason, db_exists(dbname())} of + {{seq, EndSeq}, true} -> EndSeq; _ -> couch_log:notice("~p changes listener died ~r", [?MODULE, Reason]), 0 end, - erlang:send_after(5000, self(), {start_listener, Seq}), - {noreply, State#state{last_seq=Seq}}; -handle_info({start_listener, Seq}, State) -> - {noreply, State#state{changes_pid = spawn_changes(Seq)}}; + erlang:send_after(5000, self(), {maybe_start_listener, Seq}), + {noreply, State#state{last_seq = Seq, changes_pid = undefined}}; +handle_info({maybe_start_listener, Seq}, State) -> + State1 = case may_spawn_listener(State) of + true -> + State#state{changes_pid = spawn_changes(Seq)}; + false -> + erlang:send_after(5000, self(), {maybe_start_listener, Seq}), + State + end, + {noreply, State1}; handle_info(_Msg, State) -> {noreply, State}. @@ -134,6 +141,17 @@ code_change(_OldVsn, #state{}=State, _Extra) -> %% private functions +may_spawn_listener(#state{changes_pid = Pid}) -> + case is_pid(Pid) andalso is_process_alive(Pid) of + true -> + false; + false -> + db_exists(dbname()) + end. + +db_exists(DbName) -> + is_list(catch mem3:shards(DbName)). + spawn_changes(Since) -> {Pid, _} = spawn_monitor(?MODULE, listen_for_changes, [Since]), Pid. diff --git a/src/chttpd/test/chttpd_auth_cache_test.erl b/src/chttpd/test/chttpd_auth_cache_test.erl new file mode 100644 index 00000000000..0d7f801517a --- /dev/null +++ b/src/chttpd/test/chttpd_auth_cache_test.erl @@ -0,0 +1,80 @@ +% 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(chttpd_auth_cache_test). + +-include_lib("couch/include/couch_eunit.hrl"). +-include_lib("couch/include/couch_db.hrl"). + + +setup() -> + DbName = ?tempdb(), + ok = fabric:create_db(DbName), + DbName. + + +teardown(DbName) -> + fabric:delete_db(DbName). + + +auth_db_change_listener_lifecycle_test_() -> + { + "auth db and change listener lifecycle tests", + { + setup, + fun chttpd_test_util:start_couch/0, + fun chttpd_test_util:stop_couch/1, + { + foreach, + fun setup/0, fun teardown/1, + [ + fun change_listener_should_not_start_if_auth_db_missing/1, + fun change_listener_should_start_if_auth_db_exists/1 + ] + } + } + }. + + + +change_listener_should_not_start_if_auth_db_missing(_DbName) -> + ?_test(begin + %% ok = restart_app(chttpd), + AuthCachePid = whereis(chttpd_auth_cache), + ?assert(is_pid(AuthCachePid)), + Expected = {state, undefined, "0"}, + ?assertEqual(Expected, sys:get_state(AuthCachePid)), + ok + end). + + +change_listener_should_start_if_auth_db_exists(DbName) -> + ?_test(begin + config:set("chttpd_auth", "authentication_db", ?b2l(DbName), false), + ok = restart_app(chttpd), + AuthCachePid = whereis(chttpd_auth_cache), + ?assert(is_pid(AuthCachePid)), + ListenerPid = test_util:wait(fun() -> + case sys:get_state(AuthCachePid) of + {state, undefined, "0"} -> wait; + {state, Pid, "0"} when is_pid(Pid) -> Pid + end + end, 1000, 10), + ?assert(is_pid(ListenerPid) andalso is_process_alive(ListenerPid)), + config:set("chttpd_auth", "authentication_db", "_users", false), + ok + end). + + +restart_app(App) -> + ok = application:stop(App), + ok = application:start(App).