Merge remote-tracking branch 'banjiewen/stale-stable-update'
diff --git a/priv/couch_js/help.h b/priv/couch_js/help.h
index 7601e9d..e6afaa8 100644
--- a/priv/couch_js/help.h
+++ b/priv/couch_js/help.h
@@ -54,6 +54,7 @@
     "              most SIZE bytes of memory to be allocated\n"
     "  -u FILE     path to a .uri file containing the address\n"
     "              (or addresses) of one or more servers\n"
+    "  --no-eval   Disable runtime code evaluation\n"
     "\n"
     "Report bugs at <%s>.\n";
 
diff --git a/priv/couch_js/main.c b/priv/couch_js/main.c
index 50d072c..dabeb19 100644
--- a/priv/couch_js/main.c
+++ b/priv/couch_js/main.c
@@ -349,6 +349,26 @@
 };
 
 
+static JSBool
+csp_allows(JSContext* cx)
+{
+    couch_args *args = (couch_args*)JS_GetContextPrivate(cx);
+    if(args->no_eval) {
+        return JS_FALSE;
+    } else {
+        return JS_TRUE;
+    }
+}
+
+
+static JSSecurityCallbacks security_callbacks = {
+    NULL,
+    NULL,
+    NULL,
+    csp_allows
+};
+
+
 int
 main(int argc, const char* argv[])
 {
@@ -382,7 +402,8 @@
     JS_SetOptions(cx, JSOPTION_TYPE_INFERENCE);
 #endif
     JS_SetContextPrivate(cx, args);
-    
+    JS_SetRuntimeSecurityCallbacks(rt, &security_callbacks);
+
     SETUP_REQUEST(cx);
 
     global = JS_NewCompartmentAndGlobalObject(cx, &global_class, NULL);
diff --git a/priv/couch_js/utf8.c b/priv/couch_js/utf8.c
index fcafff6..4cdb9c2 100644
--- a/priv/couch_js/utf8.c
+++ b/priv/couch_js/utf8.c
@@ -83,10 +83,10 @@
             {
                 // Invalid second half of surrogate pair
                 v = (uint32) 0xFFFD;
+                // Undo our character advancement
+                src--;
+                srclen++;
             }
-            // Undo our character advancement
-            src--;
-            srclen++;
         }
         else
         {
diff --git a/priv/couch_js/util.c b/priv/couch_js/util.c
index 2f2a2a7..7919025 100644
--- a/priv/couch_js/util.c
+++ b/priv/couch_js/util.c
@@ -98,6 +98,8 @@
             }
         } else if(strcmp("-u", argv[i]) == 0) {
             args->uri_file = argv[++i];
+        } else if(strcmp("--no-eval", argv[i]) == 0) {
+            args->no_eval = 1;
         } else if(strcmp("--", argv[i]) == 0) {
             i++;
             break;
diff --git a/priv/couch_js/util.h b/priv/couch_js/util.h
index 3c71f69..062469d 100644
--- a/priv/couch_js/util.h
+++ b/priv/couch_js/util.h
@@ -16,6 +16,7 @@
 #include <jsapi.h>
 
 typedef struct {
+    int          no_eval;
     int          use_http;
     int          use_test_funs;
     int          stack_size;
diff --git a/rebar.config b/rebar.config
deleted file mode 100644
index 43e2924..0000000
--- a/rebar.config
+++ /dev/null
@@ -1,2 +0,0 @@
-{erl_opts, [{platform_define, "win32", 'WINDOWS'}]}.
-{eunit_compile_opts, [{platform_define, "win32", 'WINDOWS'}]}.
diff --git a/rebar.config.script b/rebar.config.script
index 7d803b9..8d44099 100644
--- a/rebar.config.script
+++ b/rebar.config.script
@@ -36,7 +36,7 @@
     false ->
         string:strip(os:cmd("git describe --always"), right, $\n);
     Version0 ->
-        Version0
+        string:strip(Version0, right)
 end,
 
 CouchConfig = case filelib:is_file(os:getenv("COUCHDB_CONFIG")) of
@@ -132,7 +132,14 @@
         BaseSpecs
 end,
 
-AddConfig = [{port_specs, PortSpecs}].
+AddConfig = [
+    {port_specs, PortSpecs},
+    {erl_opts, [
+        {platform_define, "win32", 'WINDOWS'},
+        {d, 'COUCHDB_VERSION', Version}
+    ]},
+    {eunit_compile_opts, [{platform_define, "win32", 'WINDOWS'}]}
+].
 
 lists:foldl(fun({K, V}, CfgAcc) ->
     lists:keystore(K, 1, CfgAcc, {K, V})
diff --git a/src/couch_auth_cache.erl b/src/couch_auth_cache.erl
index 32d706d..9b00a9d 100644
--- a/src/couch_auth_cache.erl
+++ b/src/couch_auth_cache.erl
@@ -12,7 +12,7 @@
 
 -module(couch_auth_cache).
 -behaviour(gen_server).
--vsn(2).
+-vsn(3).
 -behaviour(config_listener).
 
 % public API
@@ -33,6 +33,8 @@
 -define(BY_USER, auth_by_user_ets).
 -define(BY_ATIME, auth_by_atime_ets).
 
+-define(RELISTEN_DELAY, 5000).
+
 -record(state, {
     max_cache_size = 0,
     cache_size = 0,
@@ -242,7 +244,11 @@
             {stop, Reason, State};
         NewClosed ->
             {noreply, reinit_cache(State#state{closed = NewClosed})}
-    end.
+    end;
+handle_info(restart_config_listener, State) ->
+    ok = config:listen_for_changes(?MODULE, nil),
+    {noreply, State}.
+
 
 
 terminate(_Reason, #state{event_listener = Listener}) ->
@@ -265,12 +271,10 @@
 handle_config_change(_, _, _, _, _) ->
     {ok, nil}.
 
-handle_config_terminate(_, stop, _) -> ok;
-handle_config_terminate(_, _, _) ->
-    spawn(fun() ->
-        timer:sleep(5000),
-        config:listen_for_changes(?MODULE, nil)
-    end).
+handle_config_terminate(_, stop, _) ->
+    ok;
+handle_config_terminate(_Server, _Reason, _State) ->
+    erlang:send_after(?RELISTEN_DELAY, whereis(?MODULE), restart_config_listener).
 
 clear_cache(State) ->
     exec_if_auth_db(fun(AuthDb) -> catch couch_db:close(AuthDb) end),
diff --git a/src/couch_compaction_daemon.erl b/src/couch_compaction_daemon.erl
index 7888eb4..8f95eb2 100644
--- a/src/couch_compaction_daemon.erl
+++ b/src/couch_compaction_daemon.erl
@@ -12,6 +12,7 @@
 
 -module(couch_compaction_daemon).
 -behaviour(gen_server).
+-vsn(1).
 -behaviour(config_listener).
 
 % public API
@@ -29,6 +30,8 @@
 
 -define(CONFIG_ETS, couch_compaction_daemon_config).
 
+-define(RELISTEN_DELAY, 5000).
+
 -record(state, {
     loop_pid,
     in_progress = []
@@ -98,7 +101,10 @@
 
 
 handle_info({'EXIT', Pid, Reason}, #state{loop_pid = Pid} = State) ->
-    {stop, {compaction_loop_died, Reason}, State}.
+    {stop, {compaction_loop_died, Reason}, State};
+handle_info(restart_config_listener, State) ->
+    ok = config:listen_for_changes(?MODULE, nil),
+    {noreply, State}.
 
 
 terminate(_Reason, _State) ->
@@ -114,12 +120,10 @@
 handle_config_change(_, _, _, _, _) ->
     {ok, nil}.
 
-handle_config_terminate(_, stop, _) -> ok;
-handle_config_terminate(_, _, _) ->
-    spawn(fun() ->
-        timer:sleep(5000),
-        config:listen_for_changes(?MODULE, nil)
-    end).
+handle_config_terminate(_, stop, _) ->
+    ok;
+handle_config_terminate(_Server, _Reason, _State) ->
+    erlang:send_after(?RELISTEN_DELAY, whereis(?MODULE), restart_config_listener).
 
 compact_loop(Parent) ->
     {ok, _} = couch_server:all_databases(
diff --git a/src/couch_db.erl b/src/couch_db.erl
index 8260a5c..26c6ae7 100644
--- a/src/couch_db.erl
+++ b/src/couch_db.erl
@@ -1519,13 +1519,17 @@
 normalize_dbname(DbName) when is_list(DbName) ->
     normalize_dbname(list_to_binary(DbName));
 normalize_dbname(DbName) when is_binary(DbName) ->
+    mem3:dbname(maybe_remove_extension(DbName)).
+
+maybe_remove_extension(DbName) ->
     case filename:extension(DbName) of
         <<".couch">> ->
-            mem3:dbname(filename:rootname(DbName));
+            filename:rootname(DbName);
         _ ->
-            mem3:dbname(DbName)
+            DbName
     end.
 
+
 -spec dbname_suffix(list() | binary()) -> binary().
 
 dbname_suffix(DbName) ->
@@ -1536,15 +1540,12 @@
     validate_dbname(?l2b(DbName));
 validate_dbname(DbName) when is_binary(DbName) ->
     Normalized = normalize_dbname(DbName),
-    case couch_db_plugin:validate_dbname(DbName, Normalized) of
-        true ->
-            ok;
-        false ->
-            validate_dbname_int(DbName, Normalized)
-    end.
+    couch_db_plugin:validate_dbname(
+        DbName, Normalized, fun validate_dbname_int/2).
 
 validate_dbname_int(DbName, Normalized) when is_binary(DbName) ->
-    case re:run(DbName, ?DBNAME_REGEX, [{capture,none}, dollar_endonly]) of
+    DbNoExt = maybe_remove_extension(DbName),
+    case re:run(DbNoExt, ?DBNAME_REGEX, [{capture,none}, dollar_endonly]) of
         match ->
             ok;
         nomatch ->
@@ -1563,23 +1564,23 @@
 -include_lib("eunit/include/eunit.hrl").
 
 setup() ->
-    ok = meck:new(couch_db_plugin, [passthrough]),
-    ok = meck:expect(couch_db_plugin, validate_dbname, fun(_, _) -> false end),
+    ok = meck:new(couch_epi, [passthrough]),
+    ok = meck:expect(couch_epi, decide, fun(_, _, _, _, _) -> no_decision end),
     ok.
 
 teardown(_) ->
-    (catch meck:unload(couch_db_plugin)).
+    (catch meck:unload(couch_epi)).
 
 validate_dbname_success_test_() ->
     Cases =
-        generate_cases_with_shards("long/co$mplex-/path+/_something")
+        generate_cases_with_shards("long/co$mplex-/path+/something")
         ++ generate_cases_with_shards("something")
         ++ lists:append(
             [generate_cases_with_shards(?b2l(SystemDb))
                 || SystemDb <- ?SYSTEM_DATABASES]),
     {
         foreach, fun setup/0, fun teardown/1,
-        [{test_name(A), fun() -> should_pass_validate_dbname(A) end} || {_, A} <- Cases]
+        [should_pass_validate_dbname(A) || {_, A} <- Cases]
     }.
 
 validate_dbname_fail_test_() ->
@@ -1589,7 +1590,7 @@
        ++ generate_cases_with_shards("long/co$mplex-/path+/some.thing"),
     {
         foreach, fun setup/0, fun teardown/1,
-        [{test_name(A), fun() -> should_fail_validate_dbname(A) end} || {_, A} <- Cases]
+        [should_fail_validate_dbname(A) || {_, A} <- Cases]
     }.
 
 normalize_dbname_test_() ->
diff --git a/src/couch_db_plugin.erl b/src/couch_db_plugin.erl
index 1a6d2ea..774e9e0 100644
--- a/src/couch_db_plugin.erl
+++ b/src/couch_db_plugin.erl
@@ -13,7 +13,7 @@
 -module(couch_db_plugin).
 
 -export([
-    validate_dbname/2,
+    validate_dbname/3,
     before_doc_update/2,
     after_doc_read/2,
     validate_docid/1,
@@ -29,10 +29,8 @@
 %% API Function Definitions
 %% ------------------------------------------------------------------
 
-validate_dbname(DbName, Normalized) ->
-    Handle = couch_epi:get_handle(?SERVICE_ID),
-    %% callbacks return true only if it specifically allow the given Id
-    couch_epi:any(Handle, ?SERVICE_ID, validate_dbname, [DbName, Normalized], []).
+validate_dbname(DbName, Normalized, Default) ->
+    maybe_handle(validate_dbname, [DbName, Normalized], Default).
 
 before_doc_update(#db{before_doc_update = Fun} = Db, Doc0) ->
     case with_pipe(before_doc_update, [Doc0, Db]) of
@@ -70,3 +68,14 @@
 do_apply(Func, Args, Opts) ->
     Handle = couch_epi:get_handle(?SERVICE_ID),
     couch_epi:apply(Handle, ?SERVICE_ID, Func, Args, Opts).
+
+maybe_handle(Func, Args, Default) ->
+    Handle = couch_epi:get_handle(?SERVICE_ID),
+    case couch_epi:decide(Handle, ?SERVICE_ID, Func, Args, []) of
+       no_decision when is_function(Default) ->
+           apply(Default, Args);
+       no_decision ->
+           Default;
+       {decided, Result} ->
+           Result
+    end.
diff --git a/src/couch_doc.erl b/src/couch_doc.erl
index 11063d9..af14038 100644
--- a/src/couch_doc.erl
+++ b/src/couch_doc.erl
@@ -169,6 +169,10 @@
 
 validate_docid(<<"">>) ->
     throw({illegal_docid, <<"Document id must not be empty">>});
+validate_docid(<<"_design/">>) ->
+    throw({illegal_docid, <<"Illegal document id `_design/`">>});
+validate_docid(<<"_local/">>) ->
+    throw({illegal_docid, <<"Illegal document id `_local/`">>});
 validate_docid(Id) when is_binary(Id) ->
     case couch_util:validate_utf8(Id) of
         false -> throw({illegal_docid, <<"Document id must be valid UTF-8">>});
diff --git a/src/couch_external_manager.erl b/src/couch_external_manager.erl
index 1aeb39a..f131342 100644
--- a/src/couch_external_manager.erl
+++ b/src/couch_external_manager.erl
@@ -12,7 +12,7 @@
 
 -module(couch_external_manager).
 -behaviour(gen_server).
--vsn(2).
+-vsn(3).
 -behaviour(config_listener).
 
 -export([start_link/0, execute/2]).
@@ -23,6 +23,8 @@
 
 -include_lib("couch/include/couch_db.hrl").
 
+-define(RELISTEN_DELAY, 5000).
+
 start_link() ->
     gen_server:start_link({local, couch_external_manager},
         couch_external_manager, [], []).
@@ -41,12 +43,11 @@
 handle_config_change(_, _, _, _, _) ->
     {ok, nil}.
 
-handle_config_terminate(_, stop, _) -> ok;
-handle_config_terminate(_, _, _) ->
-    spawn(fun() ->
-        timer:sleep(5000),
-        config:listen_for_changes(?MODULE, nil)
-    end).
+handle_config_terminate(_, stop, _) ->
+    ok;
+handle_config_terminate(_Server, _Reason, _State) ->
+    erlang:send_after(?RELISTEN_DELAY, whereis(?MODULE), restart_config_listener).
+
 
 % gen_server API
 
@@ -108,7 +109,12 @@
     % Remove Pid from the handlers table so we don't try closing
     % it a second time in terminate/2.
     ets:match_delete(Handlers, {'_', Pid}),
-    {stop, normal, Handlers}.
+    {stop, normal, Handlers};
+
+handle_info(restart_config_listener, State) ->
+    ok = config:listen_for_changes(?MODULE, nil),
+    {noreply, State}.
+
 
 code_change(_OldVsn, State, _Extra) ->
     {ok, State}.
diff --git a/src/couch_external_server.erl b/src/couch_external_server.erl
index ff2e185..e2a5022 100644
--- a/src/couch_external_server.erl
+++ b/src/couch_external_server.erl
@@ -12,17 +12,16 @@
 
 -module(couch_external_server).
 -behaviour(gen_server).
--vsn(2).
--behaviour(config_listener).
+-vsn(3).
 
 -export([start_link/2, stop/1, execute/2]).
 -export([init/1, terminate/2, handle_call/3, handle_cast/2, handle_info/2, code_change/3]).
 
-% config_listener api
--export([handle_config_change/5, handle_config_terminate/3]).
-
 -include_lib("couch/include/couch_db.hrl").
 
+-define(RELISTEN_DELAY, 5000).
+-define(CONFIG_SUBSCRIPTION, [{"couchdb", "os_process_timeout"}]).
+
 % External API
 
 start_link(Name, Command) ->
@@ -40,47 +39,52 @@
 init([Name, Command]) ->
     couch_log:info("EXTERNAL: Starting process for: ~s", [Name]),
     couch_log:info("COMMAND: ~s", [Command]),
+    ok = config:subscribe_for_changes(?CONFIG_SUBSCRIPTION),
     process_flag(trap_exit, true),
     Timeout = list_to_integer(config:get("couchdb", "os_process_timeout",
         "5000")),
     {ok, Pid} = couch_os_process:start_link(Command, [{timeout, Timeout}]),
-    ok = config:listen_for_changes(?MODULE, Pid),
-    {ok, {Name, Command, Pid}}.
+    {ok, {Name, Command, Pid, whereis(config_event)}}.
 
-terminate(_Reason, {_Name, _Command, Pid}) ->
+terminate(_Reason, {_Name, _Command, Pid, _}) ->
     couch_os_process:stop(Pid),
     ok.
 
-handle_call({execute, JsonReq}, _From, {Name, Command, Pid}) ->
+handle_call({execute, JsonReq}, _From, {Name, Command, Pid, _}) ->
     {reply, couch_os_process:prompt(Pid, JsonReq), {Name, Command, Pid}}.
 
 handle_info({'EXIT', _Pid, normal}, State) ->
     {noreply, State};
-handle_info({'EXIT', Pid, Reason}, {Name, Command, Pid}) ->
+handle_info({'EXIT', Pid, Reason}, {Name, Command, Pid, _}) ->
     couch_log:info("EXTERNAL: Process for ~s exiting. (reason: ~w)",
                    [Name, Reason]),
-    {stop, Reason, {Name, Command, Pid}}.
+    {stop, Reason, {Name, Command, Pid}};
+handle_info({config_change, "couchdb", "os_process_timeout", NewTimeout, _},
+        {_Name, _Command, Pid, _} = State) ->
+    couch_os_process:set_timeout(Pid, list_to_integer(NewTimeout)),
+    {noreply, State};
+handle_info({gen_event_EXIT, _Handler, _Reason}, State) ->
+    erlang:send_after(?RELISTEN_DELAY, self(), restart_config_listener),
+    {noreply, State};
+handle_info({'EXIT', Pid, _Reason}, {_, _, _, Pid} = State) ->
+    erlang:send_after(?RELISTEN_DELAY, self(), restart_config_listener),
+    {noreply, State};
+handle_info(restart_config_listener, {Name, Command, Pid, _} = State) ->
+    case whereis(config_event) of
+        undefined ->
+            erlang:send_after(?RELISTEN_DELAY, self(), restart_config_listener),
+            {noreply, State};
+        EventMgr ->
+            ok = config:subscribe_for_changes(?CONFIG_SUBSCRIPTION),
+            {noreply, {Name, Command, Pid, EventMgr}}
+    end.
 
-handle_cast(stop, {Name, Command, Pid}) ->
+handle_cast(stop, {Name, _Command, Pid, _} = State) ->
     couch_log:info("EXTERNAL: Shutting down ~s", [Name]),
     exit(Pid, normal),
-    {stop, normal, {Name, Command, Pid}};
+    {stop, normal, State};
 handle_cast(_Whatever, State) ->
     {noreply, State}.
 
 code_change(_OldVsn, State, _Extra) ->
     {ok, State}.
-
-
-handle_config_change("couchdb", "os_process_timeout", NewTimeout, _, Pid) ->
-    couch_os_process:set_timeout(Pid, list_to_integer(NewTimeout)),
-    {ok, Pid};
-handle_config_change(_, _, _, _, Pid) ->
-    {ok, Pid}.
-
-handle_config_terminate(_, stop, _) -> ok;
-handle_config_terminate(_, _, _) ->
-    spawn(fun() ->
-        timer:sleep(5000),
-        config:listen_for_changes(?MODULE, nil)
-    end).
diff --git a/src/couch_file.erl b/src/couch_file.erl
index 8346b02..e7d9413 100644
--- a/src/couch_file.erl
+++ b/src/couch_file.erl
@@ -22,7 +22,12 @@
 -define(SIZE_BLOCK, 16#1000). % 4 KiB
 -define(READ_AHEAD, 2 * ?SIZE_BLOCK).
 -define(IS_OLD_STATE(S), tuple_size(S) /= tuple_size(#file{})).
+-define(PREFIX_SIZE, 5).
+-define(DEFAULT_READ_COUNT, 1024).
 
+-type block_id() :: non_neg_integer().
+-type location() :: non_neg_integer().
+-type header_size() :: non_neg_integer().
 
 -record(file, {
     fd,
@@ -524,34 +529,83 @@
     {stop, Reason, Fd}.
 
 
-find_header(_Fd, -1) ->
-    no_valid_header;
 find_header(Fd, Block) ->
     case (catch load_header(Fd, Block)) of
     {ok, Bin} ->
         {ok, Bin};
     _Error ->
-        find_header(Fd, Block -1)
+        ReadCount = config:get_integer(
+            "couchdb", "find_header_read_count", ?DEFAULT_READ_COUNT),
+        find_header(Fd, Block -1, ReadCount)
     end.
 
 load_header(Fd, Block) ->
     {ok, <<1, HeaderLen:32/integer, RestBlock/binary>>} =
         file:pread(Fd, Block * ?SIZE_BLOCK, ?SIZE_BLOCK),
-    TotalBytes = calculate_total_read_len(5, HeaderLen),
-    case TotalBytes > byte_size(RestBlock) of
-    false ->
-        <<RawBin:TotalBytes/binary, _/binary>> = RestBlock;
-    true ->
-        {ok, Missing} = file:pread(
-            Fd, (Block * ?SIZE_BLOCK) + 5 + byte_size(RestBlock),
-            TotalBytes - byte_size(RestBlock)),
-        RawBin = <<RestBlock/binary, Missing/binary>>
+    load_header(Fd, Block * ?SIZE_BLOCK, HeaderLen, RestBlock).
+
+load_header(Fd, Pos, HeaderLen) ->
+    load_header(Fd, Pos, HeaderLen, <<>>).
+
+load_header(Fd, Pos, HeaderLen, RestBlock) ->
+    TotalBytes = calculate_total_read_len(?PREFIX_SIZE, HeaderLen),
+    RawBin = case TotalBytes =< byte_size(RestBlock) of
+        true ->
+            <<RawBin0:TotalBytes/binary, _/binary>> = RestBlock,
+            RawBin0;
+        false ->
+            ReadStart = Pos + ?PREFIX_SIZE + byte_size(RestBlock),
+            ReadLen = TotalBytes - byte_size(RestBlock),
+            {ok, Missing} = file:pread(Fd, ReadStart, ReadLen),
+            <<RestBlock/binary, Missing/binary>>
     end,
     <<Md5Sig:16/binary, HeaderBin/binary>> =
-        iolist_to_binary(remove_block_prefixes(5, RawBin)),
+        iolist_to_binary(remove_block_prefixes(?PREFIX_SIZE, RawBin)),
     Md5Sig = couch_crypto:hash(md5, HeaderBin),
     {ok, HeaderBin}.
 
+
+%% Read multiple block locations using a single file:pread/2.
+-spec find_header(file:fd(), block_id(), non_neg_integer()) ->
+    {ok, binary()} | no_valid_header.
+find_header(_Fd, Block, _ReadCount) when Block < 0 ->
+    no_valid_header;
+find_header(Fd, Block, ReadCount) ->
+    FirstBlock = max(0, Block - ReadCount + 1),
+    BlockLocations = [?SIZE_BLOCK*B || B <- lists:seq(FirstBlock, Block)],
+    {ok, DataL} = file:pread(Fd, [{L, ?PREFIX_SIZE} || L <- BlockLocations]),
+    %% Since BlockLocations are ordered from oldest to newest, we rely
+    %% on lists:foldl/3 to reverse the order, making HeaderLocations
+    %% correctly ordered from newest to oldest.
+    HeaderLocations = lists:foldl(fun
+        ({Loc, <<1, HeaderSize:32/integer>>}, Acc) ->
+            [{Loc, HeaderSize} | Acc];
+        (_, Acc) ->
+            Acc
+    end, [], lists:zip(BlockLocations, DataL)),
+    case find_newest_header(Fd, HeaderLocations) of
+        {ok, _Location, HeaderBin} ->
+            {ok, HeaderBin};
+        _ ->
+            ok = file:advise(
+                Fd, hd(BlockLocations), ReadCount * ?SIZE_BLOCK, dont_need),
+            NextBlock = hd(BlockLocations) div ?SIZE_BLOCK - 1,
+            find_header(Fd, NextBlock, ReadCount)
+    end.
+
+-spec find_newest_header(file:fd(), [{location(), header_size()}]) ->
+    {ok, location(), binary()} | not_found.
+find_newest_header(_Fd, []) ->
+    not_found;
+find_newest_header(Fd, [{Location, Size} | LocationSizes]) ->
+    case (catch load_header(Fd, Location, Size)) of
+        {ok, HeaderBin} ->
+            {ok, Location, HeaderBin};
+        _Error ->
+            find_newest_header(Fd, LocationSizes)
+    end.
+
+
 maybe_read_more_iolist(Buffer, DataSize, _, _)
     when DataSize =< byte_size(Buffer) ->
     <<Data:DataSize/binary, _/binary>> = Buffer,
diff --git a/src/couch_httpd.erl b/src/couch_httpd.erl
index 2dd6ed2..7c4e573 100644
--- a/src/couch_httpd.erl
+++ b/src/couch_httpd.erl
@@ -170,7 +170,7 @@
     AuthHandlers = lists:map(
         fun(A) -> {auth_handler_name(A), make_arity_1_fun(A)} end, AuthenticationSrcs),
     AuthenticationFuns = AuthHandlers ++ [
-        {<<"local">>, fun couch_httpd_auth:party_mode_handler/1} %% should be last
+        fun couch_httpd_auth:party_mode_handler/1 %% must be last
     ],
     ok = application:set_env(couch, auth_handlers, AuthenticationFuns).
 
diff --git a/src/couch_httpd_auth.erl b/src/couch_httpd_auth.erl
index 15d3ac6..ec7ede1 100644
--- a/src/couch_httpd_auth.erl
+++ b/src/couch_httpd_auth.erl
@@ -91,7 +91,7 @@
         case AuthModule:get_user_creds(Req, User) of
             nil ->
                 throw({unauthorized, <<"Name or password is incorrect.">>});
-            {ok, UserProps, AuthCtx} ->
+            {ok, UserProps, _AuthCtx} ->
                 reject_if_totp(UserProps),
                 UserName = ?l2b(User),
                 Password = ?l2b(Pass),
@@ -298,7 +298,7 @@
     UserName = ?l2b(extract_username(Form)),
     Password = ?l2b(couch_util:get_value("password", Form, "")),
     couch_log:debug("Attempt Login: ~s",[UserName]),
-    {ok, UserProps, AuthCtx} = case AuthModule:get_user_creds(Req, UserName) of
+    {ok, UserProps, _AuthCtx} = case AuthModule:get_user_creds(Req, UserName) of
         nil -> {ok, [], nil};
         Result -> Result
     end,
diff --git a/src/couch_httpd_vhost.erl b/src/couch_httpd_vhost.erl
index 05fc874..91a2476 100644
--- a/src/couch_httpd_vhost.erl
+++ b/src/couch_httpd_vhost.erl
@@ -12,6 +12,7 @@
 
 -module(couch_httpd_vhost).
 -behaviour(gen_server).
+-vsn(1).
 -behaviour(config_listener).
 
 -export([start_link/0, reload/0, get_state/0, dispatch_host/1]).
@@ -27,6 +28,7 @@
 
 -define(SEPARATOR, $\/).
 -define(MATCH_ALL, {bind, '*'}).
+-define(RELISTEN_DELAY, 5000).
 
 -record(vhosts_state, {
         vhosts,
@@ -355,6 +357,9 @@
 handle_cast(_Msg, State) ->
     {noreply, State}.
 
+handle_info(restart_config_listener, State) ->
+    ok = config:listen_for_changes(?MODULE, nil),
+    {noreply, State};
 handle_info(_Info, State) ->
     {noreply, State}.
 
@@ -374,12 +379,10 @@
 handle_config_change(_, _, _, _, _) ->
     {ok, nil}.
 
-handle_config_terminate(_, stop, _) -> ok;
-handle_config_terminate(_, _, _) ->
-    spawn(fun() ->
-        timer:sleep(5000),
-        config:listen_for_changes(?MODULE, nil)
-    end).
+handle_config_terminate(_, stop, _) ->
+    ok;
+handle_config_terminate(_Server, _Reason, _State) ->
+    erlang:send_after(?RELISTEN_DELAY, whereis(?MODULE), restart_config_listener).
 
 load_conf() ->
     %% get vhost globals
diff --git a/src/couch_os_daemons.erl b/src/couch_os_daemons.erl
index 409ba02..2c2c1a2 100644
--- a/src/couch_os_daemons.erl
+++ b/src/couch_os_daemons.erl
@@ -11,6 +11,7 @@
 % the License.
 -module(couch_os_daemons).
 -behaviour(gen_server).
+-vsn(1).
 -behaviour(config_listener).
 
 -export([start_link/0, info/0, info/1]).
@@ -36,6 +37,7 @@
 
 -define(PORT_OPTIONS, [stream, {line, 1024}, binary, exit_status, hide]).
 -define(TIMEOUT, 5000).
+-define(RELISTEN_DELAY, 5000).
 
 start_link() ->
     gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
@@ -183,6 +185,9 @@
     [D] = ets:lookup(Table, Port),
     true = ets:insert(Table, D#daemon{status=restarting, buf=nil}),
     {noreply, Table};
+handle_info(restart_config_listener, State) ->
+    ok = config:listen_for_changes(?MODULE, nil),
+    {noreply, State};
 handle_info(Msg, Table) ->
     couch_log:error("Unexpected info message to ~p: ~p", [?MODULE, Msg]),
     {stop, error, Table}.
@@ -195,12 +200,11 @@
     gen_server:cast(?MODULE, {config_change, Section, Key}),
     {ok, nil}.
 
-handle_config_terminate(_, stop, _) -> ok;
-handle_config_terminate(_, _, _) ->
-    spawn(fun() ->
-        timer:sleep(5000),
-        config:listen_for_changes(?MODULE, nil)
-    end).
+handle_config_terminate(_, stop, _) ->
+    ok;
+handle_config_terminate(_Server, _Reason, _State) ->
+    erlang:send_after(?RELISTEN_DELAY, whereis(?MODULE), restart_config_listener).
+
 
 % Internal API
 
diff --git a/src/couch_os_process.erl b/src/couch_os_process.erl
index 4f15555..5852233 100644
--- a/src/couch_os_process.erl
+++ b/src/couch_os_process.erl
@@ -146,7 +146,7 @@
 % gen_server API
 init([Command, Options, PortOptions]) ->
     PrivDir = couch_util:priv_dir(),
-    Spawnkiller = filename:join(PrivDir, "couchspawnkillable"),
+    Spawnkiller = "\"" ++ filename:join(PrivDir, "couchspawnkillable") ++ "\"",
     V = config:get("query_server_config", "os_process_idle_limit", "300"),
     IdleLimit = list_to_integer(V) * 1000,
     BaseProc = #os_proc{
diff --git a/src/couch_proc_manager.erl b/src/couch_proc_manager.erl
index 33cc1e5..04101f2 100644
--- a/src/couch_proc_manager.erl
+++ b/src/couch_proc_manager.erl
@@ -13,7 +13,7 @@
 -module(couch_proc_manager).
 -behaviour(gen_server).
 -behaviour(config_listener).
--vsn(2).
+-vsn(3).
 
 -export([
     start_link/0,
@@ -43,6 +43,7 @@
 -define(PROCS, couch_proc_manager_procs).
 -define(WAITERS, couch_proc_manager_waiters).
 -define(OPENING, couch_proc_manager_opening).
+-define(RELISTEN_DELAY, 5000).
 
 -record(state, {
     config,
@@ -131,35 +132,17 @@
     {reply, ets:select_count(?PROCS, MatchSpec), State};
 
 handle_call({get_proc, #doc{body={Props}}=DDoc, DDocKey}, From, State) ->
-    {ClientPid, _} = From,
     LangStr = couch_util:get_value(<<"language">>, Props, <<"javascript">>),
     Lang = couch_util:to_binary(LangStr),
-    IterFun = fun(Proc, Acc) ->
-        case lists:member(DDocKey, Proc#proc_int.ddoc_keys) of
-            true ->
-                {stop, assign_proc(ClientPid, Proc)};
-            false ->
-                {ok, Acc}
-        end
-    end,
-    TeachFun = fun(Proc0, Acc) ->
-        try
-            {ok, Proc1} = teach_ddoc(DDoc, DDocKey, Proc0),
-            {stop, assign_proc(ClientPid, Proc1)}
-        catch _:_ ->
-            {ok, Acc}
-        end
-    end,
     Client = #client{from=From, lang=Lang, ddoc=DDoc, ddoc_key=DDocKey},
-    find_proc(State, Client, [IterFun, TeachFun]);
+    add_waiting_client(Client),
+    {noreply, flush_waiters(State, Lang)};
 
-handle_call({get_proc, Lang}, From, State) ->
-    {ClientPid, _} = From,
-    IterFun = fun(Proc, _Acc) ->
-        {stop, assign_proc(ClientPid, Proc)}
-    end,
-    Client = #client{from=From, lang=couch_util:to_binary(Lang)},
-    find_proc(State, Client, [IterFun]);
+handle_call({get_proc, LangStr}, From, State) ->
+    Lang = couch_util:to_binary(LangStr),
+    Client = #client{from=From, lang=Lang},
+    add_waiting_client(Client),
+    {noreply, flush_waiters(State, Lang)};
 
 handle_call({ret_proc, #proc{client=Ref} = Proc}, _From, State) ->
     erlang:demonitor(Ref, [flush]),
@@ -266,6 +249,11 @@
             {noreply, State0}
     end;
 
+
+handle_info(restart_config_listener, State) ->
+    ok = config:listen_for_changes(?MODULE, nil),
+    {noreply, State};
+
 handle_info(_Msg, State) ->
     {noreply, State}.
 
@@ -273,15 +261,11 @@
 code_change(_OldVsn, #state{}=State, _Extra) ->
     {ok, State}.
 
-handle_config_terminate(_, stop, _) -> ok;
-handle_config_terminate(_, _, _) ->
-    spawn(fun() ->
-        timer:sleep(5000),
-        config:listen_for_changes(?MODULE, undefined),
-        % Reload our config in case it changed in the last
-        % five seconds.
-        gen_server:cast(?MODULE, reload_config)
-    end).
+handle_config_terminate(_, stop, _) ->
+    ok;
+handle_config_terminate(_Server, _Reason, _State) ->
+    gen_server:cast(?MODULE, reload_config),
+    erlang:send_after(?RELISTEN_DELAY, whereis(?MODULE), restart_config_listener).
 
 handle_config_change("query_server_config", _, _, _, _) ->
     gen_server:cast(?MODULE, reload_config),
@@ -290,54 +274,60 @@
     {ok, undefined}.
 
 
-find_proc(State, Client, [Fun | FindFuns]) ->
-    try iter_procs(Client#client.lang, Fun, undefined) of
-        {not_found, _} ->
-            find_proc(State, Client, FindFuns);
-        {ok, Proc} ->
-            {reply, {ok, Proc, State#state.config}, State}
+find_proc(#client{lang = Lang, ddoc_key = undefined}) ->
+    Pred = fun(_) ->
+        true
+    end,
+    find_proc(Lang, Pred);
+find_proc(#client{lang = Lang, ddoc = DDoc, ddoc_key = DDocKey} = Client) ->
+    Pred = fun(#proc_int{ddoc_keys = DDocKeys}) ->
+        lists:member(DDocKey, DDocKeys)
+    end,
+    case find_proc(Lang, Pred) of
+        not_found ->
+            case find_proc(Client#client{ddoc_key=undefined}) of
+                {ok, Proc} ->
+                    teach_ddoc(DDoc, DDocKey, Proc);
+                Else ->
+                    Else
+            end;
+        Else ->
+            Else
+    end.
+
+find_proc(Lang, Fun) ->
+    try iter_procs(Lang, Fun)
     catch error:Reason ->
-        couch_log:error("~p ~p ~p", [?MODULE, Reason, erlang:get_stacktrace()]),
-        {reply, {error, Reason}, State}
-    end;
-find_proc(State, Client, []) ->
-    {noreply, maybe_spawn_proc(State, Client)}.
+        StackTrace = erlang:get_stacktrace(),
+        couch_log:error("~p ~p ~p", [?MODULE, Reason, StackTrace]),
+        {error, Reason}
+    end.
 
 
-iter_procs(Lang, Fun, Acc) when is_binary(Lang) ->
+iter_procs(Lang, Fun) when is_binary(Lang) ->
     Pattern = #proc_int{lang=Lang, client=undefined, _='_'},
     MSpec = [{Pattern, [], ['$_']}],
     case ets:select_reverse(?PROCS, MSpec, 25) of
         '$end_of_table' ->
-            {not_found, Acc};
+            not_found;
         Continuation ->
-            iter_procs_int(Continuation, Fun, Acc)
+            iter_procs_int(Continuation, Fun)
     end.
 
 
-iter_procs_int({[], Continuation0}, Fun, Acc) ->
+iter_procs_int({[], Continuation0}, Fun) ->
     case ets:select_reverse(Continuation0) of
         '$end_of_table' ->
-            {not_found, Acc};
+            not_found;
         Continuation1 ->
-            iter_procs_int(Continuation1, Fun, Acc)
+            iter_procs_int(Continuation1, Fun)
     end;
-iter_procs_int({[Proc | Rest], Continuation}, Fun, Acc0) ->
-    case Fun(Proc, Acc0) of
-        {ok, Acc1} ->
-            iter_procs_int({Rest, Continuation}, Fun, Acc1);
-        {stop, Acc1} ->
-            {ok, Acc1}
-    end.
-
-
-maybe_spawn_proc(State, Client) ->
-    case dict:find(Client#client.lang, State#state.counts) of
-    {ok, Count} when Count >= State#state.hard_limit ->
-        add_waiting_client(Client),
-        State;
-    _ ->
-        spawn_proc(State, Client)
+iter_procs_int({[Proc | Rest], Continuation}, Fun) ->
+    case Fun(Proc) of
+        true ->
+            {ok, Proc};
+        false ->
+            iter_procs_int({Rest, Continuation}, Fun)
     end.
 
 
@@ -447,23 +437,13 @@
                 true = ets:update_element(?PROCS, Pid, [
                     {#proc_int.client, undefined}
                 ]),
-                maybe_assign_proc(State, ProcInt)
+                State
         end;
     false ->
         remove_proc(State, ProcInt)
     end,
     flush_waiters(NewState, Lang).
 
-maybe_assign_proc(#state{} = State, ProcInt) ->
-    #proc_int{lang = Lang} = ProcInt,
-    case get_waiting_client(Lang) of
-        #client{from = From} = Client ->
-            Proc = assign_proc(Client, ProcInt#proc_int{client=undefined}),
-            gen_server:reply(From, {ok, Proc, State#state.config}),
-            State;
-        undefined ->
-            State
-    end.
 
 remove_proc(State, #proc_int{}=Proc) ->
     ets:delete(?PROCS, Proc#proc_int.pid),
@@ -500,16 +480,27 @@
 
 
 flush_waiters(State, Lang) ->
-    case dict:fetch(Lang, State#state.counts) of
-        Count when Count < State#state.hard_limit ->
-            case get_waiting_client(Lang) of
-                #client{} = Client ->
+    CanSpawn = can_spawn(State, Lang),
+    case get_waiting_client(Lang) of
+        #client{from = From} = Client ->
+            case find_proc(Client) of
+                {ok, ProcInt} ->
+                    Proc = assign_proc(Client, ProcInt),
+                    gen_server:reply(From, {ok, Proc, State#state.config}),
+                    remove_waiting_client(Client),
+                    flush_waiters(State, Lang);
+                {error, Error} ->
+                    gen_server:reply(From, {error, Error}),
+                    remove_waiting_client(Client),
+                    flush_waiters(State, Lang);
+                not_found when CanSpawn ->
                     NewState = spawn_proc(State, Client),
+                    remove_waiting_client(Client),
                     flush_waiters(NewState, Lang);
-                undefined ->
+                not_found ->
                     State
             end;
-        _ ->
+        undefined ->
             State
     end.
 
@@ -523,11 +514,21 @@
         '$end_of_table' ->
             undefined;
         {[#client{}=Client], _} ->
-            ets:delete(?WAITERS, Client#client.timestamp),
             Client
     end.
 
 
+remove_waiting_client(#client{timestamp = Timestamp}) ->
+    ets:delete(?WAITERS, Timestamp).
+
+
+can_spawn(#state{hard_limit = HardLimit, counts = Counts}, Lang) ->
+    case dict:find(Lang, Counts) of
+        {ok, Count} -> Count < HardLimit;
+        error -> true
+    end.
+
+
 get_proc_config() ->
     Limit = config:get("query_server_config", "reduce_limit", "true"),
     Timeout = config:get("couchdb", "os_process_timeout", "5000"),
diff --git a/src/couch_query_servers.erl b/src/couch_query_servers.erl
index a3d7a47..ea7628e 100644
--- a/src/couch_query_servers.erl
+++ b/src/couch_query_servers.erl
@@ -22,7 +22,7 @@
 -export([with_ddoc_proc/2, proc_prompt/2, ddoc_prompt/3, ddoc_proc_prompt/3, json_doc/1]).
 
 % For 210-os-proc-pool.t
--export([get_os_process/1, ret_os_process/1]).
+-export([get_os_process/1, get_ddoc_process/2, ret_os_process/1]).
 
 -include_lib("couch/include/couch_db.hrl").
 
diff --git a/src/couch_server.erl b/src/couch_server.erl
index 2634bbf..417c791 100644
--- a/src/couch_server.erl
+++ b/src/couch_server.erl
@@ -13,7 +13,7 @@
 -module(couch_server).
 -behaviour(gen_server).
 -behaviour(config_listener).
--vsn(2).
+-vsn(3).
 
 -export([open/2,create/2,delete/2,get_version/0,get_version/1,get_uuid/0]).
 -export([all_databases/0, all_databases/2]).
@@ -28,6 +28,7 @@
 -include_lib("couch/include/couch_db.hrl").
 
 -define(MAX_DBS_OPEN, 100).
+-define(RELISTEN_DELAY, 5000).
 
 -record(server,{
     root_dir = [],
@@ -44,10 +45,7 @@
     couch:start().
 
 get_version() ->
-    case application:get_key(couch, vsn) of
-        {ok, Version} -> Version;
-        undefined -> "0.0.0"
-    end.
+    ?COUCHDB_VERSION. %% Defined in rebar.config.script
 get_version(short) ->
   %% strip git hash from version string
   [Version|_Rest] = string:tokens(get_version(), "+"),
@@ -121,9 +119,11 @@
 maybe_add_sys_db_callbacks(DbName, Options) ->
     DbsDbName = config:get("mem3", "shards_db", "_dbs"),
     NodesDbName = config:get("mem3", "nodes_db", "_nodes"),
-    IsReplicatorDb = path_ends_with(DbName, <<"_replicator">>),
-    IsUsersDb = DbName ==config:get("couch_httpd_auth", "authentication_db", "_users") orelse
-	path_ends_with(DbName, <<"_users">>),
+
+    IsReplicatorDb = path_ends_with(DbName, "_replicator"),
+    UsersDbSuffix = config:get("couchdb", "users_db_suffix", "_users"),
+    IsUsersDb = path_ends_with(DbName, "_users")
+        orelse path_ends_with(DbName, UsersDbSuffix),
     if
 	DbName == DbsDbName ->
 	    [sys_db | Options];
@@ -141,8 +141,10 @@
 	    Options
     end.
 
-path_ends_with(Path, Suffix) ->
-    Suffix == couch_db:normalize_dbname(Path).
+path_ends_with(Path, Suffix) when is_binary(Suffix) ->
+    Suffix =:= couch_db:dbname_suffix(Path);
+path_ends_with(Path, Suffix) when is_list(Suffix) ->
+    path_ends_with(Path, ?l2b(Suffix)).
 
 check_dbname(#server{}, DbName) ->
     couch_db:validate_dbname(DbName).
@@ -233,13 +235,10 @@
 handle_config_change(_, _, _, _, _) ->
     {ok, nil}.
 
-handle_config_terminate(_, stop, _) -> ok;
-handle_config_terminate(_, _, _) ->
-    spawn(fun() ->
-        timer:sleep(5000),
-        config:listen_for_changes(?MODULE, nil)
-    end).
-
+handle_config_terminate(_, stop, _) ->
+    ok;
+handle_config_terminate(_Server, _Reason, _State) ->
+    erlang:send_after(?RELISTEN_DELAY, whereis(?MODULE), restart_config_listener).
 
 
 all_databases() ->
@@ -527,6 +526,9 @@
     [] ->
         {noreply, Server}
     end;
+handle_info(restart_config_listener, State) ->
+    ok = config:listen_for_changes(?MODULE, nil),
+    {noreply, State};
 handle_info(Info, Server) ->
     {stop, {unknown_message, Info}, Server}.
 
@@ -541,3 +543,89 @@
         false -> Server#server{dbs_open=Server#server.dbs_open - 1};
         true -> Server
     end.
+
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+
+setup() ->
+    ok = meck:new(config, [passthrough]),
+    ok = meck:expect(config, get, fun config_get/3),
+    ok.
+
+teardown(_) ->
+    (catch meck:unload(config)).
+
+config_get("couchdb", "users_db_suffix", _) -> "users_db";
+config_get(_, _, _) -> undefined.
+
+maybe_add_sys_db_callbacks_pass_test_() ->
+    SysDbCases = [
+        "shards/00000000-3fffffff/foo/users_db.1415960794.couch",
+        "shards/00000000-3fffffff/foo/users_db.1415960794",
+        "shards/00000000-3fffffff/foo/users_db",
+        "shards/00000000-3fffffff/users_db.1415960794.couch",
+        "shards/00000000-3fffffff/users_db.1415960794",
+        "shards/00000000-3fffffff/users_db",
+
+        "shards/00000000-3fffffff/_users.1415960794.couch",
+        "shards/00000000-3fffffff/_users.1415960794",
+        "shards/00000000-3fffffff/_users",
+
+        "foo/users_db.couch",
+        "foo/users_db",
+        "users_db.couch",
+        "users_db",
+        "foo/_users.couch",
+        "foo/_users",
+        "_users.couch",
+        "_users",
+
+        "shards/00000000-3fffffff/foo/_replicator.1415960794.couch",
+        "shards/00000000-3fffffff/foo/_replicator.1415960794",
+        "shards/00000000-3fffffff/_replicator",
+        "foo/_replicator.couch",
+        "foo/_replicator",
+        "_replicator.couch",
+        "_replicator"
+    ],
+
+    NonSysDbCases = [
+        "shards/00000000-3fffffff/foo/mydb.1415960794.couch",
+        "shards/00000000-3fffffff/foo/mydb.1415960794",
+        "shards/00000000-3fffffff/mydb",
+        "foo/mydb.couch",
+        "foo/mydb",
+        "mydb.couch",
+        "mydb"
+    ],
+    {
+        foreach, fun setup/0, fun teardown/1,
+        [
+            [should_add_sys_db_callbacks(C) || C <- SysDbCases]
+            ++
+            [should_add_sys_db_callbacks(?l2b(C)) || C <- SysDbCases]
+            ++
+            [should_not_add_sys_db_callbacks(C) || C <- NonSysDbCases]
+            ++
+            [should_not_add_sys_db_callbacks(?l2b(C)) || C <- NonSysDbCases]
+        ]
+    }.
+
+should_add_sys_db_callbacks(DbName) ->
+    {test_name(DbName), ?_test(begin
+        Options = maybe_add_sys_db_callbacks(DbName, [other_options]),
+        ?assert(lists:member(sys_db, Options)),
+        ok
+    end)}.
+should_not_add_sys_db_callbacks(DbName) ->
+    {test_name(DbName), ?_test(begin
+        Options = maybe_add_sys_db_callbacks(DbName, [other_options]),
+        ?assertNot(lists:member(sys_db, Options)),
+        ok
+    end)}.
+
+test_name(DbName) ->
+    lists:flatten(io_lib:format("~p", [DbName])).
+
+
+-endif.
diff --git a/src/couch_sup.erl b/src/couch_sup.erl
index f7a4344..8dcaf1d 100644
--- a/src/couch_sup.erl
+++ b/src/couch_sup.erl
@@ -12,6 +12,7 @@
 
 -module(couch_sup).
 -behaviour(supervisor).
+-vsn(1).
 -behaviour(config_listener).
 
 
@@ -35,7 +36,6 @@
             notify_started(),
             notify_uris(),
             write_uris(),
-            ok = config:listen_for_changes(?MODULE, nil),
             Resp;
         Else ->
             notify_error(Else),
@@ -47,6 +47,14 @@
     couch_log:info("Starting ~s", [?MODULE]),
     {ok, {{one_for_one,10, 60}, [
         {
+            config_listener_mon,
+            {config_listener_mon, start_link, [?MODULE, nil]},
+            permanent,
+            5000,
+            worker,
+            [config_listener_mon]
+        },
+        {
             couch_primary_services,
             {couch_primary_sup, start_link, []},
             permanent,
@@ -76,12 +84,8 @@
 handle_config_change(_, _, _, _, _) ->
     {ok, nil}.
 
-handle_config_terminate(_, stop, _) -> ok;
-handle_config_terminate(_, _, _) ->
-    spawn(fun() ->
-        timer:sleep(5000),
-        config:listen_for_changes(?MODULE, undefined)
-    end).
+handle_config_terminate(_Server, _Reason, _State) ->
+    ok.
 
 notify_starting() ->
     couch_log:info("Apache CouchDB ~s is starting.~n", [
diff --git a/src/couch_uuids.erl b/src/couch_uuids.erl
index 5e1fda9..0553243 100644
--- a/src/couch_uuids.erl
+++ b/src/couch_uuids.erl
@@ -13,7 +13,7 @@
 -include_lib("couch/include/couch_db.hrl").
 
 -behaviour(gen_server).
--vsn(2).
+-vsn(3).
 -behaviour(config_listener).
 
 -export([start/0, stop/0]).
@@ -25,6 +25,8 @@
 % config_listener api
 -export([handle_config_change/5, handle_config_terminate/3]).
 
+-define(RELISTEN_DELAY, 5000).
+
 start() ->
     gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
 
@@ -77,6 +79,9 @@
 handle_cast(_Msg, State) ->
     {noreply, State}.
 
+handle_info(restart_config_listener, State) ->
+    ok = config:listen_for_changes(?MODULE, nil),
+    {noreply, State};
 handle_info(_Info, State) ->
     {noreply, State}.
 
@@ -88,15 +93,11 @@
 handle_config_change(_, _, _, _, _) ->
     {ok, nil}.
 
-handle_config_terminate(_, stop, _) -> ok;
-handle_config_terminate(_, _, _) ->
-    spawn(fun() ->
-        timer:sleep(5000),
-        config:listen_for_changes(?MODULE, undefined),
-        % Reload our config in case it changed in the last
-        % five seconds.
-        gen_server:cast(?MODULE, change)
-    end).
+handle_config_terminate(_, stop, _) ->
+    ok;
+handle_config_terminate(_Server, _Reason, _State) ->
+    gen_server:cast(?MODULE, change),
+    erlang:send_after(?RELISTEN_DELAY, whereis(?MODULE), restart_config_listener).
 
 new_prefix() ->
     couch_util:to_hex((crypto:rand_bytes(13))).
diff --git a/src/test_util.erl b/src/test_util.erl
index 88cfc63..3c4d170 100644
--- a/src/test_util.erl
+++ b/src/test_util.erl
@@ -220,7 +220,7 @@
     start(Module, ExtraApps, []).
 
 start(Module, ExtraApps, Options) ->
-    Apps = start_applications([config, ioq, couch_epi | ExtraApps]),
+    Apps = start_applications([config, couch_log, ioq, couch_epi | ExtraApps]),
     ToMock = [config, couch_stats] -- proplists:get_value(dont_mock, Options, []),
     mock(ToMock),
     #test_context{module = Module, mocked = ToMock, started = Apps}.
diff --git a/test/couch_db_plugin_tests.erl b/test/couch_db_plugin_tests.erl
index 337207e..ea9b230 100644
--- a/test/couch_db_plugin_tests.erl
+++ b/test/couch_db_plugin_tests.erl
@@ -52,9 +52,10 @@
 teardown(Ctx) ->
     couch_tests:teardown(Ctx).
 
-validate_dbname({true, _Db}, _) -> true;
-validate_dbname({false, _Db}, _) -> false;
-validate_dbname({fail, _Db}, _) -> throw(validate_dbname).
+validate_dbname({true, _Db}, _) -> {decided, true};
+validate_dbname({false, _Db}, _) -> {decided, false};
+validate_dbname({fail, _Db}, _) -> throw(validate_dbname);
+validate_dbname({pass, _Db}, _) -> no_decision.
 
 before_doc_update({fail, _Doc}, _Db) -> throw(before_doc_update);
 before_doc_update({true, Doc}, Db) -> [{true, [before_doc_update|Doc]}, Db];
@@ -82,44 +83,52 @@
         {
             setup, fun setup/0, fun teardown/1,
             [
-                fun validate_dbname_match/0,
-                fun validate_dbname_no_match/0,
-                fun validate_dbname_throw/0,
+                {"validate_dbname_match", fun validate_dbname_match/0},
+                {"validate_dbname_no_match", fun validate_dbname_no_match/0},
+                {"validate_dbname_throw", fun validate_dbname_throw/0},
+                {"validate_dbname_pass", fun validate_dbname_pass/0},
 
-                fun before_doc_update_match/0,
-                fun before_doc_update_no_match/0,
-                fun before_doc_update_throw/0,
+                {"before_doc_update_match", fun before_doc_update_match/0},
+                {"before_doc_update_no_match", fun before_doc_update_no_match/0},
+                {"before_doc_update_throw", fun before_doc_update_throw/0},
 
-                fun after_doc_read_match/0,
-                fun after_doc_read_no_match/0,
-                fun after_doc_read_throw/0,
+                {"after_doc_read_match", fun after_doc_read_match/0},
+                {"after_doc_read_no_match", fun after_doc_read_no_match/0},
+                {"after_doc_read_throw", fun after_doc_read_throw/0},
 
-                fun validate_docid_match/0,
-                fun validate_docid_no_match/0,
-                fun validate_docid_throw/0,
+                {"validate_docid_match", fun validate_docid_match/0},
+                {"validate_docid_no_match", fun validate_docid_no_match/0},
+                {"validate_docid_throw", fun validate_docid_throw/0},
 
-                fun check_is_admin_match/0,
-                fun check_is_admin_no_match/0,
-                fun check_is_admin_throw/0,
+                {"check_is_admin_match", fun check_is_admin_match/0},
+                {"check_is_admin_no_match", fun check_is_admin_no_match/0},
+                {"check_is_admin_throw", fun check_is_admin_throw/0},
 
-                fun on_delete_match/0,
-                fun on_delete_no_match/0,
-                fun on_delete_throw/0
+                {"on_delete_match", fun on_delete_match/0},
+                {"on_delete_no_match", fun on_delete_no_match/0},
+                {"on_delete_throw", fun on_delete_throw/0}
             ]
         }
     }.
 
 
 validate_dbname_match() ->
-    ?assert(couch_db_plugin:validate_dbname({true, [db]}, db)).
+    ?assert(couch_db_plugin:validate_dbname(
+        {true, [db]}, db, fun(_, _) -> pass end)).
 
 validate_dbname_no_match() ->
-    ?assertNot(couch_db_plugin:validate_dbname({false, [db]}, db)).
+    ?assertNot(couch_db_plugin:validate_dbname(
+        {false, [db]}, db, fun(_, _) -> pass end)).
 
 validate_dbname_throw() ->
     ?assertThrow(
         validate_dbname,
-        couch_db_plugin:validate_dbname({fail, [db]}, db)).
+        couch_db_plugin:validate_dbname(
+            {fail, [db]}, db, fun(_, _) -> pass end)).
+
+validate_dbname_pass() ->
+    ?assertEqual(pass, couch_db_plugin:validate_dbname(
+        {pass, [db]}, db, fun(_, _) -> pass end)).
 
 before_doc_update_match() ->
     ?assertMatch(
diff --git a/test/couch_doc_tests.erl b/test/couch_doc_tests.erl
index 2025c25..fce4ff7 100644
--- a/test/couch_doc_tests.erl
+++ b/test/couch_doc_tests.erl
@@ -72,6 +72,34 @@
         couch_doc:len_doc_to_multi_part_stream(Boundary, JsonBytes, Atts, true),
     ok.
 
+validate_docid_test_() ->
+    {setup,
+        fun() ->
+            ok = meck:new(couch_db_plugin, [passthrough]),
+            meck:expect(couch_db_plugin, validate_docid, fun(_) -> false end)
+        end,
+        fun(_) ->
+            meck:unload(couch_db_plugin)
+        end,
+        [
+            ?_assertEqual(ok, couch_doc:validate_docid(<<"idx">>)),
+            ?_assertEqual(ok, couch_doc:validate_docid(<<"_design/idx">>)),
+            ?_assertEqual(ok, couch_doc:validate_docid(<<"_local/idx">>)),
+            ?_assertThrow({illegal_docid, _},
+                couch_doc:validate_docid(<<>>)),
+            ?_assertThrow({illegal_docid, _},
+                couch_doc:validate_docid(<<16#80>>)),
+            ?_assertThrow({illegal_docid, _},
+                couch_doc:validate_docid(<<"_idx">>)),
+            ?_assertThrow({illegal_docid, _},
+                couch_doc:validate_docid(<<"_">>)),
+            ?_assertThrow({illegal_docid, _},
+                couch_doc:validate_docid(<<"_design/">>)),
+            ?_assertThrow({illegal_docid, _},
+                couch_doc:validate_docid(<<"_local/">>))
+        ]
+    }.
+
 request(start) ->
     {ok, Doc} = file:read_file(?REQUEST_FIXTURE),
     {Doc, fun() -> request(stop) end};
diff --git a/test/couchdb_auth_tests.erl b/test/couchdb_auth_tests.erl
index 9fb4ceb..0d3cb79 100644
--- a/test/couchdb_auth_tests.erl
+++ b/test/couchdb_auth_tests.erl
@@ -27,7 +27,7 @@
 auth_test_() ->
     Tests = [
         fun should_return_username_on_post_to_session/2,
-        fun should_return_authenticated_field/2,
+        fun should_not_return_authenticated_field/2,
         fun should_return_list_of_handlers/2
     ],
     {
@@ -58,21 +58,21 @@
             proplists:get_value(<<"name">>, Json)
         end).
 
-should_return_authenticated_field(_PortType, Url) ->
-    ?_assertEqual(<<"local">>,
+should_not_return_authenticated_field(_PortType, Url) ->
+    ?_assertThrow({not_found, _},
         begin
             couch_util:get_nested_json_value(session(Url), [
                 <<"info">>, <<"authenticated">>])
         end).
 
 should_return_list_of_handlers(backdoor, Url) ->
-    ?_assertEqual([<<"oauth">>,<<"cookie">>,<<"default">>, <<"local">>],
+    ?_assertEqual([<<"oauth">>,<<"cookie">>,<<"default">>],
         begin
             couch_util:get_nested_json_value(session(Url), [
                 <<"info">>, <<"authentication_handlers">>])
         end);
 should_return_list_of_handlers(clustered, Url) ->
-    ?_assertEqual([<<"cookie">>,<<"default">>,<<"local">>],
+    ?_assertEqual([<<"cookie">>,<<"default">>],
         begin
             couch_util:get_nested_json_value(session(Url), [
                 <<"info">>, <<"authentication_handlers">>])
diff --git a/test/couchdb_mrview_tests.erl b/test/couchdb_mrview_tests.erl
new file mode 100644
index 0000000..fa9ba70
--- /dev/null
+++ b/test/couchdb_mrview_tests.erl
@@ -0,0 +1,165 @@
+% 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(couchdb_mrview_tests).
+
+-include_lib("couch/include/couch_eunit.hrl").
+-include_lib("couch/include/couch_db.hrl").
+
+
+
+-define(DDOC, {[
+    {<<"_id">>, <<"_design/foo">>},
+    {<<"shows">>, {[
+        {<<"bar">>, <<"function(doc, req) {return '<h1>wosh</h1>';}">>}
+    ]}},
+    {<<"updates">>, {[
+        {<<"report">>, <<"function(doc, req) {"
+            "var data = JSON.parse(req.body); "
+            "return ['test', data];"
+        "}">>}
+    ]}}
+]}).
+
+-define(USER, "admin").
+-define(PASS, "pass").
+-define(AUTH, {basic_auth, {?USER, ?PASS}}).
+
+
+start() ->
+    Ctx = test_util:start_couch([chttpd]),
+    ok = config:set("admins", ?USER, ?PASS, _Persist=false),
+    Ctx.
+
+setup(PortType) ->
+    ok = meck:new(mochiweb_socket, [passthrough]),
+    ok = meck:expect(mochiweb_socket, recv, fun mochiweb_socket_recv/3),
+
+    DbName = ?tempdb(),
+    ok = create_db(PortType, DbName),
+
+    Host = host_url(PortType),
+    upload_ddoc(Host, ?b2l(DbName)),
+    {Host, ?b2l(DbName)}.
+
+teardown(Ctx) ->
+    ok = config:delete("admins", ?USER, _Persist=false),
+    test_util:stop_couch(Ctx).
+
+teardown(PortType, {_Host, DbName}) ->
+    (catch meck:unload(mochiweb_socket)),
+    delete_db(PortType, ?l2b(DbName)),
+    ok.
+
+mrview_update_test_() ->
+    {
+        "Check show functionality",
+        {
+            setup,
+            fun start/0, fun teardown/1,
+            [
+                make_test_case(clustered, [fun should_return_invalid_request_body/2]),
+                make_test_case(backdoor, [fun should_return_invalid_request_body/2])
+            ]
+        }
+    }.
+
+make_test_case(Mod, Funs) ->
+    {
+        lists:flatten(io_lib:format("~s", [Mod])),
+        {foreachx, fun setup/1, fun teardown/2, [{Mod, Fun} || Fun <- Funs]}
+    }.
+
+should_return_invalid_request_body(PortType, {Host, DbName}) ->
+    ?_test(begin
+         ok = create_doc(PortType, ?l2b(DbName), <<"doc_id">>, {[]}),
+         ReqUrl = Host ++ "/" ++ DbName ++ "/_design/foo/_update/report/doc_id",
+         {ok, Status, _Headers, Body} =
+              test_request:post(ReqUrl, [?AUTH], <<"{truncated}">>),
+         {Props} = jiffy:decode(Body),
+         ?assertEqual(
+            <<"bad_request">>, couch_util:get_value(<<"error">>, Props)),
+         ?assertEqual(
+            <<"Invalid request body">>, couch_util:get_value(<<"reason">>, Props)),
+         ?assertEqual(400, Status),
+         ok
+    end).
+
+create_doc(backdoor, DbName, Id, Body) ->
+    JsonDoc = couch_util:json_apply_field({<<"_id">>, Id}, Body),
+    Doc = couch_doc:from_json_obj(JsonDoc),
+    {ok, Db} = couch_db:open(DbName, [?ADMIN_CTX]),
+    {ok, _} = couch_db:update_docs(Db, [Doc]),
+    couch_db:ensure_full_commit(Db),
+    couch_db:close(Db);
+create_doc(clustered, DbName, Id, Body) ->
+    JsonDoc = couch_util:json_apply_field({<<"_id">>, Id}, Body),
+    Doc = couch_doc:from_json_obj(JsonDoc),
+    {ok, _} = fabric:update_docs(DbName, [Doc], [?ADMIN_CTX]),
+    ok.
+
+create_db(backdoor, DbName) ->
+    {ok, Db} = couch_db:create(DbName, [?ADMIN_CTX]),
+    couch_db:close(Db);
+create_db(clustered, DbName) ->
+    {ok, Status, _, _} = test_request:put(db_url(DbName), [?AUTH], ""),
+    assert_success(create_db, Status),
+    ok.
+
+delete_db(backdoor, DbName) ->
+    couch_server:delete(DbName, [?ADMIN_CTX]);
+delete_db(clustered, DbName) ->
+    {ok, Status, _, _} = test_request:delete(db_url(DbName), [?AUTH]),
+    assert_success(delete_db, Status),
+    ok.
+
+assert_success(create_db, Status) ->
+    ?assert(lists:member(Status, [201, 202]));
+assert_success(delete_db, Status) ->
+    ?assert(lists:member(Status, [200, 202])).
+
+
+host_url(PortType) ->
+    "http://" ++ bind_address(PortType) ++ ":" ++ port(PortType).
+
+bind_address(PortType) ->
+    config:get(section(PortType), "bind_address", "127.0.0.1").
+
+section(backdoor) -> "http";
+section(clustered) -> "chttpd".
+
+db_url(DbName) when is_binary(DbName) ->
+    db_url(binary_to_list(DbName));
+db_url(DbName) when is_list(DbName) ->
+    host_url(clustered) ++ "/" ++ DbName.
+
+port(clustered) ->
+    integer_to_list(mochiweb_socket_server:get(chttpd, port));
+port(backdoor) ->
+    integer_to_list(mochiweb_socket_server:get(couch_httpd, port)).
+
+
+upload_ddoc(Host, DbName) ->
+    Url = Host ++ "/" ++ DbName ++ "/_design/foo",
+    Body = couch_util:json_encode(?DDOC),
+    {ok, 201, _Resp, _Body} = test_request:put(Url, [?AUTH], Body),
+    ok.
+
+mochiweb_socket_recv(Sock, Len, Timeout) ->
+    case meck:passthrough([Sock, Len, Timeout]) of
+        {ok, <<"{truncated}">>} ->
+            {error, closed};
+        {ok, Data} ->
+            {ok, Data};
+        Else ->
+            Else
+    end.
diff --git a/test/couchdb_os_proc_pool.erl b/test/couchdb_os_proc_pool.erl
index a698070..f14af68 100644
--- a/test/couchdb_os_proc_pool.erl
+++ b/test/couchdb_os_proc_pool.erl
@@ -15,26 +15,33 @@
 -include_lib("couch/include/couch_eunit.hrl").
 -include_lib("couch/include/couch_db.hrl").
 
--define(TIMEOUT, 3000).
+-define(TIMEOUT, 1000).
 
 
-start() ->
-    Ctx = test_util:start_couch(),
-    config:set("query_server_config", "os_process_limit", "3", false),
-    timer:sleep(100), %% we need to wait to let gen_server:cast finish
-    Ctx.
+setup() ->
+    ok = couch_proc_manager:reload(),
+    ok = setup_config().
 
+teardown(_) ->
+    ok.
 
 os_proc_pool_test_() ->
     {
         "OS processes pool tests",
         {
             setup,
-            fun start/0, fun test_util:stop_couch/1,
-            [
-                should_block_new_proc_on_full_pool(),
-                should_free_slot_on_proc_unexpected_exit()
-            ]
+            fun test_util:start_couch/0, fun test_util:stop_couch/1,
+            {
+                foreach,
+                fun setup/0, fun teardown/1,
+                [
+                    should_block_new_proc_on_full_pool(),
+                    should_free_slot_on_proc_unexpected_exit(),
+                    should_reuse_known_proc(),
+%                    should_process_waiting_queue_as_fifo(),
+                    should_reduce_pool_on_idle_os_procs()
+                ]
+            }
         }
     }.
 
@@ -115,6 +122,113 @@
     end).
 
 
+should_reuse_known_proc() ->
+    ?_test(begin
+        Client1 = spawn_client(<<"ddoc1">>),
+        Client2 = spawn_client(<<"ddoc2">>),
+
+        ?assertEqual(ok, ping_client(Client1)),
+        ?assertEqual(ok, ping_client(Client2)),
+
+        Proc1 = get_client_proc(Client1, "1"),
+        Proc2 = get_client_proc(Client2, "2"),
+        ?assertNotEqual(Proc1#proc.pid, Proc2#proc.pid),
+
+        ?assertEqual(ok, stop_client(Client1)),
+        ?assertEqual(ok, stop_client(Client2)),
+        ?assert(is_process_alive(Proc1#proc.pid)),
+        ?assert(is_process_alive(Proc2#proc.pid)),
+
+        Client1Again = spawn_client(<<"ddoc1">>),
+        ?assertEqual(ok, ping_client(Client1Again)),
+        Proc1Again = get_client_proc(Client1Again, "1-again"),
+        ?assertEqual(Proc1#proc.pid, Proc1Again#proc.pid),
+        ?assertNotEqual(Proc1#proc.client, Proc1Again#proc.client),
+        ?assertEqual(ok, stop_client(Client1Again))
+    end).
+
+
+%should_process_waiting_queue_as_fifo() ->
+%    ?_test(begin
+%        Client1 = spawn_client(<<"ddoc1">>),
+%        Client2 = spawn_client(<<"ddoc2">>),
+%        Client3 = spawn_client(<<"ddoc3">>),
+%        Client4 = spawn_client(<<"ddoc4">>),
+%        timer:sleep(100),
+%        Client5 = spawn_client(<<"ddoc5">>),
+%
+%        ?assertEqual(ok, ping_client(Client1)),
+%        ?assertEqual(ok, ping_client(Client2)),
+%        ?assertEqual(ok, ping_client(Client3)),
+%        ?assertEqual(timeout, ping_client(Client4)),
+%        ?assertEqual(timeout, ping_client(Client5)),
+%
+%        Proc1 = get_client_proc(Client1, "1"),
+%        ?assertEqual(ok, stop_client(Client1)),
+%        ?assertEqual(ok, ping_client(Client4)),
+%        Proc4 = get_client_proc(Client4, "4"),
+%
+%        ?assertNotEqual(Proc4#proc.client, Proc1#proc.client),
+%        ?assertEqual(Proc1#proc.pid, Proc4#proc.pid),
+%        ?assertEqual(timeout, ping_client(Client5)),
+%
+%        ?assertEqual(ok, stop_client(Client2)),
+%        ?assertEqual(ok, stop_client(Client3)),
+%        ?assertEqual(ok, stop_client(Client4)),
+%        ?assertEqual(ok, stop_client(Client5))
+%    end).
+
+
+should_reduce_pool_on_idle_os_procs() ->
+    ?_test(begin
+        %% os_process_idle_limit is in sec
+        config:set("query_server_config",
+            "os_process_idle_limit", "1", false),
+        ok = confirm_config("os_process_idle_limit", "1"),
+
+        Client1 = spawn_client(<<"ddoc1">>),
+        Client2 = spawn_client(<<"ddoc2">>),
+        Client3 = spawn_client(<<"ddoc3">>),
+
+        ?assertEqual(ok, ping_client(Client1)),
+        ?assertEqual(ok, ping_client(Client2)),
+        ?assertEqual(ok, ping_client(Client3)),
+
+        ?assertEqual(3, couch_proc_manager:get_proc_count()),
+
+        ?assertEqual(ok, stop_client(Client1)),
+        ?assertEqual(ok, stop_client(Client2)),
+        ?assertEqual(ok, stop_client(Client3)),
+
+        timer:sleep(1200),
+        ?assertEqual(1, couch_proc_manager:get_proc_count())
+    end).
+
+
+setup_config() ->
+    config:set("query_server_config", "os_process_limit", "3", false),
+    config:set("query_server_config", "os_process_soft_limit", "2", false),
+    ok = confirm_config("os_process_soft_limit", "2").
+
+confirm_config(Key, Value) ->
+    confirm_config(Key, Value, 0).
+
+confirm_config(Key, Value, Count) ->
+    case config:get("query_server_config", Key) of
+        Value ->
+            ok;
+        _ when Count > 10 ->
+            erlang:error({config_setup, [
+                {module, ?MODULE},
+                {line, ?LINE},
+                {value, timeout}
+            ]});
+        _ ->
+            %% we need to wait to let gen_server:cast finish
+            timer:sleep(10),
+            confirm_config(Key, Value, Count + 1)
+    end.
+
 spawn_client() ->
     Parent = self(),
     Ref = make_ref(),
@@ -124,6 +238,17 @@
     end),
     {Pid, Ref}.
 
+spawn_client(DDocId) ->
+    Parent = self(),
+    Ref = make_ref(),
+    Pid = spawn(fun() ->
+        DDocKey = {DDocId, <<"1-abcdefgh">>},
+        DDoc = #doc{body={[]}},
+        Proc = couch_query_servers:get_ddoc_process(DDoc, DDocKey),
+        loop(Parent, Ref, Proc)
+    end),
+    {Pid, Ref}.
+
 ping_client({Pid, Ref}) ->
     Pid ! ping,
     receive