You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by rn...@apache.org on 2014/08/01 14:06:02 UTC

[01/11] git commit: Initial commit.

Repository: couchdb-global-changes
Updated Branches:
  refs/heads/windsor-merge [created] 9035f4da4


Initial commit.


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

Branch: refs/heads/windsor-merge
Commit: fed1844e33fa363377f5f8b67e830e5c9f29bcb8
Parents: 
Author: Benjamin Bastian <be...@gmail.com>
Authored: Fri Aug 9 13:47:35 2013 +0100
Committer: Robert Newson <rn...@apache.org>
Committed: Fri Aug 1 10:19:40 2014 +0100

----------------------------------------------------------------------
 README.md | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-global-changes/blob/fed1844e/README.md
----------------------------------------------------------------------
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..e69de29


[02/11] git commit: Add the initial version of the global_changes app.

Posted by rn...@apache.org.
Add the initial version of the global_changes app.

BugzID: 17681


Project: http://git-wip-us.apache.org/repos/asf/couchdb-global-changes/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-global-changes/commit/679b1345
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-global-changes/tree/679b1345
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-global-changes/diff/679b1345

Branch: refs/heads/windsor-merge
Commit: 679b1345db0f1d6df297b5c21df67eb7ebef42cd
Parents: fed1844
Author: Benjamin Bastian <be...@gmail.com>
Authored: Thu Sep 5 18:33:38 2013 -0700
Committer: Robert Newson <rn...@apache.org>
Committed: Fri Aug 1 13:01:54 2014 +0100

----------------------------------------------------------------------
 .gitignore                             |   2 +
 README.md                              |  27 ++++
 src/global_changes.app.src             |  18 +++
 src/global_changes_app.erl             |  18 +++
 src/global_changes_config_listener.erl |  94 +++++++++++++
 src/global_changes_httpd.erl           | 203 ++++++++++++++++++++++++++
 src/global_changes_listener.erl        | 148 +++++++++++++++++++
 src/global_changes_server.erl          | 211 ++++++++++++++++++++++++++++
 src/global_changes_sup.erl             |  35 +++++
 src/global_changes_util.erl            |  17 +++
 10 files changed, 773 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-global-changes/blob/679b1345/.gitignore
----------------------------------------------------------------------
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e1b16d5
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+.eunit/
+ebin/

http://git-wip-us.apache.org/repos/asf/couchdb-global-changes/blob/679b1345/README.md
----------------------------------------------------------------------
diff --git a/README.md b/README.md
index e69de29..f22ee2c 100644
--- a/README.md
+++ b/README.md
@@ -0,0 +1,27 @@
+### global\_changes
+
+This app supplies the functionality for the `/_db_updates` endpoint.
+
+When a database is created, deleted, or updated, a corresponding event will be persisted to disk (Note: This was designed without the guarantee that a DB event will be persisted or ever occur in the `_db_updates` feed. It probably will, but it isn't guaranteed). Users can subscribe to a `_changes`-like feed of these database events by querying the `_db_updates` endpoint.
+
+When an admin user queries the `/_db_updates` endpoint, they will see the account name associated with the DB update as well as update
+
+### Captured Metrics
+
+1: `global_changes`, `db_writes`: The number of doc updates caused by global\_changes.
+
+2: `global_changes`, `server_pending_updates`: The number of documents aggregated into the pending write batch.
+
+3: `global_changes`, `listener_pending_updates`: The number of documents aggregated into the pending event batch.
+
+4: `global_changes`, `event_doc_conflict`: The number of rev tree branches in event docs encountered by global\_changes. Should never happen.
+
+5: `global_changes`, `rpcs`: The number of non-fabric RPCs caused by global\_changes.
+
+### Important Configs
+
+1: `global_changes`, `max_event_delay`: (integer, milliseconds) The total timed added before an event is forwarded to the writer.
+
+2: `global_changes`, `max_write_delay`: (integer, milliseconds) The time added before an event is sent to disk.
+
+3: `global_changes`, `update_db`: (true/false) A flag setting whether to update the global\_changes database. If false, changes will be lost and there will be no performance impact of global\_changes on the cluster.

http://git-wip-us.apache.org/repos/asf/couchdb-global-changes/blob/679b1345/src/global_changes.app.src
----------------------------------------------------------------------
diff --git a/src/global_changes.app.src b/src/global_changes.app.src
new file mode 100644
index 0000000..8586d7c
--- /dev/null
+++ b/src/global_changes.app.src
@@ -0,0 +1,18 @@
+{application, global_changes, [
+    {description, "_changes-like feeds for multiple DBs"},
+    {vsn, git},
+    {registered, [global_changes_config_listener, global_changes_server]},
+    {applications, [
+        kernel,
+        stdlib,
+        config,
+        couch_log,
+        couch,
+        mem3,
+        fabric
+    ]},
+    {mod, {global_changes_app, []}},
+    {env, [
+        {dbname, <<"global_changes">>}
+    ]}
+]}.

http://git-wip-us.apache.org/repos/asf/couchdb-global-changes/blob/679b1345/src/global_changes_app.erl
----------------------------------------------------------------------
diff --git a/src/global_changes_app.erl b/src/global_changes_app.erl
new file mode 100644
index 0000000..a722155
--- /dev/null
+++ b/src/global_changes_app.erl
@@ -0,0 +1,18 @@
+%% Copyright 2013 Cloudant
+
+-module(global_changes_app).
+-behavior(application).
+
+
+-export([
+    start/2,
+    stop/1
+]).
+
+
+start(_StartType, _StartArgs) ->
+    global_changes_sup:start_link().
+
+
+stop(_State) ->
+    ok.

http://git-wip-us.apache.org/repos/asf/couchdb-global-changes/blob/679b1345/src/global_changes_config_listener.erl
----------------------------------------------------------------------
diff --git a/src/global_changes_config_listener.erl b/src/global_changes_config_listener.erl
new file mode 100644
index 0000000..58f69bb
--- /dev/null
+++ b/src/global_changes_config_listener.erl
@@ -0,0 +1,94 @@
+% Copyright 2013 Cloudant. All rights reserved.
+
+-module(global_changes_config_listener).
+-behavior(gen_server).
+-behavior(config_listener).
+
+
+-export([
+    start_link/0
+]).
+
+-export([
+    init/1,
+    terminate/2,
+    handle_call/3,
+    handle_cast/2,
+    handle_info/2,
+    code_change/3
+]).
+
+-export([
+    handle_config_change/5
+]).
+
+
+-define(LISTENER, global_changes_listener).
+-define(SERVER, global_changes_server).
+
+
+start_link() ->
+    gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
+
+
+init([]) ->
+    ok = config:listen_for_changes(?MODULE, nil),
+    {ok, nil}.
+
+
+terminate(_, _St) ->
+    ok.
+
+
+handle_call(Msg, _From, St) ->
+    {stop, {invalid_call, Msg}, {invalid_call, Msg}, St}.
+
+
+handle_cast(Msg, St) ->
+    {stop, {invalid_cast, Msg}, St}.
+
+
+handle_info({gen_event_EXIT, {config_listener, ?MODULE}, _Reason}, St) ->
+    erlang:send_after(5000, self(), restart_config_listener),
+    {noreply, St};
+handle_info(restart_config_listener, St) ->
+    ok = config:listen_for_changes(?MODULE, St),
+    {noreply, St};
+handle_info(_Msg, St) ->
+    {noreply, St}.
+
+
+code_change(_, St, _) ->
+    {ok, St}.
+
+
+handle_config_change("global_changes", "max_event_delay", MaxDelayStr, _, _) ->
+    try list_to_integer(MaxDelayStr) of
+        MaxDelay ->
+            gen_server:cast(?LISTENER, {set_max_event_delay, MaxDelay})
+    catch error:badarg ->
+        ok
+    end,
+    {ok, nil};
+
+handle_config_change("global_changes", "max_write_delay", MaxDelayStr, _, _) ->
+    try list_to_integer(MaxDelayStr) of
+        MaxDelay ->
+            gen_server:cast(?SERVER, {set_max_write_delay, MaxDelay})
+    catch error:badarg ->
+        ok
+    end,
+    {ok, nil};
+
+handle_config_change("global_changes", "update_db", "false", _, _) ->
+    gen_server:cast(?LISTENER, {set_update_db, false}),
+    gen_server:cast(?SERVER, {set_update_db, false}),
+    {ok, nil};
+
+handle_config_change("global_changes", "update_db", _, _, _) ->
+    gen_server:cast(?LISTENER, {set_update_db, true}),
+    gen_server:cast(?SERVER, {set_update_db, true}),
+    {ok, nil};
+
+handle_config_change(_, _, _, _, _) ->
+    {ok, nil}.

http://git-wip-us.apache.org/repos/asf/couchdb-global-changes/blob/679b1345/src/global_changes_httpd.erl
----------------------------------------------------------------------
diff --git a/src/global_changes_httpd.erl b/src/global_changes_httpd.erl
new file mode 100644
index 0000000..0bbc534
--- /dev/null
+++ b/src/global_changes_httpd.erl
@@ -0,0 +1,203 @@
+%% Copyright 2013 Cloudant
+
+-module(global_changes_httpd).
+
+-export([handle_global_changes_req/1]).
+
+-include_lib("couch/include/couch_db.hrl").
+
+-record(acc, {
+    heartbeat_interval,
+    last_data_sent_time,
+    feed,
+    prepend,
+    resp,
+    etag,
+    username
+}).
+
+handle_global_changes_req(#httpd{method='GET'}=Req) ->
+    Db = global_changes_util:get_dbname(),
+    Feed = couch_httpd:qs_value(Req, "feed", "normal"),
+    Options = parse_global_changes_query(Req),
+    Heartbeat = case lists:keyfind(heartbeat, 1, Options) of
+        {heartbeat, Other} -> Other;
+        {heartbeat, true} -> 60000;
+        false -> false
+    end,
+    chttpd:verify_is_server_admin(Req),
+    Acc = #acc{
+        username=admin,
+        feed=Feed,
+        resp=Req,
+        heartbeat_interval=Heartbeat
+    },
+    case Feed of
+        "normal" ->
+            {ok, Info} = fabric:get_db_info(Db),
+            Etag = chttpd:make_etag(Info),
+            chttpd:etag_respond(Req, Etag, fun() ->
+                fabric:changes(Db, fun changes_callback/2, Acc#acc{etag=Etag}, Options)
+            end);
+        Feed when Feed =:= "continuous"; Feed =:= "longpoll" ->
+            fabric:changes(Db, fun changes_callback/2, Acc, Options);
+        _ ->
+            Msg = <<"Supported `feed` types: normal, continuous, longpoll">>,
+            throw({bad_request, Msg})
+    end;
+handle_global_changes_req(Req) ->
+    chttpd:send_method_not_allowed(Req, "GET").
+
+
+transform_change(Username, Resp, {Props}) ->
+    {id, Id} = lists:keyfind(id, 1, Props),
+    {seq, Seq} = lists:keyfind(seq, 1, Props),
+    Info = case binary:split(Id, <<":">>) of
+        [Event0, DbNameAndUsername] ->
+            case binary:split(DbNameAndUsername, <<"/">>) of
+                [AccountName0, DbName0] ->
+                    {Event0, AccountName0, DbName0};
+                [DbName0] ->
+                    {Event0, '_admin', DbName0}
+            end;
+        _ ->
+            skip
+    end,
+    case Info of
+        % Matches the client's username
+        {Event, Username, DbName} when Username /= admin ->
+            {[
+                {dbname, DbName},
+                {type, Event},
+                {seq, Seq}
+            ]};
+        % Client is an admin, show them everything.
+        {Event, AccountName, DbName} when Username == admin ->
+            {[
+                {dbname, DbName},
+                {type, Event},
+                {account, AccountName},
+                {seq, Seq}
+            ]};
+        _ ->
+            skip
+    end.
+
+
+% callbacks for continuous feed (newline-delimited JSON Objects)
+changes_callback(start, #acc{feed="continuous"}=Acc) ->
+    #acc{resp=Req} = Acc,
+    {ok, Resp} = chttpd:start_delayed_json_response(Req, 200),
+    {ok, Acc#acc{resp=Resp, last_data_sent_time=os:timestamp()}};
+changes_callback({change, Change0}, #acc{feed="continuous"}=Acc) ->
+    #acc{resp=Resp, username=Username} = Acc,
+    case transform_change(Username, Resp, Change0) of
+        skip ->
+            {ok, maybe_send_heartbeat(Acc)};
+        Change ->
+            Line = [?JSON_ENCODE(Change) | "\n"],
+            {ok, Resp1} = chttpd:send_delayed_chunk(Resp, Line),
+            {ok, Acc#acc{resp=Resp1, last_data_sent_time=os:timestamp()}}
+    end;
+changes_callback({stop, EndSeq}, #acc{feed="continuous"}=Acc) ->
+    #acc{resp=Resp} = Acc,
+    {ok, Resp1} = chttpd:send_delayed_chunk(Resp,
+        [?JSON_ENCODE({[{<<"last_seq">>, EndSeq}]}) | "\n"]),
+    chttpd:end_delayed_json_response(Resp1);
+
+% callbacks for longpoll and normal (single JSON Object)
+changes_callback(start, #acc{feed="normal", etag=Etag}=Acc)
+        when Etag =/= undefined ->
+    #acc{resp=Req} = Acc,
+    FirstChunk = "{\"results\":[\n",
+    {ok, Resp} = chttpd:start_delayed_json_response(Req, 200,
+       [{"Etag",Etag}], FirstChunk),
+    {ok, Acc#acc{resp=Resp, prepend="", last_data_sent_time=os:timestamp()}};
+changes_callback(start, Acc) ->
+    #acc{resp=Req} = Acc,
+    FirstChunk = "{\"results\":[\n",
+    {ok, Resp} = chttpd:start_delayed_json_response(Req, 200, [], FirstChunk),
+    {ok, Acc#acc{resp=Resp, prepend="", last_data_sent_time=os:timestamp()}};
+changes_callback({change, Change0}, Acc) ->
+    #acc{resp=Resp, prepend=Prepend, username=Username} = Acc,
+    case transform_change(Username, Resp, Change0) of
+        skip ->
+            {ok, maybe_send_heartbeat(Acc)};
+        Change ->
+            #acc{resp=Resp, prepend=Prepend} = Acc,
+            Line = [Prepend, ?JSON_ENCODE(Change)],
+            {ok, Resp1} = chttpd:send_delayed_chunk(Resp, Line),
+            Acc1 = Acc#acc{
+                prepend=",\r\n",
+                resp=Resp1,
+                last_data_sent_time=os:timestamp()
+            },
+            {ok, Acc1}
+    end;
+changes_callback({stop, EndSeq}, Acc) ->
+    #acc{resp=Resp} = Acc,
+    {ok, Resp1} = chttpd:send_delayed_chunk(Resp,
+        ["\n],\n\"last_seq\":", ?JSON_ENCODE(EndSeq), "}\n"]),
+    chttpd:end_delayed_json_response(Resp1);
+
+changes_callback(timeout, Acc) ->
+    {ok, maybe_send_heartbeat(Acc)};
+
+changes_callback({error, Reason}, #acc{resp=Req=#httpd{}}) ->
+    chttpd:send_error(Req, Reason);
+changes_callback({error, Reason}, Acc) ->
+    #acc{etag=Etag, feed=Feed, resp=Resp} = Acc,
+    case {Feed, Etag} of
+        {"normal", Etag} when Etag =/= undefined ->
+            chttpd:send_error(Resp, Reason);
+        _ ->
+            chttpd:send_delayed_error(Resp, Reason)
+    end.
+
+
+maybe_send_heartbeat(#acc{heartbeat_interval=false}=Acc) ->
+    Acc;
+maybe_send_heartbeat(Acc) ->
+    #acc{last_data_sent_time=LastSentTime, heartbeat_interval=Interval, resp=Resp} = Acc,
+    Now = os:timestamp(),
+    case timer:now_diff(Now, LastSentTime) div 1000 > Interval of
+        true ->
+            {ok, Resp1} = chttpd:send_delayed_chunk(Resp, "\n"),
+            Acc#acc{last_data_sent_time=Now, resp=Resp1};
+        false ->
+            Acc
+    end.
+
+
+parse_global_changes_query(Req) ->
+    lists:foldl(fun({Key, Value}, Args) ->
+        case {Key, Value} of
+        {"feed", _} ->
+            [{feed, Value} | Args];
+        {"descending", "true"} ->
+            [{dir, rev} | Args];
+        {"since", _} ->
+            [{since, Value} | Args];
+        {"limit", _} ->
+            [{limit, to_non_neg_int(Value)} | Args];
+        {"heartbeat", "true"} ->
+            [{heartbeat, true} | Args];
+        {"heartbeat", _} ->
+            [{heartbeat, to_non_neg_int(Value)} | Args];
+        {"timeout", _} ->
+            [{timeout, to_non_neg_int(Value)} | Args];
+        _Else -> % unknown key value pair, ignore.
+            Args
+        end
+    end, [], couch_httpd:qs(Req)).
+
+
+to_non_neg_int(Value) ->
+    try list_to_integer(Value) of
+        V when V >= 0 ->
+            V;
+        _ ->
+            throw({bad_request, invalid_integer})
+    catch error:badarg ->
+        throw({bad_request, invalid_integer})
+    end.

http://git-wip-us.apache.org/repos/asf/couchdb-global-changes/blob/679b1345/src/global_changes_listener.erl
----------------------------------------------------------------------
diff --git a/src/global_changes_listener.erl b/src/global_changes_listener.erl
new file mode 100644
index 0000000..d836f2b
--- /dev/null
+++ b/src/global_changes_listener.erl
@@ -0,0 +1,148 @@
+% Copyright 2013 Cloudant. All rights reserved.
+
+-module(global_changes_listener).
+-behavior(couch_event_listener).
+
+
+-export([
+    start/0
+]).
+
+-export([
+    init/1,
+    terminate/2,
+    handle_event/3,
+    handle_cast/2,
+    handle_info/2
+]).
+
+-record(state, {
+    update_db,
+    pending_update_count,
+    pending_updates,
+    last_update_time,
+    max_event_delay,
+    dbname
+}).
+
+
+-include_lib("mem3/include/mem3.hrl").
+
+
+start() ->
+    couch_event_listener:start(?MODULE, nil, [all_dbs]).
+
+
+init(_) ->
+    % get configs as strings
+    UpdateDb0 = config:get("global_changes", "update_db", "true"),
+    MaxEventDelay0 = config:get("global_changes", "max_event_delay", "25"),
+
+    % make config strings into other data types
+    UpdateDb = case UpdateDb0 of "false" -> false; _ -> true end,
+    MaxEventDelay = list_to_integer(MaxEventDelay0),
+
+    State = #state{
+        update_db=UpdateDb,
+        pending_update_count=0,
+        pending_updates=sets:new(),
+        max_event_delay=MaxEventDelay,
+        dbname=global_changes_util:get_dbname()
+    },
+    {ok, State}.
+
+
+terminate(_Reason, _State) ->
+    ok.
+
+
+handle_event(_ShardName, _Event, #state{update_db=false}=State) ->
+    {ok, State};
+handle_event(ShardName, Event, State0)
+        when Event =:= updated orelse Event =:= deleted
+        orelse Event =:= created ->
+    #state{dbname=ChangesDbName} = State0,
+    State = case mem3:dbname(ShardName) of
+        ChangesDbName ->
+            State0;
+        DbName ->
+            #state{pending_update_count=Count} = State0,
+            EventBin = erlang:atom_to_binary(Event, latin1),
+            Key = <<EventBin/binary, <<":">>/binary, DbName/binary>>,
+            Pending = sets:add_element(Key, State0#state.pending_updates),
+            Metric = [global_changes, listener_pending_updates],
+            State0#state{pending_updates=Pending, pending_update_count=Count+1}
+    end,
+    maybe_send_updates(State);
+handle_event(_DbName, _Event, State) ->
+    maybe_send_updates(State).
+
+
+handle_cast({set_max_event_delay, MaxEventDelay}, State) ->
+    maybe_send_updates(State#state{max_event_delay=MaxEventDelay});
+handle_cast({set_update_db, Boolean}, State0) ->
+    % If turning update_db off, clear out server state
+    State = case {Boolean, State0#state.update_db} of
+        {false, true} ->
+            State0#state{
+                update_db=Boolean,
+                pending_updates=sets:new(),
+                pending_update_count=0,
+                last_update_time=undefined
+            };
+        _ ->
+            State0#state{update_db=Boolean}
+    end,
+    maybe_send_updates(State);
+handle_cast(_Msg, State) ->
+    maybe_send_updates(State).
+
+
+maybe_send_updates(#state{pending_update_count=0}=State) ->
+    {ok, State};
+maybe_send_updates(#state{update_db=true}=State) ->
+    #state{max_event_delay=MaxEventDelay, last_update_time=LastUpdateTime} = State,
+    Now = os:timestamp(),
+    case LastUpdateTime of
+    undefined ->
+        {ok, State#state{last_update_time=Now}, MaxEventDelay};
+    _ ->
+        Delta = timer:now_diff(Now, LastUpdateTime) div 1000,
+        if Delta >= MaxEventDelay ->
+            Updates = sets:to_list(State#state.pending_updates),
+            try group_updates_by_node(State#state.dbname, Updates) of
+                Grouped ->
+                    dict:map(fun(Node, Docs) ->
+                        Metric = [global_changes, rpcs],
+                        MFA = {global_changes_server, update_docs, [Docs]},
+                        rexi:cast(Node, MFA)
+                    end, Grouped)
+            catch error:database_does_not_exist ->
+                ok
+            end,
+            State1 = State#state{
+                pending_updates=sets:new(),
+                pending_update_count=0,
+                last_update_time=undefined
+            },
+            {ok, State1};
+        true ->
+            {ok, State, MaxEventDelay-Delta}
+        end
+    end;
+maybe_send_updates(State) ->
+    {ok, State}.
+
+
+handle_info(_Msg, State) ->
+    maybe_send_updates(State).
+
+
+-spec group_updates_by_node(binary(), [binary()]) -> dict().
+group_updates_by_node(DbName, Updates) ->
+    lists:foldl(fun(Key, OuterAcc) ->
+        Shards = mem3:shards(DbName, Key),
+        lists:foldl(fun(#shard{node=Node}, InnerAcc) ->
+            dict:append(Node, Key, InnerAcc)
+        end, OuterAcc, Shards)
+    end, dict:new(), Updates).

http://git-wip-us.apache.org/repos/asf/couchdb-global-changes/blob/679b1345/src/global_changes_server.erl
----------------------------------------------------------------------
diff --git a/src/global_changes_server.erl b/src/global_changes_server.erl
new file mode 100644
index 0000000..8ad9244
--- /dev/null
+++ b/src/global_changes_server.erl
@@ -0,0 +1,211 @@
+% Copyright 2013 Cloudant. All rights reserved.
+
+-module(global_changes_server).
+-behavior(gen_server).
+
+
+-export([
+    start_link/0
+]).
+
+-export([
+    init/1,
+    terminate/2,
+    handle_call/3,
+    handle_cast/2,
+    handle_info/2,
+    code_change/3
+]).
+
+-export([
+    update_docs/1
+]).
+
+
+-include_lib("couch/include/couch_db.hrl").
+-include_lib("mem3/include/mem3.hrl").
+
+
+-record(state, {
+    update_db,
+    pending_update_count,
+    last_update_time,
+    pending_updates,
+    max_write_delay,
+    dbname,
+    handler_ref
+}).
+
+
+start_link() ->
+    gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
+
+
+init([]) ->
+    {ok, Handler} = global_changes_listener:start(),
+    % get configs as strings
+    UpdateDb0 = config:get("global_changes", "update_db", "true"),
+    MaxWriteDelay0 = config:get("global_changes", "max_write_delay", "25"),
+
+    % make config strings into other data types
+    UpdateDb = case UpdateDb0 of "false" -> false; _ -> true end,
+    MaxWriteDelay = list_to_integer(MaxWriteDelay0),
+
+    State = #state{
+        update_db=UpdateDb,
+        pending_update_count=0,
+        pending_updates=sets:new(),
+        max_write_delay=MaxWriteDelay,
+        dbname=global_changes_util:get_dbname(),
+        handler_ref=erlang:monitor(process, Handler)
+    },
+    {ok, State}.
+
+
+terminate(_Reason, _Srv) ->
+    ok.
+
+
+handle_call(_Msg, _From, #state{update_db=false}=State) ->
+    {reply, ok, State};
+handle_call({update_docs, DocIds}, _From, State) ->
+    Pending = sets:union(sets:from_list(DocIds), State#state.pending_updates),
+    NewState = State#state{
+        pending_updates=Pending,
+        pending_update_count=sets:size(Pending)
+    },
+    format_reply(reply, maybe_update_docs(NewState)).
+
+
+handle_cast({set_max_write_delay, MaxWriteDelay}, State) ->
+    NewState = State#state{max_write_delay=MaxWriteDelay},
+    format_reply(noreply, maybe_update_docs(NewState));
+handle_cast({set_update_db, Boolean}, State0) ->
+    % If turning update_db off, clear out server state
+    State = case {Boolean, State0#state.update_db} of
+        {false, true} ->
+            State0#state{
+                update_db=Boolean,
+                pending_updates=sets:new(),
+                pending_update_count=0,
+                last_update_time=undefined
+            };
+        _ ->
+            State0#state{update_db=Boolean}
+    end,
+    format_reply(noreply, maybe_update_docs(State));
+handle_cast(_Msg, State) ->
+    format_reply(noreply, maybe_update_docs(State)).
+
+
+handle_info(start_listener, State) ->
+    {ok, Handler} = global_changes_listener:start(),
+    NewState = State#state{
+        handler_ref=erlang:monitor(process, Handler)
+    },
+    format_reply(noreply, maybe_update_docs(NewState));
+handle_info({'DOWN', Ref, _, _, Reason}, #state{handler_ref=Ref}=State) ->
+    couch_log:error("global_changes_listener terminated: ~w", [Reason]),
+    erlang:send_after(5000, self(), start_listener),
+    format_reply(noreply, maybe_update_docs(State));
+handle_info(_, State) ->
+    format_reply(noreply, maybe_update_docs(State)).
+
+
+code_change(_OldVsn, State, _Extra) ->
+    {ok, State}.
+
+
+maybe_update_docs(#state{pending_update_count=0}=State) ->
+    State;
+maybe_update_docs(#state{update_db=true}=State) ->
+    #state{max_write_delay=MaxWriteDelay, last_update_time=LastUpdateTime} = State,
+    Now = os:timestamp(),
+    case LastUpdateTime of
+    undefined ->
+        {State#state{last_update_time=Now}, MaxWriteDelay};
+    _ ->
+        Delta = round(timer:now_diff(Now, LastUpdateTime)/1000),
+        if Delta >= MaxWriteDelay ->
+            DocIds = sets:to_list(State#state.pending_updates),
+            try group_ids_by_shard(State#state.dbname, DocIds) of
+            GroupedIds ->
+                Docs = dict:fold(fun(ShardName, Ids, DocInfoAcc) ->
+                    {ok, Shard} = couch_db:open(ShardName, []),
+                    try
+                        GroupedDocs = get_docs_locally(Shard, Ids),
+                        GroupedDocs ++ DocInfoAcc
+                    after
+                        couch_db:close(Shard)
+                    end
+                end, [], GroupedIds),
+
+                spawn(fun() ->
+                    fabric:update_docs(State#state.dbname, Docs, [])
+                end),
+
+                Count = State#state.pending_update_count,
+            catch error:database_does_not_exist ->
+                ok
+            end,
+            State#state{
+                pending_updates=sets:new(),
+                pending_update_count=0,
+                last_update_time=undefined
+            };
+        true ->
+            {State, MaxWriteDelay-Delta}
+        end
+    end;
+maybe_update_docs(State) ->
+    State.
+
+
+update_docs(Updates) ->
+    gen_server:call(?MODULE, {update_docs, Updates}).
+
+
+group_ids_by_shard(DbName, DocIds) ->
+    LocalNode = node(),
+    lists:foldl(fun(DocId, Acc) ->
+        Shards = mem3:shards(DbName, DocId),
+        lists:foldl(fun
+            (#shard{node=Node, name=Name}, Acc1) when Node == LocalNode ->
+                dict:append(Name, DocId, Acc1);
+            (_, Acc1) ->
+                Acc1
+        end, Acc, Shards)
+    end, dict:new(), DocIds).
+
+
+format_reply(reply, #state{}=State) ->
+    {reply, ok, State};
+format_reply(reply, {State, Timeout}) ->
+    {reply, ok, State, Timeout};
+format_reply(noreply, #state{}=State) ->
+    {noreply, State};
+format_reply(noreply, {State, Timeout}) ->
+    {noreply, State, Timeout}.
+
+
+get_docs_locally(Shard, Ids) ->
+    lists:map(fun(Id) ->
+        DocInfo = couch_db:get_doc_info(Shard, Id),
+        #doc{id=Id, revs=get_rev(DocInfo)}
+    end, Ids).
+
+
+get_rev(not_found) ->
+    {0, []};
+get_rev({ok, #doc_info{revs=[RevInfo]}}) ->
+    {Pos, Rev} = RevInfo#rev_info.rev,
+    {Pos, [Rev]};
+get_rev({ok, #doc_info{revs=[RevInfo|_]}}) ->
+    % couch_doc:to_doc_info/1 sorts things so that the first
+    % #rev_info in the list is the "winning" revision which is
+    % the one we'd want to base our edit off of. In theory
+    % global_changes should never encounter a conflict by design
+    % but we should record if it happens in case our design isn't
+    % quite right.
+    {Pos, Rev} = RevInfo#rev_info.rev,
+    {Pos, [Rev]}.

http://git-wip-us.apache.org/repos/asf/couchdb-global-changes/blob/679b1345/src/global_changes_sup.erl
----------------------------------------------------------------------
diff --git a/src/global_changes_sup.erl b/src/global_changes_sup.erl
new file mode 100644
index 0000000..471c8ab
--- /dev/null
+++ b/src/global_changes_sup.erl
@@ -0,0 +1,35 @@
+% Copyright 2013 Cloudant. All rights reserved.
+
+-module(global_changes_sup).
+-behavior(supervisor).
+
+
+-export([start_link/0]).
+
+-export([init/1]).
+
+
+start_link() ->
+    supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+
+
+init([]) ->
+    {ok, {
+        {one_for_one, 5, 10}, [
+            {
+                global_changes_server,
+                {global_changes_server, start_link, []},
+                permanent,
+                5000,
+                worker,
+                [global_changes_server]
+            },
+            {
+                global_changes_config_listener,
+                {global_changes_config_listener, start_link, []},
+                permanent,
+                5000,
+                worker,
+                [global_changes_config_listener]
+            }
+    ]}}.

http://git-wip-us.apache.org/repos/asf/couchdb-global-changes/blob/679b1345/src/global_changes_util.erl
----------------------------------------------------------------------
diff --git a/src/global_changes_util.erl b/src/global_changes_util.erl
new file mode 100644
index 0000000..46abf48
--- /dev/null
+++ b/src/global_changes_util.erl
@@ -0,0 +1,17 @@
+% Copyright 2013 Cloudant. All rights reserved.
+
+-module(global_changes_util).
+
+
+-export([get_dbname/0]).
+
+
+get_dbname() ->
+    case application:get_env(global_changes, dbname) of
+        {ok, DbName} when is_binary(DbName) ->
+            DbName;
+        {ok, DbName} when is_list(DbName) ->
+            iolist_to_binary(DbName);
+        _ ->
+            <<"global_changes">>
+    end.


[07/11] git commit: Make changes_callback end request when limit=1

Posted by rn...@apache.org.
Make changes_callback end request when limit=1

Prior to this commit, the limit was hit when limit=0 by the first
changes_callback function clause. This makes maybe_finish end the
request when limit=1, so that it doesn't have to wait for another change
to end the request.

BugzID: 26166


Project: http://git-wip-us.apache.org/repos/asf/couchdb-global-changes/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-global-changes/commit/2b2005a2
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-global-changes/tree/2b2005a2
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-global-changes/diff/2b2005a2

Branch: refs/heads/windsor-merge
Commit: 2b2005a247a74c811e3bb6493ac1f999bae15659
Parents: 7e3839c
Author: Benjamin Bastian <be...@gmail.com>
Authored: Fri Dec 13 14:31:30 2013 -0800
Committer: Robert Newson <rn...@apache.org>
Committed: Fri Aug 1 13:03:15 2014 +0100

----------------------------------------------------------------------
 src/global_changes_httpd.erl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-global-changes/blob/2b2005a2/src/global_changes_httpd.erl
----------------------------------------------------------------------
diff --git a/src/global_changes_httpd.erl b/src/global_changes_httpd.erl
index 931aa55..1ff4be9 100644
--- a/src/global_changes_httpd.erl
+++ b/src/global_changes_httpd.erl
@@ -182,7 +182,7 @@ changes_callback({error, Reason}, Acc) ->
 
 maybe_finish(Acc) ->
     case Acc#acc.limit of
-        0 ->
+        1 ->
             {stop, Acc};
         undefined ->
             {ok, Acc};


[04/11] git commit: Fix changes API usage for new pending values

Posted by rn...@apache.org.
Fix changes API usage for new pending values

BugzId: 26119


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

Branch: refs/heads/windsor-merge
Commit: f3b10d8e2521ddd7026e53bf5ed05451885be13f
Parents: 57d22d6
Author: Paul J. Davis <pa...@gmail.com>
Authored: Thu Dec 12 12:33:50 2013 -0600
Committer: Robert Newson <rn...@apache.org>
Committed: Fri Aug 1 13:01:58 2014 +0100

----------------------------------------------------------------------
 src/global_changes_httpd.erl | 6 ++++++
 1 file changed, 6 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-global-changes/blob/f3b10d8e/src/global_changes_httpd.erl
----------------------------------------------------------------------
diff --git a/src/global_changes_httpd.erl b/src/global_changes_httpd.erl
index c05338b..a23b1e2 100644
--- a/src/global_changes_httpd.erl
+++ b/src/global_changes_httpd.erl
@@ -100,6 +100,9 @@ changes_callback({change, Change0}, #acc{feed="continuous"}=Acc) ->
             {ok, Acc#acc{resp=Resp1, last_data_sent_time=os:timestamp()}}
     end;
 changes_callback({stop, EndSeq}, #acc{feed="continuous"}=Acc) ->
+    % Temporary upgrade clause - Case 24236
+    changes_callback({stop, EndSeq, null}, Acc);
+changes_callback({stop, EndSeq, _Pending}, #acc{feed="continuous"}=Acc) ->
     #acc{resp=Resp} = Acc,
     {ok, Resp1} = chttpd:send_delayed_chunk(Resp,
         [?JSON_ENCODE({[{<<"last_seq">>, EndSeq}]}) | "\n"]),
@@ -135,6 +138,9 @@ changes_callback({change, Change0}, Acc) ->
             {ok, Acc1}
     end;
 changes_callback({stop, EndSeq}, Acc) ->
+    % Temporary upgrade clause - Case 24236
+    changes_callback({stop, EndSeq, null}, Acc);
+changes_callback({stop, EndSeq, _Pending}, Acc) ->
     #acc{resp=Resp} = Acc,
     {ok, Resp1} = chttpd:send_delayed_chunk(Resp,
         ["\n],\n\"last_seq\":", ?JSON_ENCODE(EndSeq), "}\n"]),


[06/11] git commit: Fix limit=N for non-admins by counting post-filter

Posted by rn...@apache.org.
Fix limit=N for non-admins by counting post-filter

Prior to this commit, the _db_updates endpoint would often return less
than N results for limit=N queries, even when there were N changes to
return. This was because the limiting was done by passing the limit
parameter to fabric:changes, which doesn't account for the per-user
filtering done in global_changes_httpd.

This commit adds explicit counting of the filtered changes in
global_changes_httpd and manually ends the request when N post-filter
changes have been seen.

BugzID: 25272


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

Branch: refs/heads/windsor-merge
Commit: df64e86e9329d433b632ab12f8cd8ea747e43c5e
Parents: f3b10d8
Author: Benjamin Bastian <be...@gmail.com>
Authored: Tue Dec 10 13:50:00 2013 -0800
Committer: Robert Newson <rn...@apache.org>
Committed: Fri Aug 1 13:03:13 2014 +0100

----------------------------------------------------------------------
 src/global_changes_httpd.erl | 45 +++++++++++++++++++++++++++++++++------
 1 file changed, 38 insertions(+), 7 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-global-changes/blob/df64e86e/src/global_changes_httpd.erl
----------------------------------------------------------------------
diff --git a/src/global_changes_httpd.erl b/src/global_changes_httpd.erl
index a23b1e2..f3680c6 100644
--- a/src/global_changes_httpd.erl
+++ b/src/global_changes_httpd.erl
@@ -13,7 +13,8 @@
     prepend,
     resp,
     etag,
-    username
+    username,
+    limit
 }).
 
 handle_global_changes_req(#httpd{method='GET'}=Req) ->
@@ -25,22 +26,27 @@ handle_global_changes_req(#httpd{method='GET'}=Req) ->
         {heartbeat, Other} -> Other;
         false -> false
     end,
+    % Limit is handled in the changes callback, since the limit count needs to
+    % only account for changes which happen after the filter.
+    Limit = couch_util:get_value(limit, Options),
+    Options1 = lists:keydelete(limit, 1, Options),
     chttpd:verify_is_server_admin(Req),
     Acc = #acc{
         username=admin,
         feed=Feed,
         resp=Req,
-        heartbeat_interval=Heartbeat
+        heartbeat_interval=Heartbeat,
+        limit=Limit
     },
     case Feed of
         "normal" ->
             {ok, Info} = fabric:get_db_info(Db),
             Etag = chttpd:make_etag(Info),
             chttpd:etag_respond(Req, Etag, fun() ->
-                fabric:changes(Db, fun changes_callback/2, Acc#acc{etag=Etag}, Options)
+                fabric:changes(Db, fun changes_callback/2, Acc#acc{etag=Etag}, Options1)
             end);
         Feed when Feed =:= "continuous"; Feed =:= "longpoll" ->
-            fabric:changes(Db, fun changes_callback/2, Acc, Options);
+            fabric:changes(Db, fun changes_callback/2, Acc, Options1);
         _ ->
             Msg = <<"Supported `feed` types: normal, continuous, longpoll">>,
             throw({bad_request, Msg})
@@ -84,6 +90,11 @@ transform_change(Username, Resp, {Props}) ->
     end.
 
 
+% This clause is only hit when _db_updates is queried with limit=0. For
+% limit>0, the request is stopped by maybe_finish/1.
+changes_callback({change, _}, #acc{limit=0}=Acc) ->
+    {stop, Acc};
+
 % callbacks for continuous feed (newline-delimited JSON Objects)
 changes_callback(start, #acc{feed="continuous"}=Acc) ->
     #acc{resp=Req} = Acc,
@@ -97,7 +108,11 @@ changes_callback({change, Change0}, #acc{feed="continuous"}=Acc) ->
         Change ->
             Line = [?JSON_ENCODE(Change) | "\n"],
             {ok, Resp1} = chttpd:send_delayed_chunk(Resp, Line),
-            {ok, Acc#acc{resp=Resp1, last_data_sent_time=os:timestamp()}}
+            Acc1 = Acc#acc{
+                resp=Resp1,
+                last_data_sent_time=os:timestamp()
+            },
+            maybe_finish(Acc1)
     end;
 changes_callback({stop, EndSeq}, #acc{feed="continuous"}=Acc) ->
     % Temporary upgrade clause - Case 24236
@@ -120,7 +135,12 @@ changes_callback(start, Acc) ->
     #acc{resp=Req} = Acc,
     FirstChunk = "{\"results\":[\n",
     {ok, Resp} = chttpd:start_delayed_json_response(Req, 200, [], FirstChunk),
-    {ok, Acc#acc{resp=Resp, prepend="", last_data_sent_time=os:timestamp()}};
+    Acc1 = Acc#acc{
+        resp=Resp,
+        prepend="",
+        last_data_sent_time=os:timestamp()
+    },
+    maybe_finish(Acc1);
 changes_callback({change, Change0}, Acc) ->
     #acc{resp=Resp, prepend=Prepend, username=Username} = Acc,
     case transform_change(Username, Resp, Change0) of
@@ -135,7 +155,7 @@ changes_callback({change, Change0}, Acc) ->
                 resp=Resp1,
                 last_data_sent_time=os:timestamp()
             },
-            {ok, Acc1}
+            maybe_finish(Acc1)
     end;
 changes_callback({stop, EndSeq}, Acc) ->
     % Temporary upgrade clause - Case 24236
@@ -161,6 +181,17 @@ changes_callback({error, Reason}, Acc) ->
     end.
 
 
+maybe_finish(Acc) ->
+    case Acc#acc.limit of
+        0 ->
+            {stop, Acc};
+        undefined ->
+            {ok, Acc};
+        Limit ->
+            {ok, Acc#acc{limit=Limit-1}}
+    end.
+
+
 maybe_send_heartbeat(#acc{heartbeat_interval=false}=Acc) ->
     Acc;
 maybe_send_heartbeat(Acc) ->


[05/11] git commit: set module version to 1

Posted by rn...@apache.org.
set module version to 1


Project: http://git-wip-us.apache.org/repos/asf/couchdb-global-changes/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-global-changes/commit/57d22d60
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-global-changes/tree/57d22d60
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-global-changes/diff/57d22d60

Branch: refs/heads/windsor-merge
Commit: 57d22d60c13e9821d86ef1dfa97099a970f9d2dd
Parents: 839ad2d
Author: Robert Newson <ro...@cloudant.com>
Authored: Fri Nov 22 16:54:32 2013 +0000
Committer: Robert Newson <rn...@apache.org>
Committed: Fri Aug 1 13:01:58 2014 +0100

----------------------------------------------------------------------
 src/global_changes_config_listener.erl | 3 ++-
 src/global_changes_server.erl          | 3 ++-
 2 files changed, 4 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-global-changes/blob/57d22d60/src/global_changes_config_listener.erl
----------------------------------------------------------------------
diff --git a/src/global_changes_config_listener.erl b/src/global_changes_config_listener.erl
index 58f69bb..89cb7a9 100644
--- a/src/global_changes_config_listener.erl
+++ b/src/global_changes_config_listener.erl
@@ -1,7 +1,8 @@
 % Copyright 2013 Cloudant. All rights reserved.
 
 -module(global_changes_config_listener).
--behavior(gen_server).
+-behaviour(gen_server).
+-vsn(1).
 -behavior(config_listener).
 
 

http://git-wip-us.apache.org/repos/asf/couchdb-global-changes/blob/57d22d60/src/global_changes_server.erl
----------------------------------------------------------------------
diff --git a/src/global_changes_server.erl b/src/global_changes_server.erl
index 8ad9244..619313f 100644
--- a/src/global_changes_server.erl
+++ b/src/global_changes_server.erl
@@ -1,7 +1,8 @@
 % Copyright 2013 Cloudant. All rights reserved.
 
 -module(global_changes_server).
--behavior(gen_server).
+-behaviour(gen_server).
+-vsn(1).
 
 
 -export([


[08/11] git commit: This was a silly off-by-one error while counting

Posted by rn...@apache.org.
This was a silly off-by-one error while counting

The start message doesn't count as a row so we don't want to adjust
Acc#acc.limit based on that message.

BugzId: 26165


Project: http://git-wip-us.apache.org/repos/asf/couchdb-global-changes/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-global-changes/commit/7e3839ce
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-global-changes/tree/7e3839ce
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-global-changes/diff/7e3839ce

Branch: refs/heads/windsor-merge
Commit: 7e3839cecb7c158270ff7d5a1d8837ca94603cb8
Parents: df64e86
Author: Paul J. Davis <pa...@gmail.com>
Authored: Fri Dec 13 14:38:23 2013 -0600
Committer: Robert Newson <rn...@apache.org>
Committed: Fri Aug 1 13:03:15 2014 +0100

----------------------------------------------------------------------
 src/global_changes_httpd.erl | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-global-changes/blob/7e3839ce/src/global_changes_httpd.erl
----------------------------------------------------------------------
diff --git a/src/global_changes_httpd.erl b/src/global_changes_httpd.erl
index f3680c6..931aa55 100644
--- a/src/global_changes_httpd.erl
+++ b/src/global_changes_httpd.erl
@@ -135,12 +135,11 @@ changes_callback(start, Acc) ->
     #acc{resp=Req} = Acc,
     FirstChunk = "{\"results\":[\n",
     {ok, Resp} = chttpd:start_delayed_json_response(Req, 200, [], FirstChunk),
-    Acc1 = Acc#acc{
+    {ok, Acc#acc{
         resp=Resp,
         prepend="",
         last_data_sent_time=os:timestamp()
-    },
-    maybe_finish(Acc1);
+    }};
 changes_callback({change, Change0}, Acc) ->
     #acc{resp=Resp, prepend=Prepend, username=Username} = Acc,
     case transform_change(Username, Resp, Change0) of


[11/11] git commit: Add ASF license

Posted by rn...@apache.org.
Add ASF license


Project: http://git-wip-us.apache.org/repos/asf/couchdb-global-changes/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-global-changes/commit/9035f4da
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-global-changes/tree/9035f4da
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-global-changes/diff/9035f4da

Branch: refs/heads/windsor-merge
Commit: 9035f4da47eed792730ceaa7a96d3e9220173127
Parents: 11e82f6
Author: Robert Newson <rn...@apache.org>
Authored: Wed Jul 16 13:06:58 2014 +0100
Committer: Robert Newson <rn...@apache.org>
Committed: Fri Aug 1 13:03:55 2014 +0100

----------------------------------------------------------------------
 LICENSE                                | 202 ++++++++++++++++++++++++++++
 src/global_changes.app.src             |  12 ++
 src/global_changes_app.erl             |  12 +-
 src/global_changes_config_listener.erl |  12 +-
 src/global_changes_httpd.erl           |  12 +-
 src/global_changes_listener.erl        |  12 +-
 src/global_changes_server.erl          |  12 +-
 src/global_changes_sup.erl             |  12 +-
 src/global_changes_util.erl            |  12 +-
 9 files changed, 291 insertions(+), 7 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-global-changes/blob/9035f4da/LICENSE
----------------------------------------------------------------------
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..e06d208
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,202 @@
+Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "{}"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright {yyyy} {name of copyright owner}
+
+   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.
+

http://git-wip-us.apache.org/repos/asf/couchdb-global-changes/blob/9035f4da/src/global_changes.app.src
----------------------------------------------------------------------
diff --git a/src/global_changes.app.src b/src/global_changes.app.src
index 8586d7c..ce34a80 100644
--- a/src/global_changes.app.src
+++ b/src/global_changes.app.src
@@ -1,3 +1,15 @@
+% 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.
+
 {application, global_changes, [
     {description, "_changes-like feeds for multiple DBs"},
     {vsn, git},

http://git-wip-us.apache.org/repos/asf/couchdb-global-changes/blob/9035f4da/src/global_changes_app.erl
----------------------------------------------------------------------
diff --git a/src/global_changes_app.erl b/src/global_changes_app.erl
index a722155..03322a2 100644
--- a/src/global_changes_app.erl
+++ b/src/global_changes_app.erl
@@ -1,4 +1,14 @@
-%% Copyright 2013 Cloudant
+% 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(global_changes_app).
 -behavior(application).

http://git-wip-us.apache.org/repos/asf/couchdb-global-changes/blob/9035f4da/src/global_changes_config_listener.erl
----------------------------------------------------------------------
diff --git a/src/global_changes_config_listener.erl b/src/global_changes_config_listener.erl
index 89cb7a9..afcb427 100644
--- a/src/global_changes_config_listener.erl
+++ b/src/global_changes_config_listener.erl
@@ -1,4 +1,14 @@
-% Copyright 2013 Cloudant. All rights reserved.
+% 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(global_changes_config_listener).
 -behaviour(gen_server).

http://git-wip-us.apache.org/repos/asf/couchdb-global-changes/blob/9035f4da/src/global_changes_httpd.erl
----------------------------------------------------------------------
diff --git a/src/global_changes_httpd.erl b/src/global_changes_httpd.erl
index 1ff4be9..8cd56d7 100644
--- a/src/global_changes_httpd.erl
+++ b/src/global_changes_httpd.erl
@@ -1,4 +1,14 @@
-%% Copyright 2013 Cloudant
+% 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(global_changes_httpd).
 

http://git-wip-us.apache.org/repos/asf/couchdb-global-changes/blob/9035f4da/src/global_changes_listener.erl
----------------------------------------------------------------------
diff --git a/src/global_changes_listener.erl b/src/global_changes_listener.erl
index 95d8cd0..825f58d 100644
--- a/src/global_changes_listener.erl
+++ b/src/global_changes_listener.erl
@@ -1,4 +1,14 @@
-% Copyright 2013 Cloudant. All rights reserved.
+% 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(global_changes_listener).
 -behavior(couch_event_listener).

http://git-wip-us.apache.org/repos/asf/couchdb-global-changes/blob/9035f4da/src/global_changes_server.erl
----------------------------------------------------------------------
diff --git a/src/global_changes_server.erl b/src/global_changes_server.erl
index fe9502c..be38215 100644
--- a/src/global_changes_server.erl
+++ b/src/global_changes_server.erl
@@ -1,4 +1,14 @@
-% Copyright 2013 Cloudant. All rights reserved.
+% 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(global_changes_server).
 -behaviour(gen_server).

http://git-wip-us.apache.org/repos/asf/couchdb-global-changes/blob/9035f4da/src/global_changes_sup.erl
----------------------------------------------------------------------
diff --git a/src/global_changes_sup.erl b/src/global_changes_sup.erl
index 471c8ab..eb93bcf 100644
--- a/src/global_changes_sup.erl
+++ b/src/global_changes_sup.erl
@@ -1,4 +1,14 @@
-% Copyright 2013 Cloudant. All rights reserved.
+% 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(global_changes_sup).
 -behavior(supervisor).

http://git-wip-us.apache.org/repos/asf/couchdb-global-changes/blob/9035f4da/src/global_changes_util.erl
----------------------------------------------------------------------
diff --git a/src/global_changes_util.erl b/src/global_changes_util.erl
index 46abf48..e8f5435 100644
--- a/src/global_changes_util.erl
+++ b/src/global_changes_util.erl
@@ -1,4 +1,14 @@
-% Copyright 2013 Cloudant. All rights reserved.
+% 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(global_changes_util).
 


[09/11] git commit: Use gen_server:cast rather than rexi RPCs

Posted by rn...@apache.org.
Use gen_server:cast rather than rexi RPCs

Prior to this commit, the global_changes_listener would send updates to
the global_changes_server via a rexi RPC which only did a
gen_server:call. This is needlessly heavyweight and would cause
gen_server:call timeout errors when the global_changes_server was slow
(usually due to slow disk IO). This commit removes the rexi RPC and
replaces it with a gen_server:cast.

Since this causes all global_changes_server handlers to not reply, this
commit also removes the format_reply function as it's unnecessary.

BugzID: 28242


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

Branch: refs/heads/windsor-merge
Commit: b226a241a8cda786d16e951bb9ffaf374c657268
Parents: 2b2005a
Author: Benjamin Bastian <be...@gmail.com>
Authored: Wed Feb 19 13:41:28 2014 -0800
Committer: Robert Newson <rn...@apache.org>
Committed: Fri Aug 1 13:03:55 2014 +0100

----------------------------------------------------------------------
 src/global_changes_listener.erl |  3 +-
 src/global_changes_server.erl   | 55 ++++++++++++++++--------------------
 2 files changed, 25 insertions(+), 33 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-global-changes/blob/b226a241/src/global_changes_listener.erl
----------------------------------------------------------------------
diff --git a/src/global_changes_listener.erl b/src/global_changes_listener.erl
index d836f2b..ccee705 100644
--- a/src/global_changes_listener.erl
+++ b/src/global_changes_listener.erl
@@ -114,8 +114,7 @@ maybe_send_updates(#state{update_db=true}=State) ->
                 Grouped ->
                     dict:map(fun(Node, Docs) ->
                         Metric = [global_changes, rpcs],
-                        MFA = {global_changes_server, update_docs, [Docs]},
-                        rexi:cast(Node, MFA)
+                        global_changes_server:update_docs(Node, Docs)
                     end, Grouped)
             catch error:database_does_not_exist ->
                 ok

http://git-wip-us.apache.org/repos/asf/couchdb-global-changes/blob/b226a241/src/global_changes_server.erl
----------------------------------------------------------------------
diff --git a/src/global_changes_server.erl b/src/global_changes_server.erl
index 619313f..966d866 100644
--- a/src/global_changes_server.erl
+++ b/src/global_changes_server.erl
@@ -19,7 +19,7 @@
 ]).
 
 -export([
-    update_docs/1
+    update_docs/2
 ]).
 
 
@@ -67,20 +67,23 @@ terminate(_Reason, _Srv) ->
     ok.
 
 
-handle_call(_Msg, _From, #state{update_db=false}=State) ->
-    {reply, ok, State};
-handle_call({update_docs, DocIds}, _From, State) ->
+handle_call(_Msg, _From, State) ->
+    {reply, ok, State}.
+
+
+handle_cast(_Msg, #state{update_db=false}=State) ->
+    {noreply, State};
+handle_cast({update_docs, DocIds}, State) ->
     Pending = sets:union(sets:from_list(DocIds), State#state.pending_updates),
     NewState = State#state{
         pending_updates=Pending,
         pending_update_count=sets:size(Pending)
     },
-    format_reply(reply, maybe_update_docs(NewState)).
-
+    maybe_update_docs(NewState);
 
 handle_cast({set_max_write_delay, MaxWriteDelay}, State) ->
     NewState = State#state{max_write_delay=MaxWriteDelay},
-    format_reply(noreply, maybe_update_docs(NewState));
+    maybe_update_docs(NewState);
 handle_cast({set_update_db, Boolean}, State0) ->
     % If turning update_db off, clear out server state
     State = case {Boolean, State0#state.update_db} of
@@ -94,9 +97,9 @@ handle_cast({set_update_db, Boolean}, State0) ->
         _ ->
             State0#state{update_db=Boolean}
     end,
-    format_reply(noreply, maybe_update_docs(State));
+    maybe_update_docs(State);
 handle_cast(_Msg, State) ->
-    format_reply(noreply, maybe_update_docs(State)).
+    maybe_update_docs(State).
 
 
 handle_info(start_listener, State) ->
@@ -104,13 +107,13 @@ handle_info(start_listener, State) ->
     NewState = State#state{
         handler_ref=erlang:monitor(process, Handler)
     },
-    format_reply(noreply, maybe_update_docs(NewState));
+    maybe_update_docs(NewState);
 handle_info({'DOWN', Ref, _, _, Reason}, #state{handler_ref=Ref}=State) ->
     couch_log:error("global_changes_listener terminated: ~w", [Reason]),
     erlang:send_after(5000, self(), start_listener),
-    format_reply(noreply, maybe_update_docs(State));
+    maybe_update_docs(State);
 handle_info(_, State) ->
-    format_reply(noreply, maybe_update_docs(State)).
+    maybe_update_docs(State).
 
 
 code_change(_OldVsn, State, _Extra) ->
@@ -118,13 +121,13 @@ code_change(_OldVsn, State, _Extra) ->
 
 
 maybe_update_docs(#state{pending_update_count=0}=State) ->
-    State;
+    {noreply, State};
 maybe_update_docs(#state{update_db=true}=State) ->
     #state{max_write_delay=MaxWriteDelay, last_update_time=LastUpdateTime} = State,
     Now = os:timestamp(),
     case LastUpdateTime of
     undefined ->
-        {State#state{last_update_time=Now}, MaxWriteDelay};
+        {noreply, State#state{last_update_time=Now}, MaxWriteDelay};
     _ ->
         Delta = round(timer:now_diff(Now, LastUpdateTime)/1000),
         if Delta >= MaxWriteDelay ->
@@ -147,23 +150,23 @@ maybe_update_docs(#state{update_db=true}=State) ->
 
                 Count = State#state.pending_update_count,
             catch error:database_does_not_exist ->
-                ok
+                {noreply, State}
             end,
-            State#state{
+            {noreply, State#state{
                 pending_updates=sets:new(),
                 pending_update_count=0,
                 last_update_time=undefined
-            };
+            }};
         true ->
-            {State, MaxWriteDelay-Delta}
+            {noreply, State, MaxWriteDelay-Delta}
         end
     end;
 maybe_update_docs(State) ->
-    State.
+    {noreply, State}.
 
 
-update_docs(Updates) ->
-    gen_server:call(?MODULE, {update_docs, Updates}).
+update_docs(Node, Updates) ->
+    gen_server:cast({?MODULE, Node}, {update_docs, Updates}).
 
 
 group_ids_by_shard(DbName, DocIds) ->
@@ -179,16 +182,6 @@ group_ids_by_shard(DbName, DocIds) ->
     end, dict:new(), DocIds).
 
 
-format_reply(reply, #state{}=State) ->
-    {reply, ok, State};
-format_reply(reply, {State, Timeout}) ->
-    {reply, ok, State, Timeout};
-format_reply(noreply, #state{}=State) ->
-    {noreply, State};
-format_reply(noreply, {State, Timeout}) ->
-    {noreply, State, Timeout}.
-
-
 get_docs_locally(Shard, Ids) ->
     lists:map(fun(Id) ->
         DocInfo = couch_db:get_doc_info(Shard, Id),


[03/11] git commit: Fix heartbeat=true for db_updates feeds

Posted by rn...@apache.org.
Fix heartbeat=true for db_updates feeds

This commit fixes some bad case clause ordering. It'd match 'true' to a
variable when the variable should only either be false or a number.

BugzID: 24170


Project: http://git-wip-us.apache.org/repos/asf/couchdb-global-changes/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-global-changes/commit/839ad2df
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-global-changes/tree/839ad2df
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-global-changes/diff/839ad2df

Branch: refs/heads/windsor-merge
Commit: 839ad2df150fccd13acdf317fc21d58a63169eaa
Parents: 679b134
Author: Benjamin Bastian <be...@gmail.com>
Authored: Tue Oct 15 11:50:07 2013 -0700
Committer: Robert Newson <rn...@apache.org>
Committed: Fri Aug 1 13:01:57 2014 +0100

----------------------------------------------------------------------
 src/global_changes_httpd.erl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-global-changes/blob/839ad2df/src/global_changes_httpd.erl
----------------------------------------------------------------------
diff --git a/src/global_changes_httpd.erl b/src/global_changes_httpd.erl
index 0bbc534..c05338b 100644
--- a/src/global_changes_httpd.erl
+++ b/src/global_changes_httpd.erl
@@ -21,8 +21,8 @@ handle_global_changes_req(#httpd{method='GET'}=Req) ->
     Feed = couch_httpd:qs_value(Req, "feed", "normal"),
     Options = parse_global_changes_query(Req),
     Heartbeat = case lists:keyfind(heartbeat, 1, Options) of
-        {heartbeat, Other} -> Other;
         {heartbeat, true} -> 60000;
+        {heartbeat, Other} -> Other;
         false -> false
     end,
     chttpd:verify_is_server_admin(Req),


[10/11] git commit: Raise default global_changes max_write_delay & max_event_delay to 500ms

Posted by rn...@apache.org.
Raise default global_changes max_write_delay & max_event_delay to 500ms

BugzID: 28257


Project: http://git-wip-us.apache.org/repos/asf/couchdb-global-changes/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-global-changes/commit/11e82f68
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-global-changes/tree/11e82f68
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-global-changes/diff/11e82f68

Branch: refs/heads/windsor-merge
Commit: 11e82f684887028c8cf8181f8b40720d3837dccf
Parents: b226a24
Author: robfraz <ro...@cloudant.com>
Authored: Thu Feb 20 13:48:19 2014 +0000
Committer: Robert Newson <rn...@apache.org>
Committed: Fri Aug 1 13:03:55 2014 +0100

----------------------------------------------------------------------
 src/global_changes_listener.erl | 2 +-
 src/global_changes_server.erl   | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-global-changes/blob/11e82f68/src/global_changes_listener.erl
----------------------------------------------------------------------
diff --git a/src/global_changes_listener.erl b/src/global_changes_listener.erl
index ccee705..95d8cd0 100644
--- a/src/global_changes_listener.erl
+++ b/src/global_changes_listener.erl
@@ -36,7 +36,7 @@ start() ->
 init(_) ->
     % get configs as strings
     UpdateDb0 = config:get("global_changes", "update_db", "true"),
-    MaxEventDelay0 = config:get("global_changes", "max_event_delay", "25"),
+    MaxEventDelay0 = config:get("global_changes", "max_event_delay", "500"),
 
     % make config strings into other data types
     UpdateDb = case UpdateDb0 of "false" -> false; _ -> true end,

http://git-wip-us.apache.org/repos/asf/couchdb-global-changes/blob/11e82f68/src/global_changes_server.erl
----------------------------------------------------------------------
diff --git a/src/global_changes_server.erl b/src/global_changes_server.erl
index 966d866..fe9502c 100644
--- a/src/global_changes_server.erl
+++ b/src/global_changes_server.erl
@@ -46,7 +46,7 @@ init([]) ->
     {ok, Handler} = global_changes_listener:start(),
     % get configs as strings
     UpdateDb0 = config:get("global_changes", "update_db", "true"),
-    MaxWriteDelay0 = config:get("global_changes", "max_write_delay", "25"),
+    MaxWriteDelay0 = config:get("global_changes", "max_write_delay", "500"),
 
     % make config strings into other data types
     UpdateDb = case UpdateDb0 of "false" -> false; _ -> true end,