#!/usr/bin/env escript %% -*- erlang -*- % 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. -record(user_ctx, { name = null, roles = [], handler }). -define(LATEST_DISK_VERSION, 5). -record(db_header, {disk_version = ?LATEST_DISK_VERSION, update_seq = 0, unused = 0, fulldocinfo_by_id_btree_state = nil, docinfo_by_seq_btree_state = nil, local_docs_btree_state = nil, purge_seq = 0, purged_docs = nil, security_ptr = nil, revs_limit = 1000 }). -record(db, { main_pid = nil, update_pid = nil, compactor_pid = nil, instance_start_time, % number of microsecs since jan 1 1970 as a binary string fd, fd_ref_counter, header = #db_header{}, committed_update_seq, fulldocinfo_by_id_btree, docinfo_by_seq_btree, local_docs_btree, update_seq, name, filepath, validate_doc_funs = [], security = [], security_ptr = nil, user_ctx = #user_ctx{}, waiting_delayed_commit = nil, revs_limit = 1000, fsync_options = [], is_sys_db = false }). test_db_name() -> <<"couch_test_view_group_db_leaks">>. ddoc_name() -> <<"foo">>. main(_) -> test_util:init_code_path(), etap:plan(18), case (catch test()) of ok -> etap:end_tests(); Other -> etap:diag(io_lib:format("Test died abnormally: ~p", [Other])), etap:bail(Other) end, ok. test() -> couch_server_sup:start_link(test_util:config_files()), timer:sleep(1000), put(addr, couch_config:get("httpd", "bind_address", "127.0.0.1")), put(port, integer_to_list(mochiweb_socket_server:get(couch_httpd, port))), application:start(inets), delete_db(), create_db(), create_docs(), create_design_doc(), ViewGroup = couch_view:get_group_server( test_db_name(), <<"_design/", (ddoc_name())/binary>>), etap:is(is_pid(ViewGroup), true, "got view group pid"), etap:is(is_process_alive(ViewGroup), true, "view group pid is alive"), query_view(), check_db_ref_count(), etap:is(is_process_alive(ViewGroup), true, "view group pid is alive"), create_new_doc(<<"doc1000">>), query_view(), check_db_ref_count(), etap:is(is_process_alive(ViewGroup), true, "view group pid is alive"), Ref1 = get_db_ref_counter(), compact_db(), check_db_ref_count(), Ref2 = get_db_ref_counter(), etap:isnt(Ref1, Ref2, "DB ref counter changed"), etap:is(false, is_process_alive(Ref1), "old DB ref counter is not alive"), etap:is(is_process_alive(ViewGroup), true, "view group pid is alive"), compact_view_group(), check_db_ref_count(), Ref3 = get_db_ref_counter(), etap:is(Ref3, Ref2, "DB ref counter didn't change"), etap:is(is_process_alive(ViewGroup), true, "view group pid is alive"), create_new_doc(<<"doc1001">>), query_view(), check_db_ref_count(), etap:is(is_process_alive(ViewGroup), true, "view group pid is alive"), MonRef = erlang:monitor(process, ViewGroup), ok = couch_server:delete(test_db_name(), []), receive {'DOWN', MonRef, _, _, _} -> etap:diag("view group is dead after DB deletion") after 5000 -> etap:bail("view group did not die after DB deletion") end, ok = timer:sleep(1000), delete_db(), couch_server_sup:stop(), ok. admin_user_ctx() -> {user_ctx, #user_ctx{roles=[<<"_admin">>]}}. create_db() -> {ok, Db} = couch_db:create(test_db_name(), [admin_user_ctx()]), ok = couch_db:close(Db). delete_db() -> couch_server:delete(test_db_name(), [admin_user_ctx()]). compact_db() -> {ok, Db} = couch_db:open_int(test_db_name(), []), ok = couch_db:start_compact(Db), ok = couch_db:close(Db), wait_db_compact_done(10). wait_db_compact_done(0) -> etap:bail("DB compaction failed to finish."); wait_db_compact_done(N) -> {ok, Db} = couch_db:open_int(test_db_name(), []), ok = couch_db:close(Db), case is_pid(Db#db.compactor_pid) of false -> ok; true -> ok = timer:sleep(500), wait_db_compact_done(N - 1) end. compact_view_group() -> ok = couch_view_compactor:start_compact(test_db_name(), ddoc_name()), wait_view_compact_done(10). wait_view_compact_done(0) -> etap:bail("View group compaction failed to finish."); wait_view_compact_done(N) -> {ok, {{_, Code, _}, _Headers, Body}} = http:request( get, {db_url() ++ "/_design/" ++ binary_to_list(ddoc_name()) ++ "/_info", []}, [], [{sync, true}]), case Code of 200 -> ok; _ -> etap:bail("Invalid view group info.") end, {Info} = couch_util:json_decode(Body), {IndexInfo} = couch_util:get_value(<<"view_index">>, Info), CompactRunning = couch_util:get_value(<<"compact_running">>, IndexInfo), case CompactRunning of false -> ok; true -> ok = timer:sleep(500), wait_view_compact_done(N - 1) end. get_db_ref_counter() -> {ok, #db{fd_ref_counter = Ref} = Db} = couch_db:open_int(test_db_name(), []), ok = couch_db:close(Db), Ref. check_db_ref_count() -> {ok, #db{fd_ref_counter = Ref} = Db} = couch_db:open_int(test_db_name(), []), ok = couch_db:close(Db), etap:is(couch_ref_counter:count(Ref), 2, "DB ref counter is only held by couch_db and couch_db_updater"), ok. create_docs() -> {ok, Db} = couch_db:open(test_db_name(), [admin_user_ctx()]), Doc1 = couch_doc:from_json_obj({[ {<<"_id">>, <<"doc1">>}, {<<"value">>, 1} ]}), Doc2 = couch_doc:from_json_obj({[ {<<"_id">>, <<"doc2">>}, {<<"value">>, 2} ]}), Doc3 = couch_doc:from_json_obj({[ {<<"_id">>, <<"doc3">>}, {<<"value">>, 3} ]}), {ok, _} = couch_db:update_docs(Db, [Doc1, Doc2, Doc3]), couch_db:ensure_full_commit(Db), couch_db:close(Db). create_design_doc() -> {ok, Db} = couch_db:open(test_db_name(), [admin_user_ctx()]), DDoc = couch_doc:from_json_obj({[ {<<"_id">>, <<"_design/", (ddoc_name())/binary>>}, {<<"language">>, <<"javascript">>}, {<<"views">>, {[ {<<"bar">>, {[ {<<"map">>, <<"function(doc) { emit(doc._id, null); }">>} ]}} ]}} ]}), {ok, _} = couch_db:update_docs(Db, [DDoc]), couch_db:ensure_full_commit(Db), couch_db:close(Db). create_new_doc(Id) -> {ok, Db} = couch_db:open(test_db_name(), [admin_user_ctx()]), Doc666 = couch_doc:from_json_obj({[ {<<"_id">>, Id}, {<<"value">>, 999} ]}), {ok, _} = couch_db:update_docs(Db, [Doc666]), couch_db:ensure_full_commit(Db), couch_db:close(Db). db_url() -> "http://" ++ get(addr) ++ ":" ++ get(port) ++ "/" ++ binary_to_list(test_db_name()). query_view() -> {ok, {{_, Code, _}, _Headers, _Body}} = http:request( get, {db_url() ++ "/_design/" ++ binary_to_list(ddoc_name()) ++ "/_view/bar", []}, [], [{sync, true}]), etap:is(Code, 200, "got view response"), ok.