Merge remote-tracking branch 'banjiewen/stale-stable-update'
diff --git a/include/couch_mrview.hrl b/include/couch_mrview.hrl
index e74d6ec..a341e30 100644
--- a/include/couch_mrview.hrl
+++ b/include/couch_mrview.hrl
@@ -100,7 +100,8 @@
     buffer = [],
     bufsize = 0,
     threshold = 1490,
-    row_sent = false
+    row_sent = false,
+    meta_sent = false
 }).
 
 -record(lacc, {
diff --git a/src/couch_mrview.erl b/src/couch_mrview.erl
index 20eb4be..088327c 100644
--- a/src/couch_mrview.erl
+++ b/src/couch_mrview.erl
@@ -14,7 +14,7 @@
 
 -export([validate/2]).
 -export([query_all_docs/2, query_all_docs/4]).
--export([query_view/3, query_view/4, query_view/6]).
+-export([query_view/3, query_view/4, query_view/6, get_view_index_pid/4]).
 -export([view_changes_since/5]).
 -export([view_changes_since/6, view_changes_since/7]).
 -export([count_view_changes_since/4, count_view_changes_since/5]).
@@ -249,6 +249,10 @@
     query_view(Db, VInfo, Args, Callback, Acc1).
 
 
+get_view_index_pid(Db, DDoc, ViewName, Args0) ->
+    couch_mrview_util:get_view_index_pid(Db, DDoc, ViewName, Args0).
+
+
 query_view(Db, {Type, View, Ref}, Args, Callback, Acc) ->
     try
         case Type of
diff --git a/src/couch_mrview_http.erl b/src/couch_mrview_http.erl
index 5f52d08..7e3fd78 100644
--- a/src/couch_mrview_http.erl
+++ b/src/couch_mrview_http.erl
@@ -320,7 +320,6 @@
         _ -> {ok, Resp2}
     end.
 
-
 filtered_view_cb({row, Row0}, Acc) ->
   Row1 = lists:map(fun({doc, null}) ->
         {doc, null};
@@ -335,16 +334,44 @@
     view_cb(Obj, Acc).
 
 
-view_cb({meta, Meta}, #vacc{resp=undefined}=Acc) ->
-    % Map function starting
+%% these clauses start (and possibly end) the response
+view_cb({error, Reason}, #vacc{resp=undefined}=Acc) ->
+    {ok, Resp} = chttpd:send_error(Acc#vacc.req, Reason),
+    {ok, Acc#vacc{resp=Resp}};
+
+view_cb(complete, #vacc{resp=undefined}=Acc) ->
+    % Nothing in view
+    {ok, Resp} = chttpd:send_json(Acc#vacc.req, 200, {[{rows, []}]}),
+    {ok, Acc#vacc{resp=Resp}};
+
+view_cb(Msg, #vacc{resp=undefined}=Acc) ->
+    %% Start response
     Headers = [],
     {ok, Resp} = chttpd:start_delayed_json_response(Acc#vacc.req, 200, Headers),
-    view_cb({meta, Meta}, Acc#vacc{resp=Resp, should_close=true});
-view_cb({meta, _Meta}, #vacc{row_sent=true}=Acc) ->
-    % sorted=false and meta arrived late, ignore it.
-    {ok, Acc};
-view_cb({meta, Meta}, #vacc{}=Acc) ->
-    % Sending metadata
+    view_cb(Msg, Acc#vacc{resp=Resp, should_close=true});
+
+%% ---------------------------------------------------
+
+%% From here on down, the response has been started.
+
+view_cb({error, Reason}, #vacc{resp=Resp}=Acc) ->
+    {ok, Resp1} = chttpd:send_delayed_error(Resp, Reason),
+    {ok, Acc#vacc{resp=Resp1}};
+
+view_cb(complete, #vacc{resp=Resp, buffer=Buf, threshold=Max}=Acc) ->
+    % Finish view output and possibly end the response
+    {ok, Resp1} = chttpd:close_delayed_json_object(Resp, Buf, "\r\n]}", Max),
+    case Acc#vacc.should_close of
+        true ->
+            {ok, Resp2} = chttpd:end_delayed_json_response(Resp1),
+            {ok, Acc#vacc{resp=Resp2}};
+        _ ->
+            {ok, Acc#vacc{resp=Resp1, meta_sent=false, row_sent=false,
+                prepend=",\r\n", buffer=[], bufsize=0}}
+    end;
+
+view_cb({meta, Meta}, #vacc{meta_sent=false, row_sent=false}=Acc) ->
+    % Sending metadata as we've not sent it or any row yet
     Parts = case couch_util:get_value(total, Meta) of
         undefined -> [];
         Total -> [io_lib:format("\"total_rows\":~p", [Total])]
@@ -360,36 +387,23 @@
     end ++ ["\"rows\":["],
     Chunk = [prepend_val(Acc), "{", string:join(Parts, ","), "\r\n"],
     {ok, AccOut} = maybe_flush_response(Acc, Chunk, iolist_size(Chunk)),
-    {ok, AccOut#vacc{prepend=""}};
-view_cb({row, Row}, #vacc{resp=undefined}=Acc) ->
-    % sorted=false and a row arrived before meta, start response.
-    Pre = "{\"rows\":[\r\n",
-    {ok, Resp} = chttpd:start_delayed_json_response(Acc#vacc.req, 200, []),
-    view_cb({row, Row}, Acc#vacc{row_sent=true,resp=Resp, prepend=Pre, should_close=true});
-view_cb({row, Row}, Acc) ->
+    {ok, AccOut#vacc{prepend="", meta_sent=true}};
+
+view_cb({meta, _Meta}, #vacc{}=Acc) ->
+    %% ignore metadata
+    {ok, Acc};
+
+view_cb({row, Row}, #vacc{meta_sent=false}=Acc) ->
+    %% sorted=false and row arrived before meta
+    % Adding another row
+    Chunk = [prepend_val(Acc), "{\"rows\":[\r\n", row_to_json(Row)],
+    maybe_flush_response(Acc#vacc{meta_sent=true, row_sent=true}, Chunk, iolist_size(Chunk));
+
+view_cb({row, Row}, #vacc{meta_sent=true}=Acc) ->
     % Adding another row
     Chunk = [prepend_val(Acc), row_to_json(Row)],
-    maybe_flush_response(Acc#vacc{row_sent=true}, Chunk, iolist_size(Chunk));
-view_cb(complete, #vacc{resp=undefined}=Acc) ->
-    % Nothing in view
-    {ok, Resp} = chttpd:send_json(Acc#vacc.req, 200, {[{rows, []}]}),
-    {ok, Acc#vacc{resp=Resp}};
-view_cb(complete, #vacc{resp=Resp, buffer=Buf, threshold=Max}=Acc) ->
-    % Finish view output and possibly end the response
-    {ok, Resp1} = chttpd:close_delayed_json_object(Resp, Buf, "\r\n]}", Max),
-    case Acc#vacc.should_close of
-        true ->
-            {ok, Resp2} = chttpd:end_delayed_json_response(Resp1),
-            {ok, Acc#vacc{resp=Resp2}};
-        _ ->
-            {ok, Acc#vacc{resp=Resp1, prepend=",\r\n", buffer=[], bufsize=0}}
-    end;
-view_cb({error, Reason}, #vacc{resp=undefined}=Acc) ->
-    {ok, Resp} = chttpd:send_error(Acc#vacc.req, Reason),
-    {ok, Acc#vacc{resp=Resp}};
-view_cb({error, Reason}, #vacc{resp=Resp}=Acc) ->
-    {ok, Resp1} = chttpd:send_delayed_error(Resp, Reason),
-    {ok, Acc#vacc{resp=Resp1}}.
+    maybe_flush_response(Acc#vacc{row_sent=true}, Chunk, iolist_size(Chunk)).
+
 
 maybe_flush_response(#vacc{bufsize=Size, threshold=Max} = Acc, Data, Len)
         when Size > 0 andalso (Size + Len) > Max ->
diff --git a/src/couch_mrview_show.erl b/src/couch_mrview_show.erl
index f7e0f56..52e07a7 100644
--- a/src/couch_mrview_show.erl
+++ b/src/couch_mrview_show.erl
@@ -317,13 +317,15 @@
     Acc.
 
 
+apply_etag(JsonResp, undefined) ->
+    JsonResp;
 apply_etag({ExternalResponse}, CurrentEtag) ->
     % Here we embark on the delicate task of replacing or creating the
     % headers on the JsonResponse object. We need to control the Etag and
     % Vary headers. If the external function controls the Etag, we'd have to
     % run it to check for a match, which sort of defeats the purpose.
     apply_headers(ExternalResponse, [
-        {<<"Etag">>, CurrentEtag},
+        {<<"ETag">>, CurrentEtag},
         {<<"Vary">>, <<"Accept">>}
     ]).
 
diff --git a/src/couch_mrview_util.erl b/src/couch_mrview_util.erl
index 11bd0df..3830b96 100644
--- a/src/couch_mrview_util.erl
+++ b/src/couch_mrview_util.erl
@@ -12,7 +12,7 @@
 
 -module(couch_mrview_util).
 
--export([get_view/4]).
+-export([get_view/4, get_view_index_pid/4]).
 -export([ddoc_to_mrst/2, init_state/4, reset_index/3]).
 -export([make_header/1]).
 -export([index_file/2, compaction_file/2, open_file/1]).
@@ -40,11 +40,7 @@
 
 
 get_view(Db, DDoc, ViewName, Args0) ->
-    ArgCheck = fun(InitState) ->
-        Args1 = set_view_type(Args0, ViewName, InitState#mrst.views),
-        {ok, validate_args(Args1)}
-    end,
-    {ok, Pid, Args2} = couch_index_server:get_index(?MOD, Db, DDoc, ArgCheck),
+    {ok, Pid, Args2} = get_view_index_pid(Db, DDoc, ViewName, Args0),
     DbUpdateSeq = couch_util:with_db(Db, fun(WDb) ->
         couch_db:get_update_seq(WDb)
     end),
@@ -67,6 +63,14 @@
     {ok, {Type, View, Ref}, Sig, Args3}.
 
 
+get_view_index_pid(Db, DDoc, ViewName, Args0) ->
+    ArgCheck = fun(InitState) ->
+        Args1 = set_view_type(Args0, ViewName, InitState#mrst.views),
+        {ok, validate_args(Args1)}
+    end,
+    couch_index_server:get_index(?MOD, Db, DDoc, ArgCheck).
+
+
 ddoc_to_mrst(DbName, #doc{id=Id, body={Fields}}) ->
     MakeDict = fun({Name, {MRFuns}}, DictBySrcAcc) ->
         case couch_util:get_value(<<"map">>, MRFuns) of
diff --git a/test/couch_mrview_modules_load_tests.erl b/test/couch_mrview_modules_load_tests.erl
deleted file mode 100644
index 15ce443..0000000
--- a/test/couch_mrview_modules_load_tests.erl
+++ /dev/null
@@ -1,37 +0,0 @@
-% 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_mrview_modules_load_tests).
-
--include_lib("couch/include/couch_eunit.hrl").
-
-
-modules_load_test_() ->
-    {
-        "Verify that all modules loads",
-        should_load_modules()
-    }.
-
-
-should_load_modules() ->
-    Modules = [
-        couch_mrview,
-        couch_mrview_compactor,
-        couch_mrview_http,
-        couch_mrview_index,
-        couch_mrview_updater,
-        couch_mrview_util
-    ],
-    [should_load_module(Mod) || Mod <- Modules].
-
-should_load_module(Mod) ->
-    {atom_to_list(Mod), ?_assertMatch({module, _}, code:load_file(Mod))}.