From 165531bcf223f1c05c4c2eaef7cd2f2943c10584 Mon Sep 17 00:00:00 2001 From: Adam Kocoloski Date: Sat, 15 Aug 2009 05:11:45 +0000 Subject: better failure modes in replication. See COUCHDB-193, COUCHDB-416 If you try to replicate a DB to itself, the replication will proceed, but no checkpoints will be saved, and the logs will say "checkpoint failure: conflict (are you replicating to yourself?)" If you try to specify a non-existent DB as source or target, replication will fail immediately with a 404. The response body will indicate which DB could not be opened. git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@804436 13f79535-47bb-0310-9956-ffa450edef68 --- src/couchdb/couch_httpd_misc_handlers.erl | 5 ++++- src/couchdb/couch_rep.erl | 24 +++++++++++++++++------- src/couchdb/couch_rep_httpc.erl | 2 ++ 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/couchdb/couch_httpd_misc_handlers.erl b/src/couchdb/couch_httpd_misc_handlers.erl index c4f28308..e72f9d2f 100644 --- a/src/couchdb/couch_httpd_misc_handlers.erl +++ b/src/couchdb/couch_httpd_misc_handlers.erl @@ -79,13 +79,16 @@ handle_task_status_req(Req) -> handle_replicate_req(#httpd{method='POST'}=Req) -> PostBody = couch_httpd:json_body_obj(Req), - case couch_rep:replicate(PostBody, Req#httpd.user_ctx) of + try couch_rep:replicate(PostBody, Req#httpd.user_ctx) of {ok, {JsonResults}} -> send_json(Req, {[{ok, true} | JsonResults]}); {error, {Type, Details}} -> send_json(Req, 500, {[{error, Type}, {reason, Details}]}); {error, Reason} -> send_json(Req, 500, {[{error, Reason}]}) + catch + throw:{db_not_found, Msg} -> + send_json(Req, 404, {[{error, db_not_found}, {reason, Msg}]}) end; handle_replicate_req(Req) -> send_method_not_allowed(Req, "POST"). diff --git a/src/couchdb/couch_rep.erl b/src/couchdb/couch_rep.erl index c5a07685..cde0a85e 100644 --- a/src/couchdb/couch_rep.erl +++ b/src/couchdb/couch_rep.erl @@ -78,7 +78,11 @@ replicate(PostBody, UserCtx) -> replicate(PostBody, UserCtx) end. -init([RepId, {PostProps}, UserCtx] = InitArgs) -> +init(InitArgs) -> + try do_init(InitArgs) + catch throw:{db_not_found, DbUrl} -> {stop, {db_not_found, DbUrl}} end. + +do_init([RepId, {PostProps}, UserCtx] = InitArgs) -> process_flag(trap_exit, true), SourceProps = proplists:get_value(<<"source">>, PostProps), @@ -177,7 +181,6 @@ handle_info({'EXIT', Pid, Reason}, State) -> {stop, Reason, State}. terminate(normal, State) -> - % ?LOG_DEBUG("replication terminating normally", []), #state{ checkpoint_history = CheckpointHistory, committed_seq = NewSeq, @@ -252,7 +255,9 @@ start_replication_server(Replicator) -> end; {error, {already_started, Pid}} -> ?LOG_DEBUG("replication ~p already running at ~p", [RepId, Pid]), - Pid + Pid; + {error, {{db_not_found, DbUrl}, _}} -> + throw({db_not_found, <<"could not open ", DbUrl/binary>>}) end. compare_replication_logs(SrcDoc, TgtDoc) -> @@ -389,7 +394,7 @@ open_db({Props}, _UserCtx) -> }, case couch_rep_httpc:db_exists(Db) of true -> Db; - false -> throw({db_not_found, Url}) + false -> throw({db_not_found, ?l2b(Url)}) end; open_db(<<"http://",_/binary>>=Url, _) -> open_db({[{<<"url">>,Url}]}, []); @@ -446,17 +451,22 @@ do_checkpoint(State) -> {<<"source_last_seq">>, RecordSeqNum}, {<<"history">>, lists:sublist([NewHistoryEntry | OldHistory], 50)} ]}, - % ?LOG_DEBUG("updating src doc ~p", [SourceLog]), + + try {SrcRevPos,SrcRevId} = update_doc(Source, SourceLog#doc{body=NewRepHistory}, []), - % ?LOG_DEBUG("updating tgt doc ~p", [TargetLog]), {TgtRevPos,TgtRevId} = update_doc(Target, TargetLog#doc{body=NewRepHistory}, []), State#state{ checkpoint_history = NewRepHistory, source_log = SourceLog#doc{revs={SrcRevPos, [SrcRevId]}}, target_log = TargetLog#doc{revs={TgtRevPos, [TgtRevId]}} - }. + } + catch throw:conflict -> + ?LOG_ERROR("checkpoint failure: conflict (are you replicating to yourself?)", + []), + State + end. commit_to_both(Source, Target) -> % commit the src async diff --git a/src/couchdb/couch_rep_httpc.erl b/src/couchdb/couch_rep_httpc.erl index 3e3b5f05..3d0696e4 100644 --- a/src/couchdb/couch_rep_httpc.erl +++ b/src/couchdb/couch_rep_httpc.erl @@ -96,6 +96,8 @@ process_response({ok, Status, Headers, Body}, Req) -> MochiHeaders = mochiweb_headers:make(Headers), RedirectUrl = mochiweb_headers:get_value("Location", MochiHeaders), do_request(Req#http_db{url = RedirectUrl}); + Code =:= 409 -> + throw(conflict); Code >= 400, Code < 500 -> ?JSON_DECODE(maybe_decompress(Headers, Body)); Code =:= 500; Code =:= 502 -> -- cgit v1.2.3