You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by fd...@apache.org on 2012/01/23 00:43:12 UTC

[5/50] git commit: Merge remote-tracking branch 'asf-write/master' into COUCHDB-1342

Merge remote-tracking branch 'asf-write/master' into COUCHDB-1342

Conflicts:
	src/couchdb/couch_db.erl


Project: http://git-wip-us.apache.org/repos/asf/couchdb/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb/commit/62ce6101
Tree: http://git-wip-us.apache.org/repos/asf/couchdb/tree/62ce6101
Diff: http://git-wip-us.apache.org/repos/asf/couchdb/diff/62ce6101

Branch: refs/heads/COUCHDB-1342
Commit: 62ce610150ac84d79db0bbdfa6db691420439d17
Parents: 6e38856 6dba2e9
Author: Filipe David Borba Manana <fd...@apache.org>
Authored: Sun Jan 22 13:31:09 2012 +0000
Committer: Filipe David Borba Manana <fd...@apache.org>
Committed: Sun Jan 22 13:31:09 2012 +0000

----------------------------------------------------------------------
 .gitignore                                         |    4 +
 AUTHORS                                            |    2 +
 CHANGES                                            |   22 +
 DEVELOPERS                                         |    4 +
 INSTALL.Unix                                       |   10 +-
 INSTALL.Windows                                    |    2 +-
 Makefile.am                                        |   10 +-
 NEWS                                               |   11 +-
 NOTICE                                             |    2 +-
 THANKS                                             |    2 +-
 bin/Makefile.am                                    |    2 +
 bootstrap                                          |    2 +-
 configure.ac                                       |   81 +-
 etc/couchdb/default.ini.tpl.in                     |   38 +-
 etc/couchdb/local.ini                              |    5 +-
 license.skip                                       |    3 +
 share/Makefile.am                                  |    3 +
 share/www/script/couch.js                          |   28 +-
 share/www/script/couch_test_runner.js              |   34 +-
 share/www/script/couch_tests.js                    |    3 +
 share/www/script/futon.js                          |    9 +-
 share/www/script/jquery.couch.js                   |   32 +-
 share/www/script/test/attachment_ranges.js         |   29 +-
 share/www/script/test/bulk_docs.js                 |   13 +
 share/www/script/test/config.js                    |    4 +-
 share/www/script/test/cookie_auth.js               |  122 ++-
 share/www/script/test/oauth.js                     |    5 +-
 share/www/script/test/oauth_users_db.js            |  161 +++
 share/www/script/test/proxyauth.js                 |    1 -
 share/www/script/test/reader_acl.js                |    1 -
 share/www/script/test/replicator_db.js             |  104 ++-
 share/www/script/test/replicator_db_security.js    |  395 ++++++
 share/www/script/test/users_db_security.js         |  234 ++++
 src/Makefile.am                                    |    1 +
 src/couch_index/src/couch_index_server.erl         |    9 +-
 src/couch_mrview/src/couch_mrview_http.erl         |   15 +
 src/couch_replicator/Makefile.am                   |   75 ++
 src/couch_replicator/src/couch_replicator.app.src  |   33 +
 src/couch_replicator/src/couch_replicator.erl      |  952 +++++++++++++++
 src/couch_replicator/src/couch_replicator.hrl      |   30 +
 .../src/couch_replicator_api_wrap.erl              |  778 ++++++++++++
 .../src/couch_replicator_api_wrap.hrl              |   36 +
 .../src/couch_replicator_httpc.erl                 |  286 +++++
 .../src/couch_replicator_httpc_pool.erl            |  138 +++
 .../src/couch_replicator_httpd.erl                 |   66 +
 .../src/couch_replicator_job_sup.erl               |   31 +
 .../src/couch_replicator_js_functions.hrl          |  151 +++
 .../src/couch_replicator_manager.erl               |  694 +++++++++++
 .../src/couch_replicator_notifier.erl              |   57 +
 .../src/couch_replicator_utils.erl                 |  382 ++++++
 .../src/couch_replicator_worker.erl                |  515 ++++++++
 src/couch_replicator/test/01-load.t                |   37 +
 src/couch_replicator/test/02-httpc-pool.t          |  250 ++++
 src/couch_replicator/test/03-replication-compact.t |  487 ++++++++
 .../test/04-replication-large-atts.t               |  267 ++++
 .../test/05-replication-many-leaves.t              |  292 +++++
 src/couch_replicator/test/06-doc-missing-stubs.t   |  304 +++++
 src/couchdb/Makefile.am                            |   28 +-
 src/couchdb/couch.app.tpl.in                       |    1 -
 src/couchdb/couch_api_wrap.erl                     |  775 ------------
 src/couchdb/couch_api_wrap.hrl                     |   36 -
 src/couchdb/couch_api_wrap_httpc.erl               |  286 -----
 src/couchdb/couch_auth_cache.erl                   |   47 +-
 src/couchdb/couch_changes.erl                      |  166 ++-
 src/couchdb/couch_compaction_daemon.erl            |    2 +-
 src/couchdb/couch_db.erl                           |  145 ++-
 src/couchdb/couch_db.hrl                           |   30 +-
 src/couchdb/couch_db_updater.erl                   |   60 +-
 src/couchdb/couch_doc.erl                          |    5 +
 src/couchdb/couch_httpc_pool.erl                   |  138 ---
 src/couchdb/couch_httpd.erl                        |   48 +-
 src/couchdb/couch_httpd_auth.erl                   |   12 +-
 src/couchdb/couch_httpd_db.erl                     |   45 +-
 src/couchdb/couch_httpd_oauth.erl                  |  382 +++++--
 src/couchdb/couch_httpd_replicator.erl             |   66 -
 src/couchdb/couch_httpd_rewrite.erl                |    4 +-
 src/couchdb/couch_js_functions.hrl                 |  176 +--
 src/couchdb/couch_log.erl                          |   56 +-
 src/couchdb/couch_primary_sup.erl                  |    6 +-
 src/couchdb/couch_query_servers.erl                |    2 +
 src/couchdb/couch_rep_sup.erl                      |   31 -
 src/couchdb/couch_replication_manager.erl          |  625 ----------
 src/couchdb/couch_replication_notifier.erl         |   57 -
 src/couchdb/couch_replicator.erl                   |  942 --------------
 src/couchdb/couch_replicator.hrl                   |   30 -
 src/couchdb/couch_replicator_utils.erl             |  382 ------
 src/couchdb/couch_replicator_worker.erl            |  515 --------
 src/couchdb/couch_server.erl                       |   29 +-
 src/couchdb/couch_users_db.erl                     |  103 ++
 src/couchdb/couch_util.erl                         |    6 +-
 src/couchdb/priv/Makefile.am                       |   14 +-
 .../priv/couch_ejson_compare/couch_ejson_compare.c |   21 +-
 src/couchdb/priv/couch_js/util.c                   |   32 +-
 src/couchdb/priv/couch_js/util.h                   |    1 +
 src/erlang-oauth/oauth_uri.erl                     |    2 +
 test/etap/001-load.t                               |    9 -
 test/etap/073-changes.t                            |   96 ++-
 test/etap/074-doc-update-conflicts.t               |  218 ++++
 test/etap/075-auth-cache.t                         |  274 +++++
 test/etap/160-vhosts.t                             |   89 ++-
 test/etap/172-os-daemon-errors.t                   |    1 -
 test/etap/200-view-group-no-db-leaks.t             |    4 +-
 test/etap/201-view-group-shutdown.t                |    4 +-
 test/etap/230-httpc-pool.t                         |  250 ----
 test/etap/240-replication-compact.t                |  485 --------
 test/etap/241-replication-large-atts.t             |  267 ----
 test/etap/242-replication-many-leaves.t            |  216 ----
 test/etap/Makefile.am                              |    8 +-
 test/javascript/cli_runner.js                      |    1 +
 test/javascript/run.tpl                            |   25 +-
 110 files changed, 8656 insertions(+), 5861 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb/blob/62ce6101/src/couch_replicator/test/03-replication-compact.t
----------------------------------------------------------------------
diff --cc src/couch_replicator/test/03-replication-compact.t
index 0000000,7c4d38c..e3f59fc
mode 000000,100755..100755
--- a/src/couch_replicator/test/03-replication-compact.t
+++ b/src/couch_replicator/test/03-replication-compact.t
@@@ -1,0 -1,488 +1,487 @@@
+ #!/usr/bin/env escript
+ %% -*- erlang -*-
+ % 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.
+ 
+ % Verify that compacting databases that are being used as the source or
+ % target of a replication doesn't affect the replication and that the
+ % replication doesn't hold their reference counters forever.
+ 
+ -define(b2l(B), binary_to_list(B)).
+ 
+ -record(user_ctx, {
+     name = null,
+     roles = [],
+     handler
+ }).
+ 
+ -record(db, {
+     main_pid = nil,
+     update_pid = nil,
+     compactor_pid = nil,
+     instance_start_time, % number of microsecs since jan 1 1970 as a binary string
+     fd,
 -    updater_fd,
+     fd_ref_counter,
+     header = nil,
+     committed_update_seq,
+     fulldocinfo_by_id_btree,
+     docinfo_by_seq_btree,
+     local_docs_btree,
+     update_seq,
+     name,
+     filepath,
+     validate_doc_funs = [],
+     security = [],
+     security_ptr = nil,
+     user_ctx = #user_ctx{},
+     waiting_delayed_commit = nil,
+     revs_limit = 1000,
+     fsync_options = [],
+     options = [],
+     compression,
+     before_doc_update,
+     after_doc_read
+ }).
+ 
+ -record(rep, {
+     id,
+     source,
+     target,
+     options,
+     user_ctx,
+     doc_id
+ }).
+ 
+ 
+ source_db_name() -> <<"couch_test_rep_db_a">>.
+ target_db_name() -> <<"couch_test_rep_db_b">>.
+ 
+ 
+ main(_) ->
+     test_util:init_code_path(),
+ 
+     etap:plan(376),
+     case (catch test()) of
+         ok ->
+             etap:end_tests();
+         Other ->
+             etap:diag(io_lib:format("Test died abnormally: ~p", [Other])),
+             etap:bail(Other)
+     end,
+     ok.
+ 
+ 
+ test() ->
+     couch_server_sup:start_link(test_util:config_files()),
+     ibrowse:start(),
+ 
+     Pairs = [
+         {source_db_name(), target_db_name()},
+         {{remote, source_db_name()}, target_db_name()},
+         {source_db_name(), {remote, target_db_name()}},
+         {{remote, source_db_name()}, {remote, (target_db_name())}}
+     ],
+ 
+     lists:foreach(
+         fun({Source, Target}) ->
+             {ok, SourceDb} = create_db(source_db_name()),
+             etap:is(couch_db:is_idle(SourceDb), true,
+                 "Source database is idle before starting replication"),
+ 
+             {ok, TargetDb} = create_db(target_db_name()),
+             etap:is(couch_db:is_idle(TargetDb), true,
+                 "Target database is idle before starting replication"),
+ 
+             {ok, RepPid, RepId} = replicate(Source, Target),
+             check_active_tasks(RepPid, RepId, Source, Target),
+             {ok, DocsWritten} = populate_and_compact_test(
+                 RepPid, SourceDb, TargetDb),
+ 
+             wait_target_in_sync(DocsWritten, TargetDb),
+             check_active_tasks(RepPid, RepId, Source, Target),
+             cancel_replication(RepId, RepPid),
+             compare_dbs(SourceDb, TargetDb),
+ 
+             delete_db(SourceDb),
+             delete_db(TargetDb),
+             couch_server_sup:stop(),
+             ok = timer:sleep(1000),
+             couch_server_sup:start_link(test_util:config_files())
+         end,
+         Pairs),
+ 
+     couch_server_sup:stop(),
+     ok.
+ 
+ 
+ populate_and_compact_test(RepPid, SourceDb0, TargetDb0) ->
+     etap:is(is_process_alive(RepPid), true, "Replication process is alive"),
+     check_db_alive("source", SourceDb0),
+     check_db_alive("target", TargetDb0),
+ 
+     Writer = spawn_writer(SourceDb0),
+ 
+     lists:foldl(
+         fun(_, {SourceDb, TargetDb, DocCount}) ->
+             pause_writer(Writer),
+ 
+             compact_db("source", SourceDb),
+             etap:is(is_process_alive(RepPid), true,
+                 "Replication process is alive after source database compaction"),
+             check_db_alive("source", SourceDb),
+             check_ref_counter("source", SourceDb),
+ 
+             compact_db("target", TargetDb),
+             etap:is(is_process_alive(RepPid), true,
+                 "Replication process is alive after target database compaction"),
+             check_db_alive("target", TargetDb),
+             check_ref_counter("target", TargetDb),
+ 
+             {ok, SourceDb2} = reopen_db(SourceDb),
+             {ok, TargetDb2} = reopen_db(TargetDb),
+ 
+             resume_writer(Writer),
+             wait_writer(Writer, DocCount),
+ 
+             compact_db("source", SourceDb2),
+             etap:is(is_process_alive(RepPid), true,
+                 "Replication process is alive after source database compaction"),
+             check_db_alive("source", SourceDb2),
+             pause_writer(Writer),
+             check_ref_counter("source", SourceDb2),
+             resume_writer(Writer),
+ 
+             compact_db("target", TargetDb2),
+             etap:is(is_process_alive(RepPid), true,
+                 "Replication process is alive after target database compaction"),
+             check_db_alive("target", TargetDb2),
+             pause_writer(Writer),
+             check_ref_counter("target", TargetDb2),
+             resume_writer(Writer),
+ 
+             {ok, SourceDb3} = reopen_db(SourceDb2),
+             {ok, TargetDb3} = reopen_db(TargetDb2),
+             {SourceDb3, TargetDb3, DocCount + 50}
+         end,
+         {SourceDb0, TargetDb0, 50}, lists:seq(1, 5)),
+ 
+     DocsWritten = stop_writer(Writer),
+     {ok, DocsWritten}.
+ 
+ 
+ check_db_alive(Type, #db{main_pid = Pid}) ->
+     etap:is(is_process_alive(Pid), true,
+         "Local " ++ Type ++ " database main pid is alive").
+ 
+ 
+ compact_db(Type, #db{name = Name}) ->
+     {ok, Db} = couch_db:open_int(Name, []),
+     {ok, CompactPid} = couch_db:start_compact(Db),
+     MonRef = erlang:monitor(process, CompactPid),
+     receive
+     {'DOWN', MonRef, process, CompactPid, normal} ->
+         ok;
+     {'DOWN', MonRef, process, CompactPid, Reason} ->
+         etap:bail("Error compacting " ++ Type ++ " database " ++ ?b2l(Name) ++
+             ": " ++ couch_util:to_list(Reason))
+     after 30000 ->
+         etap:bail("Compaction for " ++ Type ++ " database " ++ ?b2l(Name) ++
+             " didn't finish")
+     end,
+     ok = couch_db:close(Db).
+ 
+ 
+ check_ref_counter(Type, #db{name = Name, fd_ref_counter = OldRefCounter}) ->
+     MonRef = erlang:monitor(process, OldRefCounter),
+     receive
+     {'DOWN', MonRef, process, OldRefCounter, _} ->
+         etap:diag("Old " ++ Type ++ " database ref counter terminated")
+     after 30000 ->
+         etap:bail("Old " ++ Type ++ " database ref counter didn't terminate")
+     end,
+     {ok, #db{fd_ref_counter = NewRefCounter} = Db} = couch_db:open_int(Name, []),
+     ok = couch_db:close(Db),
+     etap:isnt(
+         NewRefCounter, OldRefCounter, Type ++ " database has new ref counter").
+ 
+ 
+ reopen_db(#db{name = Name}) ->
+     {ok, Db} = couch_db:open_int(Name, []),
+     ok = couch_db:close(Db),
+     {ok, Db}.
+ 
+ 
+ wait_target_in_sync(DocCount, #db{name = TargetName}) ->
+     wait_target_in_sync_loop(DocCount, TargetName, 300).
+ 
+ 
+ wait_target_in_sync_loop(_DocCount, _TargetName, 0) ->
+     etap:bail("Could not get source and target databases in sync");
+ wait_target_in_sync_loop(DocCount, TargetName, RetriesLeft) ->
+     {ok, Target} = couch_db:open_int(TargetName, []),
+     {ok, TargetInfo} = couch_db:get_db_info(Target),
+     ok = couch_db:close(Target),
+     TargetDocCount = couch_util:get_value(doc_count, TargetInfo),
+     case TargetDocCount == DocCount of
+     true ->
+         etap:diag("Source and target databases are in sync");
+     false ->
+         ok = timer:sleep(100),
+         wait_target_in_sync_loop(DocCount, TargetName, RetriesLeft - 1)
+     end.
+ 
+ 
+ compare_dbs(#db{name = SourceName}, #db{name = TargetName}) ->
+     {ok, SourceDb} = couch_db:open_int(SourceName, []),
+     {ok, TargetDb} = couch_db:open_int(TargetName, []),
+     Fun = fun(FullDocInfo, _, Acc) ->
+         {ok, Doc} = couch_db:open_doc(SourceDb, FullDocInfo),
+         {Props} = DocJson = couch_doc:to_json_obj(Doc, [attachments]),
+         DocId = couch_util:get_value(<<"_id">>, Props),
+         DocTarget = case couch_db:open_doc(TargetDb, DocId) of
+         {ok, DocT} ->
+             DocT;
+         Error ->
+             etap:bail("Error opening document '" ++ ?b2l(DocId) ++
+                 "' from target: " ++ couch_util:to_list(Error))
+         end,
+         DocTargetJson = couch_doc:to_json_obj(DocTarget, [attachments]),
+         case DocTargetJson of
+         DocJson ->
+             ok;
+         _ ->
+             etap:bail("Content from document '" ++ ?b2l(DocId) ++
+                 "' differs in target database")
+         end,
+         {ok, Acc}
+     end,
+     {ok, _, _} = couch_db:enum_docs(SourceDb, Fun, [], []),
+     etap:diag("Target database has the same documents as the source database"),
+     ok = couch_db:close(SourceDb),
+     ok = couch_db:close(TargetDb).
+ 
+ 
+ check_active_tasks(RepPid, {BaseId, Ext} = _RepId, Src, Tgt) ->
+     Source = case Src of
+     {remote, NameSrc} ->
+         <<(db_url(NameSrc))/binary, $/>>;
+     _ ->
+         Src
+     end,
+     Target = case Tgt of
+     {remote, NameTgt} ->
+         <<(db_url(NameTgt))/binary, $/>>;
+     _ ->
+         Tgt
+     end,
+     FullRepId = list_to_binary(BaseId ++ Ext),
+     Pid = list_to_binary(pid_to_list(RepPid)),
+     [RepTask] = couch_task_status:all(),
+     etap:is(couch_util:get_value(pid, RepTask), Pid,
+         "_active_tasks entry has correct pid property"),
+     etap:is(couch_util:get_value(replication_id, RepTask), FullRepId,
+         "_active_tasks entry has right replication id"),
+     etap:is(couch_util:get_value(continuous, RepTask), true,
+         "_active_tasks entry has continuous property set to true"),
+     etap:is(couch_util:get_value(source, RepTask), Source,
+         "_active_tasks entry has correct source property"),
+     etap:is(couch_util:get_value(target, RepTask), Target,
+         "_active_tasks entry has correct target property"),
+     etap:is(is_integer(couch_util:get_value(docs_read, RepTask)), true,
+         "_active_tasks entry has integer docs_read property"),
+     etap:is(is_integer(couch_util:get_value(docs_written, RepTask)), true,
+         "_active_tasks entry has integer docs_written property"),
+     etap:is(is_integer(couch_util:get_value(doc_write_failures, RepTask)), true,
+         "_active_tasks entry has integer doc_write_failures property"),
+     etap:is(is_integer(couch_util:get_value(revisions_checked, RepTask)), true,
+         "_active_tasks entry has integer revisions_checked property"),
+     etap:is(is_integer(couch_util:get_value(missing_revisions_found, RepTask)), true,
+         "_active_tasks entry has integer missing_revisions_found property"),
+     etap:is(is_integer(couch_util:get_value(checkpointed_source_seq, RepTask)), true,
+         "_active_tasks entry has integer checkpointed_source_seq property"),
+     etap:is(is_integer(couch_util:get_value(source_seq, RepTask)), true,
+         "_active_tasks entry has integer source_seq property"),
+     Progress = couch_util:get_value(progress, RepTask),
+     etap:is(is_integer(Progress), true,
+         "_active_tasks entry has an integer progress property"),
+     etap:is(Progress =< 100, true, "Progress is not greater than 100%").
+ 
+ 
+ wait_writer(Pid, NumDocs) ->
+     case get_writer_num_docs_written(Pid) of
+     N when N >= NumDocs ->
+         ok;
+     _ ->
+         wait_writer(Pid, NumDocs)
+     end.
+ 
+ 
+ spawn_writer(Db) ->
+     Parent = self(),
+     Pid = spawn(fun() -> writer_loop(Db, Parent, 0) end),
+     etap:diag("Started source database writer"),
+     Pid.
+ 
+ 
+ pause_writer(Pid) ->
+     Ref = make_ref(),
+     Pid ! {pause, Ref},
+     receive
+     {paused, Ref} ->
+         ok
+     after 30000 ->
+         etap:bail("Failed to pause source database writer")
+     end.
+ 
+ 
+ resume_writer(Pid) ->
+     Ref = make_ref(),
+     Pid ! {continue, Ref},
+     receive
+     {ok, Ref} ->
+         ok
+     after 30000 ->
+         etap:bail("Failed to unpause source database writer")
+     end.
+ 
+ 
+ get_writer_num_docs_written(Pid) ->
+     Ref = make_ref(),
+     Pid ! {get_count, Ref},
+     receive
+     {count, Ref, Count} ->
+         Count
+     after 30000 ->
+         etap:bail("Timeout getting number of documents written from "
+             "source database writer")
+     end.
+ 
+ 
+ stop_writer(Pid) ->
+     Ref = make_ref(),
+     Pid ! {stop, Ref},
+     receive
+     {stopped, Ref, DocsWritten} ->
+         MonRef = erlang:monitor(process, Pid),
+         receive
+         {'DOWN', MonRef, process, Pid, _Reason} ->
+             etap:diag("Stopped source database writer"),
+             DocsWritten
+         after 30000 ->
+             etap:bail("Timeout stopping source database writer")
+         end
+     after 30000 ->
+         etap:bail("Timeout stopping source database writer")
+     end.
+ 
+ 
+ writer_loop(#db{name = DbName}, Parent, Counter) ->
+     maybe_pause(Parent, Counter),
+     Doc = couch_doc:from_json_obj({[
+         {<<"_id">>, list_to_binary(integer_to_list(Counter + 1))},
+         {<<"value">>, Counter + 1},
+         {<<"_attachments">>, {[
+             {<<"icon1.png">>, {[
+                 {<<"data">>, base64:encode(att_data())},
+                 {<<"content_type">>, <<"image/png">>}
+             ]}},
+             {<<"icon2.png">>, {[
+                 {<<"data">>, base64:encode(iolist_to_binary(
+                     [att_data(), att_data()]))},
+                 {<<"content_type">>, <<"image/png">>}
+             ]}}
+         ]}}
+     ]}),
+     maybe_pause(Parent, Counter),
+     {ok, Db} = couch_db:open_int(DbName, []),
+     {ok, _} = couch_db:update_doc(Db, Doc, []),
+     ok = couch_db:close(Db),
+     receive
+     {get_count, Ref} ->
+         Parent ! {count, Ref, Counter + 1},
+         writer_loop(Db, Parent, Counter + 1);
+     {stop, Ref} ->
+         Parent ! {stopped, Ref, Counter + 1}
+     after 0 ->
+         ok = timer:sleep(500),
+         writer_loop(Db, Parent, Counter + 1)
+     end.
+ 
+ 
+ maybe_pause(Parent, Counter) ->
+     receive
+     {get_count, Ref} ->
+         Parent ! {count, Ref, Counter};
+     {pause, Ref} ->
+         Parent ! {paused, Ref},
+         receive {continue, Ref2} -> Parent ! {ok, Ref2} end
+     after 0 ->
+         ok
+     end.
+ 
+ 
+ db_url(DbName) ->
+     iolist_to_binary([
+         "http://", couch_config:get("httpd", "bind_address", "127.0.0.1"),
+         ":", integer_to_list(mochiweb_socket_server:get(couch_httpd, port)),
+         "/", DbName
+     ]).
+ 
+ 
+ create_db(DbName) ->
+     {ok, Db} = couch_db:create(
+         DbName,
+         [{user_ctx, #user_ctx{roles = [<<"_admin">>]}}, overwrite]),
+     couch_db:close(Db),
+     {ok, Db}.
+ 
+ 
+ delete_db(#db{name = DbName, main_pid = Pid}) ->
+     ok = couch_server:delete(
+         DbName, [{user_ctx, #user_ctx{roles = [<<"_admin">>]}}]),
+     MonRef = erlang:monitor(process, Pid),
+     receive
+     {'DOWN', MonRef, process, Pid, _Reason} ->
+         ok
+     after 30000 ->
+         etap:bail("Timeout deleting database")
+     end.
+ 
+ 
+ replicate({remote, Db}, Target) ->
+     replicate(db_url(Db), Target);
+ 
+ replicate(Source, {remote, Db}) ->
+     replicate(Source, db_url(Db));
+ 
+ replicate(Source, Target) ->
+     RepObject = {[
+         {<<"source">>, Source},
+         {<<"target">>, Target},
+         {<<"continuous">>, true}
+     ]},
+     {ok, Rep} = couch_replicator_utils:parse_rep_doc(
+         RepObject, #user_ctx{roles = [<<"_admin">>]}),
+     {ok, Pid} = couch_replicator:async_replicate(Rep),
+     {ok, Pid, Rep#rep.id}.
+ 
+ 
+ cancel_replication(RepId, RepPid) ->
+     {ok, _} = couch_replicator:cancel_replication(RepId),
+     etap:is(is_process_alive(RepPid), false,
+         "Replication process is no longer alive after cancel").
+ 
+ 
+ att_data() ->
+     {ok, Data} = file:read_file(
+         test_util:source_file("share/www/image/logo.png")),
+     Data.

http://git-wip-us.apache.org/repos/asf/couchdb/blob/62ce6101/src/couchdb/couch_db.erl
----------------------------------------------------------------------
diff --cc src/couchdb/couch_db.erl
index 9d1ca0f,ae21bfa..73d16b3
--- a/src/couchdb/couch_db.erl
+++ b/src/couchdb/couch_db.erl
@@@ -702,8 -704,8 +704,8 @@@ update_docs(Db, Docs, Options, replicat
          DocErrors = [],
          DocBuckets3 = DocBuckets
      end,
-     DocBuckets4 = [[doc_flush_atts(check_dup_atts(Doc), Db#db.fd)
-             || Doc <- Bucket] || Bucket <- DocBuckets3],
 -    DocBuckets4 = [[{doc_flush_atts(check_dup_atts(Doc), Db#db.updater_fd), Ref}
++    DocBuckets4 = [[{doc_flush_atts(check_dup_atts(Doc), Db#db.fd), Ref}
+             || {Doc, Ref} <- Bucket] || Bucket <- DocBuckets3],
      {ok, []} = write_and_commit(Db, DocBuckets4, [], [merge_conflicts | Options]),
      {ok, DocErrors};
  
@@@ -757,9 -766,9 +766,9 @@@ update_docs(Db, Docs, Options, interact
          Options2 = if AllOrNothing -> [merge_conflicts];
                  true -> [] end ++ Options,
          DocBuckets3 = [[
-                 doc_flush_atts(set_new_att_revpos(
-                         check_dup_atts(Doc)), Db#db.fd)
-                 || Doc <- B] || B <- DocBuckets2],
+                 {doc_flush_atts(set_new_att_revpos(
 -                        check_dup_atts(Doc)), Db#db.updater_fd), Ref}
++                        check_dup_atts(Doc)), Db#db.fd), Ref}
+                 || {Doc, Ref} <- B] || B <- DocBuckets2],
          {DocBuckets4, IdRevs} = new_revs(DocBuckets3, [], []),
  
          {ok, CommitResults} = write_and_commit(Db, DocBuckets4, NonRepDocs, Options2),
@@@ -832,7 -841,7 +841,7 @@@ write_and_commit(#db{update_pid=UpdateP
              % compaction. Retry by reopening the db and writing to the current file
              {ok, Db2} = open_ref_counted(Db#db.main_pid, self()),
              DocBuckets2 = [
-                 [doc_flush_atts(Doc, Db2#db.fd) || Doc <- Bucket] ||
 -                [{doc_flush_atts(Doc, Db2#db.updater_fd), Ref} || {Doc, Ref} <- Bucket] ||
++                [{doc_flush_atts(Doc, Db2#db.fd), Ref} || {Doc, Ref} <- Bucket] ||
                  Bucket <- DocBuckets1
              ],
              % We only retry once

http://git-wip-us.apache.org/repos/asf/couchdb/blob/62ce6101/src/couchdb/couch_db.hrl
----------------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/couchdb/blob/62ce6101/src/couchdb/couch_db_updater.erl
----------------------------------------------------------------------
diff --cc src/couchdb/couch_db_updater.erl
index a4d4202,54531db..357524d
--- a/src/couchdb/couch_db_updater.erl
+++ b/src/couchdb/couch_db_updater.erl
@@@ -477,9 -481,20 +477,11 @@@ init_db(DbName, Filepath, Fd, Header0, 
          revs_limit = Header#db_header.revs_limit,
          fsync_options = FsyncOptions,
          options = Options,
-         compression = Compression
+         compression = Compression,
+         before_doc_update = couch_util:get_value(before_doc_update, Options, nil),
+         after_doc_read = couch_util:get_value(after_doc_read, Options, nil)
          }.
  
 -open_reader_fd(Filepath, Options) ->
 -    {ok, Fd} = case lists:member(sys_db, Options) of
 -    true ->
 -        couch_file:open(Filepath, [read_only, sys_db]);
 -    false ->
 -        couch_file:open(Filepath, [read_only])
 -    end,
 -    unlink(Fd),
 -    Fd.
  
  close_db(#db{fd_ref_counter = RefCntr}) ->
      couch_ref_counter:drop(RefCntr).

http://git-wip-us.apache.org/repos/asf/couchdb/blob/62ce6101/test/etap/200-view-group-no-db-leaks.t
----------------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/couchdb/blob/62ce6101/test/etap/201-view-group-shutdown.t
----------------------------------------------------------------------