summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNick Vatamaniuc <vatamane@apache.org>2021-01-15 12:23:50 -0500
committerNick Vatamaniuc <nickva@users.noreply.github.com>2021-01-15 16:19:46 -0500
commit92318242b9bc5ec4c273a9be5e8c03080e4d780a (patch)
tree9879e38fe6bfbf21e3e058b6ca457b129a5286f6
parentce1ca2c4e85720848622e1fbed2c67d6882e904f (diff)
downloadcouchdb-92318242b9bc5ec4c273a9be5e8c03080e4d780a.tar.gz
Do not return broken processes to the query process pool
Previously, if an error was thrown in a `with_ddoc_proc/2` callback, the process was still returned to the process pool in the `after` clause. However, in some cases, for example when processing a _list response, the process might end up stuck in a bad state state, such that it could not be re-used anymore. In such a case, a subsequent user of that couch_js process would end up throwing an error and crashing. Fixes #2962
-rw-r--r--src/couch/src/couch_query_servers.erl11
-rw-r--r--src/couch/test/eunit/couchdb_os_proc_pool.erl62
2 files changed, 69 insertions, 4 deletions
diff --git a/src/couch/src/couch_query_servers.erl b/src/couch/src/couch_query_servers.erl
index 6649df364..c6d71345a 100644
--- a/src/couch/src/couch_query_servers.erl
+++ b/src/couch/src/couch_query_servers.erl
@@ -523,9 +523,14 @@ with_ddoc_proc(#doc{id=DDocId,revs={Start, [DiskRev|_]}}=DDoc, Fun) ->
Rev = couch_doc:rev_to_str({Start, DiskRev}),
DDocKey = {DDocId, Rev},
Proc = get_ddoc_process(DDoc, DDocKey),
- try Fun({Proc, DDocId})
- after
- ok = ret_os_process(Proc)
+ try Fun({Proc, DDocId}) of
+ Resp ->
+ ok = ret_os_process(Proc),
+ Resp
+ catch Tag:Err ->
+ Stack = erlang:get_stacktrace(),
+ catch proc_stop(Proc),
+ erlang:raise(Tag, Err, Stack)
end.
proc_prompt(Proc, Args) ->
diff --git a/src/couch/test/eunit/couchdb_os_proc_pool.erl b/src/couch/test/eunit/couchdb_os_proc_pool.erl
index 69f8051ad..b552e114a 100644
--- a/src/couch/test/eunit/couchdb_os_proc_pool.erl
+++ b/src/couch/test/eunit/couchdb_os_proc_pool.erl
@@ -20,9 +20,11 @@
setup() ->
ok = couch_proc_manager:reload(),
+ meck:new(couch_os_process, [passthrough]),
ok = setup_config().
teardown(_) ->
+ meck:unload(),
ok.
os_proc_pool_test_() ->
@@ -39,7 +41,8 @@ os_proc_pool_test_() ->
should_free_slot_on_proc_unexpected_exit(),
should_reuse_known_proc(),
% should_process_waiting_queue_as_fifo(),
- should_reduce_pool_on_idle_os_procs()
+ should_reduce_pool_on_idle_os_procs(),
+ should_not_return_broken_process_to_the_pool()
]
}
}
@@ -205,6 +208,63 @@ should_reduce_pool_on_idle_os_procs() ->
end).
+should_not_return_broken_process_to_the_pool() ->
+ ?_test(begin
+ config:set("query_server_config",
+ "os_process_soft_limit", "1", false),
+ ok = confirm_config("os_process_soft_limit", "1"),
+
+ config:set("query_server_config",
+ "os_process_limit", "1", false),
+ ok = confirm_config("os_process_limit", "1"),
+
+ DDoc1 = ddoc(<<"_design/ddoc1">>),
+
+ meck:reset(couch_os_process),
+
+ ?assertEqual(0, couch_proc_manager:get_proc_count()),
+ ok = couch_query_servers:with_ddoc_proc(DDoc1, fun(_) -> ok end),
+ ?assertEqual(0, meck:num_calls(couch_os_process, stop, 1)),
+ ?assertEqual(1, couch_proc_manager:get_proc_count()),
+
+ ?assertError(bad, couch_query_servers:with_ddoc_proc(DDoc1, fun(_) ->
+ error(bad)
+ end)),
+ ?assertEqual(1, meck:num_calls(couch_os_process, stop, 1)),
+
+ WaitFun = fun() ->
+ case couch_proc_manager:get_proc_count() of
+ 0 -> ok;
+ N when is_integer(N), N > 0 -> wait
+ end
+ end,
+ case test_util:wait(WaitFun, 5000) of
+ timeout -> error(timeout);
+ _ -> ok
+ end,
+ ?assertEqual(0, couch_proc_manager:get_proc_count()),
+
+ DDoc2 = ddoc(<<"_design/ddoc2">>),
+ ok = couch_query_servers:with_ddoc_proc(DDoc2, fun(_) -> ok end),
+ ?assertEqual(1, meck:num_calls(couch_os_process, stop, 1)),
+ ?assertEqual(1, couch_proc_manager:get_proc_count())
+ end).
+
+
+ddoc(DDocId) ->
+ #doc{
+ id = DDocId,
+ revs = {1, [<<"abc">>]},
+ body = {[
+ {<<"language">>, <<"javascript">>},
+ {<<"views">>, {[
+ {<<"v1">>, {[
+ {<<"map">>, <<"function(doc) {emit(doc.value,1);}">>}
+ ]}}
+ ]}}
+ ]}
+ }.
+
setup_config() ->
config:set("native_query_servers", "enable_erlang_query_server", "true", false),
config:set("query_server_config", "os_process_limit", "3", false),