blob: eab5d35607998083612ff1b73f1e2453c833e7c5 [file] [log] [blame]
% 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(couch_js_proc_manager_tests).
-include_lib("couch/include/couch_eunit.hrl").
-include_lib("couch/include/couch_db.hrl").
-define(TDEF(A), {atom_to_list(A), fun A/0}).
-define(NUM_PROCS, 3).
-define(TIMEOUT, 2000).
-define(TIMEOUT_ERROR(Msg),
erlang:error(
{assertion_failed, [
{module, ?MODULE},
{line, ?LINE},
{reason, Msg}
]}
)
).
start() ->
ok = application:set_env(config, ini_files, ?CONFIG_CHAIN),
{ok, Started} = application:ensure_all_started(couch_js),
config:set("native_query_servers", "enable_erlang_query_server", "true", false),
config:set("query_server_config", "os_process_limit", "3", false),
config:set("query_server_config", "os_process_soft_limit", "2", false),
config:set("query_server_config", "os_process_idle_limit", "1", false),
ok = test_util:wait(
fun() ->
case config:get("query_server_config", "os_process_idle_limit") of
"1" -> ok;
_ -> wait
end
end,
?TIMEOUT,
10
),
Started.
stop(Apps) ->
lists:foreach(
fun(App) ->
ok = application:stop(App)
end,
lists:reverse(Apps)
).
couch_js_proc_manager_test_() ->
{
"couch_js_proc_manger tests",
{
setup,
fun start/0,
fun stop/1,
[
?TDEF(should_block_new_proc_on_full_pool),
?TDEF(should_free_slot_on_proc_unexpected_exit),
?TDEF(should_reuse_known_proc),
?TDEF(should_process_waiting_queue_as_fifo),
?TDEF(should_reduce_pool_on_idle_os_procs)
]
}
}.
should_block_new_proc_on_full_pool() ->
ok = couch_js_proc_manager:reload(),
Clients = [
spawn_client(),
spawn_client(),
spawn_client()
],
lists:foreach(
fun(Client) ->
?assertEqual(ok, ping_client(Client))
end,
Clients
),
% Make sure everyone got a different proc
Procs = [get_client_proc(Client) || Client <- Clients],
?assertEqual(lists:sort(Procs), lists:usort(Procs)),
% This client will be stuck waiting for someone
% to give up their proc.
Client4 = spawn_client(),
?assert(is_client_waiting(Client4)),
Client1 = hd(Clients),
Proc1 = hd(Procs),
?assertEqual(ok, stop_client(Client1)),
?assertEqual(ok, ping_client(Client4)),
Proc4 = get_client_proc(Client4),
?assertEqual(Proc1#proc.pid, Proc4#proc.pid),
?assertNotEqual(Proc1#proc.client, Proc4#proc.client),
lists:map(
fun(C) ->
?assertEqual(ok, stop_client(C))
end,
[Client4 | tl(Clients)]
).
should_free_slot_on_proc_unexpected_exit() ->
ok = couch_js_proc_manager:reload(),
Clients = [
spawn_client(),
spawn_client(),
spawn_client()
],
lists:foreach(
fun(Client) ->
?assertEqual(ok, ping_client(Client))
end,
Clients
),
Procs1 = [get_client_proc(Client) || Client <- Clients],
ProcClients1 = [Proc#proc.client || Proc <- Procs1],
?assertEqual(lists:sort(Procs1), lists:usort(Procs1)),
?assertEqual(lists:sort(ProcClients1), lists:usort(ProcClients1)),
Client1 = hd(Clients),
Proc1 = hd(Procs1),
?assertEqual(ok, kill_client(Client1)),
Client4 = spawn_client(),
?assertEqual(ok, ping_client(Client4)),
Proc4 = get_client_proc(Client4),
?assertEqual(Proc1#proc.pid, Proc4#proc.pid),
?assertNotEqual(Proc1#proc.client, Proc4#proc.client),
Procs2 = [Proc4 | tl(Procs1)],
ProcClients2 = [Proc4#proc.client | tl(ProcClients1)],
?assertEqual(lists:sort(Procs2), lists:usort(Procs2)),
?assertEqual(lists:sort(ProcClients2), lists:usort(ProcClients2)),
lists:map(
fun(C) ->
?assertEqual(ok, stop_client(C))
end,
[Client4 | tl(Clients)]
).
should_reuse_known_proc() ->
ok = couch_js_proc_manager:reload(),
Clients = [
spawn_client(<<"ddoc1">>),
spawn_client(<<"ddoc2">>)
],
lists:foreach(
fun(Client) ->
?assertEqual(ok, ping_client(Client))
end,
Clients
),
Procs = [get_client_proc(Client) || Client <- Clients],
?assertEqual(lists:sort(Procs), lists:usort(Procs)),
lists:foreach(
fun(Client) ->
?assertEqual(ok, stop_client(Client))
end,
Clients
),
lists:foreach(
fun(Proc) ->
?assert(is_process_alive(Proc#proc.pid))
end,
Procs
),
Client = spawn_client(<<"ddoc1">>),
?assertEqual(ok, ping_client(Client)),
OldProc = hd(Procs),
NewProc = get_client_proc(Client),
?assertEqual(OldProc#proc.pid, NewProc#proc.pid),
?assertNotEqual(OldProc#proc.client, NewProc#proc.client),
?assertEqual(ok, stop_client(Client)).
should_process_waiting_queue_as_fifo() ->
Clients = [
spawn_client(<<"ddoc1">>),
spawn_client(<<"ddoc2">>),
spawn_client(<<"ddoc3">>),
spawn_client(<<"ddoc4">>),
spawn_client(<<"ddoc5">>),
spawn_client(<<"ddoc6">>)
],
lists:foldl(
fun(Client, Pos) ->
case Pos =< ?NUM_PROCS of
true ->
?assertEqual(ok, ping_client(Client));
false ->
?assert(is_client_waiting(Client))
end,
Pos + 1
end,
1,
Clients
),
LastClients = lists:foldl(
fun(_Iteration, ClientAcc) ->
FirstClient = hd(ClientAcc),
FirstProc = get_client_proc(FirstClient),
?assertEqual(ok, stop_client(FirstClient)),
RestClients = tl(ClientAcc),
lists:foldl(
fun(Client, Pos) ->
case Pos =< ?NUM_PROCS of
true ->
?assertEqual(ok, ping_client(Client));
false ->
?assert(is_client_waiting(Client))
end,
if
Pos /= ?NUM_PROCS ->
ok;
true ->
BubbleProc = get_client_proc(Client),
?assertEqual(FirstProc#proc.pid, BubbleProc#proc.pid),
?assertNotEqual(FirstProc#proc.client, BubbleProc#proc.client)
end,
Pos + 1
end,
1,
RestClients
),
RestClients
end,
Clients,
lists:seq(1, 3)
),
lists:foreach(
fun(Client) ->
?assertEqual(ok, stop_client(Client))
end,
LastClients
).
should_reduce_pool_on_idle_os_procs() ->
Clients = [
spawn_client(<<"ddoc1">>),
spawn_client(<<"ddoc2">>),
spawn_client(<<"ddoc3">>)
],
lists:foreach(
fun(Client) ->
?assertEqual(ok, ping_client(Client))
end,
Clients
),
?assertEqual(3, couch_js_proc_manager:get_proc_count()),
lists:foreach(
fun(Client) ->
?assertEqual(ok, stop_client(Client))
end,
Clients
),
?assertEqual(3, couch_js_proc_manager:get_proc_count()),
timer:sleep(1200),
?assertEqual(1, couch_js_proc_manager:get_proc_count()).
spawn_client() ->
Parent = self(),
Ref = make_ref(),
{Pid, _} = spawn_monitor(fun() ->
Parent ! {self(), initialized},
Proc = couch_js_query_servers:get_os_process(<<"erlang">>),
loop(Parent, Ref, Proc)
end),
receive
{Pid, initialized} ->
ok
after ?TIMEOUT ->
?TIMEOUT_ERROR("Error creating client.")
end,
{Pid, Ref}.
spawn_client(DDocId) ->
Parent = self(),
Ref = make_ref(),
{Pid, _} = spawn_monitor(fun() ->
DDocKey = {DDocId, <<"1-abcdefgh">>},
DDoc = #doc{body = {[{<<"language">>, <<"erlang">>}]}},
Parent ! {self(), initialized},
Proc = couch_js_query_servers:get_ddoc_process(DDoc, DDocKey),
loop(Parent, Ref, Proc)
end),
receive
{Pid, initialized} ->
ok
after ?TIMEOUT ->
?TIMEOUT_ERROR("Error creating ddoc client.")
end,
{Pid, Ref}.
loop(Parent, Ref, Proc) ->
receive
ping ->
Parent ! {pong, Ref},
loop(Parent, Ref, Proc);
get_proc ->
Parent ! {proc, Ref, Proc},
loop(Parent, Ref, Proc);
stop ->
couch_js_query_servers:ret_os_process(Proc),
Parent ! {stop, Ref};
die ->
Parent ! {die, Ref},
exit(some_error)
end.
ping_client({Pid, Ref}) ->
Pid ! ping,
receive
{pong, Ref} ->
ok
after ?TIMEOUT ->
?TIMEOUT_ERROR("Timeout pinging client")
end.
is_client_waiting({Pid, _Ref}) ->
{status, Status} = process_info(Pid, status),
{current_function, {M, F, A}} = process_info(Pid, current_function),
Status == waiting andalso {M, F, A} == {gen, do_call, 4}.
get_client_proc({Pid, Ref}) ->
Pid ! get_proc,
receive
{proc, Ref, Proc} -> Proc
after ?TIMEOUT ->
?TIMEOUT_ERROR("Timeout getting proc from client")
end.
stop_client({Pid, Ref}) ->
Pid ! stop,
receive
{stop, Ref} ->
ok
after ?TIMEOUT ->
?TIMEOUT_ERROR("Timeout stopping client")
end,
receive
{'DOWN', _, _, Pid, _} ->
ok
after ?TIMEOUT ->
?TIMEOUT_ERROR("Timeout waiting for stopped client 'DOWN'")
end.
kill_client({Pid, Ref}) ->
Pid ! die,
receive
{die, Ref} ->
ok
after ?TIMEOUT ->
?TIMEOUT_ERROR("Timeout killing client")
end,
receive
{'DOWN', _, _, Pid, _} ->
ok
after ?TIMEOUT ->
?TIMEOUT_ERROR("Timeout waiting for killed client 'DOWN'")
end.