1#!/usr/bin/env escript
2%% -*- Mode: Erlang; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
3%%! -smp enable
4
5% Licensed under the Apache License, Version 2.0 (the "License"); you may not
6% use this file except in compliance with the License. You may obtain a copy of
7% the License at
8%
9%   http://www.apache.org/licenses/LICENSE-2.0
10%
11% Unless required by applicable law or agreed to in writing, software
12% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14% License for the specific language governing permissions and limitations under
15% the License.
16
17-include_lib("couch_set_view/include/couch_set_view.hrl").
18
19-define(b2l(B), binary_to_list(B)).
20-define(MAX_WAIT_TIME, 60000).
21
22test_set_name() -> <<"couch_test_set_index_query_fdleaks">>.
23num_set_partitions() -> 8.
24ddoc_id() -> <<"_design/test">>.
25num_docs() -> 8000.
26
27
28main(_) ->
29    test_util:init_code_path(),
30
31    etap:plan(6),
32    case (catch test()) of
33        ok ->
34            etap:end_tests();
35        Other ->
36            etap:diag(io_lib:format("Test died abnormally: ~p", [Other])),
37            etap:bail(Other)
38    end,
39    ok.
40
41
42test() ->
43    couch_set_view_test_util:start_server(test_set_name()),
44    etap:diag("Checking view with only map function for leaks"),
45    test_group_refleaks(map),
46    etap:diag("Checking view with reducer function for leaks"),
47    test_group_refleaks(reduce),
48    couch_set_view_test_util:stop_server(),
49    ok.
50
51test_group_refleaks(ViewType) ->
52    create_set(lists:seq(0, num_set_partitions() - 1), [], ViewType),
53    ValueGenFun1 = fun(I) -> I end,
54    update_documents(0, num_docs(), ValueGenFun1),
55    GroupPid = couch_set_view:get_group_pid(
56        mapreduce_view, test_set_name(), ddoc_id(), prod),
57    trigger_initial_build(),
58    Msg = case ViewType of
59    reduce ->
60        io_lib:format("Group with reducer function is alive" ,[]);
61    map ->
62        io_lib:format("Group with only map function is alive", [])
63    end,
64    etap:is(is_process_alive(GroupPid), true, Msg),
65    check_refcount_leaks(GroupPid),
66    couch_set_view_test_util:delete_set_dbs(test_set_name(), num_set_partitions()).
67
68
69trigger_initial_build() ->
70    GroupPid = couch_set_view:get_group_pid(
71        mapreduce_view, test_set_name(), ddoc_id(), prod),
72    {ok, _, _} = gen_server:call(
73        GroupPid, #set_view_group_req{stale = false, debug = true}, ?MAX_WAIT_TIME).
74
75
76check_refcount_leaks(GroupPid) ->
77    RefCounter = get_group_refcounter(GroupPid),
78    RefCount = couch_ref_counter:count(RefCounter),
79    etap:is(is_process_alive(RefCounter), true, "RefCounter is alive"),
80
81    etap:diag("Querying view"),
82    query_view(),
83
84    RefCount2 = couch_ref_counter:count(RefCounter),
85    etap:is(RefCount, RefCount2, "Reference count for view group as expected after query").
86
87
88query_view() ->
89    {ok, _} = couch_set_view_test_util:query_view(
90        test_set_name(), ddoc_id(), <<"test">>).
91
92
93create_set(ActiveParts, PassiveParts, ViewType) ->
94    couch_set_view_test_util:delete_set_dbs(test_set_name(), num_set_partitions()),
95    couch_set_view_test_util:create_set_dbs(test_set_name(), num_set_partitions()),
96    couch_set_view:cleanup_index_files(mapreduce_view, test_set_name()),
97    etap:diag("Creating the set databases (# of partitions: " ++
98        integer_to_list(num_set_partitions()) ++ ")"),
99    ViewFns = [{<<"map">>, <<"function(doc, meta) { emit(doc.value, meta.id); }">>}],
100    ViewFns2 = case ViewType of
101    reduce ->
102        ViewFns ++ [{<<"reduce">>, <<"_count">>}];
103    map ->
104        ViewFns
105    end,
106
107    DDoc = {[
108        {<<"meta">>, {[{<<"id">>, ddoc_id()}]}},
109        {<<"json">>, {[
110            {<<"language">>, <<"javascript">>},
111            {<<"views">>, {[
112                {<<"test">>, {ViewFns2}}
113            ]}}
114        ]}}
115    ]},
116
117    ok = couch_set_view_test_util:update_ddoc(test_set_name(), DDoc),
118    etap:diag("Configuring set view with partitions [0 .. 31]"
119              " as active and [32 .. 47] as passive"),
120    Params = #set_view_params{
121        max_partitions = num_set_partitions(),
122        active_partitions = ActiveParts,
123        passive_partitions = PassiveParts,
124        use_replica_index = true
125    },
126    ok = couch_set_view:define_group(
127        mapreduce_view, test_set_name(), ddoc_id(), Params).
128
129
130update_documents(StartId, Count, ValueGenFun) ->
131    etap:diag("Updating " ++ integer_to_list(Count) ++ " new documents"),
132    DocList0 = lists:map(
133        fun(I) ->
134            {I rem num_set_partitions(), {[
135                    {<<"meta">>, {[{<<"id">>, doc_id(I)}]}},
136                    {<<"json">>, {[
137                        {<<"value">>, ValueGenFun(I)}
138                    ]}}
139            ]}}
140        end,
141        lists:seq(StartId, StartId + Count - 1)),
142    DocList = [Doc || {_, Doc} <- lists:keysort(1, DocList0)],
143    ok = couch_set_view_test_util:populate_set_sequentially(
144        test_set_name(),
145        lists:seq(0, num_set_partitions() - 1),
146        DocList).
147
148
149doc_id(I) ->
150    iolist_to_binary(io_lib:format("doc_~8..0b", [I])).
151
152
153get_group_refcounter(GroupPid) ->
154    {ok, Group} = gen_server:call(GroupPid, request_group, infinity),
155    Group#set_view_group.ref_counter.
156