Permalink
Browse files

Merge pull request #6 from loucash/use-round-robin-to-checkout-resources

Use round robin to checkout resources
  • Loading branch information...
2 parents 192139f + 9ce38be commit f466aecdb8ea2dda0996af08c3f2683dae612aed @ferd committed Nov 26, 2014
Showing with 65 additions and 26 deletions.
  1. +2 −1 README.markdown
  2. +7 −3 src/dispcount_serv.erl
  3. +6 −4 src/dispcount_watcher.erl
  4. +1 −0 {include → src}/state.hrl
  5. +49 −18 test/dispcount_SUITE.erl
View
@@ -13,7 +13,8 @@ There have been a few characteristics assumed to be present for the design of di
If you cannot afford to ignore a query and wish to eventually serve every one of them, dispcount might not be for you. Otherwise, you'll need to queue them yourself because all it does is grant you a resource or tell you it's busy.
-Also note that the dispatching of resources is done on a hashing basis and doesn't guarantee that all resources are to be allocated before showing a 'busy' response. As mentioned earlier, dispcount makes the assumption that there are limited resources and the demand is superior to their availability; the more requests for resources there are, the better the distribution should be. See 'how does it work' for more details.
+Dispcount supports two types of dispatching, based on: hash (default) or round-robin.
+Note that hashing-based dispatching of resources doesn't guarantee that all resources are to be allocated before showing a 'busy' response. As mentioned earlier, dispcount makes the assumption that there are limited resources and the demand is superior to their availability; the more requests for resources there are, the better the distribution should be. See 'how does it work' for more details.
## How to build ##
View
@@ -31,7 +31,9 @@ init({Parent, Name, {M,A}, Opts}) ->
%% temporarily use this process to receive all requests and just forward
%% them when the time has come, maybe.
ConfTmp = init_tables(Opts),
- Conf = ConfTmp#config{dispatch_name=Name, num_watchers=proplists:get_value(resources,Opts,10)},
+ Conf = ConfTmp#config{dispatch_name=Name,
+ num_watchers=proplists:get_value(resources,Opts,10),
+ dispatch_mechanism=proplists:get_value(dispatch_mechanism,Opts,hash)},
SupSpec =
{{simple_one_for_one, proplists:get_value(maxr, Opts, 1), proplists:get_value(maxt, Opts, 60)},
[{watchers,
@@ -49,9 +51,9 @@ init({Parent, Name, {M,A}, Opts}) ->
handle_call(get_info, _From, S = #config{}) ->
{reply, {ok, S}, S};
handle_call(wait_for_tables, _From, S = #config{num_watchers=N, dispatch_table=Tid}) ->
- %% there should be N + 1 entries in the dispatch table
+ %% there should be N + 2 entries in the dispatch table
case ets:info(Tid, size) of
- X when X =:= N+1 ->
+ X when X =:= N+2 ->
{reply, ok, S};
_ ->
timer:sleep(1),
@@ -85,12 +87,14 @@ init_tables(Opts) ->
Dispatch = ets:new(dispatch_table, [set, public, {write_concurrency,true}]),
Worker = ets:new(worker_table, [set, public, {read_concurrency,true}]),
true = ets:insert(Dispatch, {ct,0}),
+ true = ets:insert(Dispatch, {round_robin,0}),
#config{watcher_type = ets,
dispatch_table = Dispatch,
worker_table = Worker};
named -> %% here
Dispatch = ets:new(dispatch_table, [set, public, {write_concurrency,true}]),
true = ets:insert(Dispatch, {ct,0}),
+ true = ets:insert(Dispatch, {round_robin,0}),
#config{watcher_type = named,
dispatch_table = Dispatch,
worker_table = undefined};
View
@@ -28,8 +28,8 @@ checkout(Conf, Timeout) ->
checkout(self(), Conf, Timeout).
-spec checkout(pid(), #config{}, timeout()) -> {ok, Ref::term(), Resource::term()} | {error, Reason::term()}.
-checkout(ToPid,#config{dispatch_name=Name, num_watchers=Num, watcher_type=Type, dispatch_table=DTid, worker_table=WTid}, Timeout) ->
- case {Type, is_free(DTid, Id = dispatch_id(Num))} of
+checkout(ToPid,#config{dispatch_name=Name, num_watchers=Num, watcher_type=Type, dispatch_table=DTid, dispatch_mechanism=DType, worker_table=WTid}, Timeout) ->
+ case {Type, is_free(DTid, Id = dispatch_id(DType, DTid, Num))} of
{ets, true} ->
[{_,Pid}] = ets:lookup(WTid, Id),
gen_server:call(Pid, {get,ToPid}, Timeout);
@@ -145,8 +145,10 @@ init(Id,Conf,M,A) ->
X -> X
end.
-dispatch_id(Num) ->
- erlang:phash2({now(),self()}, Num) + 1.
+dispatch_id(hash, _Tid, Num) ->
+ erlang:phash2({os:timestamp(),self()}, Num) + 1;
+dispatch_id(round_robin, Tid, Num) ->
+ ets:update_counter(Tid, round_robin, {2, 1, Num, 1}).
is_free(Tid, Id) ->
%% We optionally keep a tiny message queue in there,
@@ -2,4 +2,5 @@
num_watchers = 25 :: pos_integer(),
watcher_type = ets :: 'named' | 'ets',
dispatch_table :: ets:tid() | 'undefined',
+ dispatch_mechanism :: 'hash' | 'round_robin',
worker_table :: ets:tid() | 'undefined'}).
View
@@ -1,14 +1,20 @@
-module(dispcount_SUITE).
-include_lib("common_test/include/ct.hrl").
--export([suite/0, all/0, init_per_suite/1, end_per_suite/1,
+-export([suite/0, all/0, groups/0, init_per_suite/1, end_per_suite/1,
+ init_per_group/2, end_per_group/2,
init_per_testcase/2, end_per_testcase/2]).
-export([starting/1, starting_named/1, stopping/1, overload/1, dead/1, error/1,
restart/1, timer/1]).
suite() -> [{timetrap,{seconds,30}}].
-all() -> [starting, starting_named, stopping, overload, dead, error,
- restart, timer].
+groups() ->
+ [{hash, [], [starting, starting_named, stopping, overload, dead, error,
+ restart, timer]},
+ {round_robin, [], [starting, starting_named, stopping, overload, dead, error,
+ restart, timer]}].
+
+all() -> [{group, hash}, {group, round_robin}].
init_per_suite(Config) ->
application:start(dispcount),
@@ -17,12 +23,21 @@ init_per_suite(Config) ->
end_per_suite(_Config) ->
ok.
+init_per_group(hash, Config) ->
+ [{dispatch_mechanism, hash} | Config];
+init_per_group(round_robin, Config) ->
+ [{dispatch_mechanism, round_robin} | Config].
+
+end_per_group(_Group, _Config) ->
+ ok.
+
init_per_testcase(overload, Config) ->
ok = dispcount:start_dispatch(
ref_overload_dispatcher,
{ref_dispatch, []},
[{restart,permanent},{shutdown,4000},
- {maxr,10},{maxt,60},{resources,2}]
+ {maxr,10},{maxt,60},{resources,2},
+ {dispatch_mechanism, ?config(dispatch_mechanism, Config)}]
),
{ok, Info} = dispcount:dispatcher_info(ref_overload_dispatcher),
[{info, Info} | Config];
@@ -31,7 +46,8 @@ init_per_testcase(dead, Config) ->
ref_dead_dispatcher,
{ref_dispatch, []},
[{restart,permanent},{shutdown,4000},
- {maxr,10},{maxt,60},{resources,1}]
+ {maxr,10},{maxt,60},{resources,1},
+ {dispatch_mechanism, ?config(dispatch_mechanism, Config)}]
),
{ok, Info} = dispcount:dispatcher_info(ref_dead_dispatcher),
[{info, Info} | Config];
@@ -40,7 +56,8 @@ init_per_testcase(error, Config) ->
ref_error_dispatcher,
{ref_dispatch_error, []},
[{restart,permanent},{shutdown,4000},
- {maxr,10},{maxt,60},{resources,1}]
+ {maxr,10},{maxt,60},{resources,1},
+ {dispatch_mechanism, ?config(dispatch_mechanism, Config)}]
),
{ok, Info} = dispcount:dispatcher_info(ref_error_dispatcher),
[{info, Info} | Config];
@@ -50,7 +67,8 @@ init_per_testcase(restart, Config) ->
ref_restart_dispatcher,
{ref_dispatch_restart, [Ref]},
[{restart,permanent},{shutdown,4000},
- {maxr,100},{maxt,1},{resources,1}]
+ {maxr,100},{maxt,1},{resources,1},
+ {dispatch_mechanism, ?config(dispatch_mechanism, Config)}]
),
{ok, Info} = dispcount:dispatcher_info(ref_restart_dispatcher),
[{info, Info},{ref,Ref} | Config];
@@ -59,7 +77,8 @@ init_per_testcase(timer, Config) ->
ref_timer_dispatcher,
{ref_dispatch_noreply, []},
[{restart,permanent},{shutdown,4000},
- {maxr,10},{maxt,1},{resources,1}]
+ {maxr,10},{maxt,1},{resources,1},
+ {dispatch_mechanism, ?config(dispatch_mechanism, Config)}]
),
{ok, Info} = dispcount:dispatcher_info(ref_timer_dispatcher),
[{info, Info} | Config];
@@ -74,15 +93,18 @@ end_per_testcase(error, _Config) ->
dispcount:stop_dispatch(ref_error_dispatcher);
end_per_testcase(restart, _Config) ->
dispcount:stop_dispatch(ref_restart_dispatcher);
+end_per_testcase(timer, _Config) ->
+ dispcount:stop_dispatch(ref_timer_dispatcher);
end_per_testcase(_, _Config) ->
ok.
-starting(_Config) ->
+starting(Config) ->
ok = dispcount:start_dispatch(
ref_dispatcher,
{ref_dispatch, []},
[{restart,permanent},{shutdown,4000},
- {maxr,10},{maxt,60},{resources,10}]
+ {maxr,10},{maxt,60},{resources,10},
+ {dispatch_mechanism, ?config(dispatch_mechanism, Config)}]
),
{ok, Info} = dispcount:dispatcher_info(ref_dispatcher),
case dispcount:checkout(Info) of
@@ -91,20 +113,23 @@ starting(_Config) ->
dispcount:checkin(Info, CheckinReference, Resource);
{error, busy} ->
give_up
- end.
+ end,
+ dispcount:stop_dispatch(ref_dispatcher).
-starting_named(_Config) ->
+starting_named(Config) ->
ok = dispcount:start_dispatch(
first_named_dispatcher,
{ref_dispatch, []},
[{restart,permanent},{shutdown,4000},
- {maxr,10},{maxt,60},{resources,10},{watcher_type,named}]
+ {maxr,10},{maxt,60},{resources,10},{watcher_type,named},
+ {dispatch_mechanism, ?config(dispatch_mechanism, Config)}]
),
ok = dispcount:start_dispatch(
second_named_dispatcher,
{ref_dispatch, []},
[{restart,permanent},{shutdown,4000},
- {maxr,10},{maxt,60},{resources,10},{watcher_type,named}]
+ {maxr,10},{maxt,60},{resources,10},{watcher_type,named},
+ {dispatch_mechanism, ?config(dispatch_mechanism, Config)}]
),
{ok, FirstInfo} = dispcount:dispatcher_info(first_named_dispatcher),
{ok, FirstRef, FirstRes} = dispcount:checkout(FirstInfo),
@@ -113,27 +138,33 @@ starting_named(_Config) ->
{ok, SecondInfo} = dispcount:dispatcher_info(second_named_dispatcher),
{ok, SecondRef, SecondRes} = dispcount:checkout(SecondInfo),
dispcount:checkin(SecondInfo, SecondRef, SecondRes),
+
+ dispcount:stop_dispatch(first_named_dispatcher),
+ dispcount:stop_dispatch(second_named_dispatcher),
ok.
-stopping(_Config) ->
+stopping(Config) ->
ok = dispcount:start_dispatch(
stop_dispatch,
{ref_dispatch, []},
[{restart,permanent},{shutdown,4000},
- {maxr,10},{maxt,60},{resources,1}]
+ {maxr,10},{maxt,60},{resources,1},
+ {dispatch_mechanism, ?config(dispatch_mechanism, Config)}]
),
already_started = dispcount:start_dispatch(
stop_dispatch,
{ref_dispatch, []},
[{restart,permanent},{shutdown,4000},
- {maxr,10},{maxt,60},{resources,1}]
+ {maxr,10},{maxt,60},{resources,1},
+ {dispatch_mechanism, ?config(dispatch_mechanism, Config)}]
),
dispcount:stop_dispatch(stop_dispatch),
ok = dispcount:start_dispatch(
stop_dispatch,
{ref_dispatch, []},
[{restart,permanent},{shutdown,4000},
- {maxr,10},{maxt,60},{resources,1}]
+ {maxr,10},{maxt,60},{resources,1},
+ {dispatch_mechanism, ?config(dispatch_mechanism, Config)}]
),
dispcount:stop_dispatch(stop_dispatch).

0 comments on commit f466aec

Please sign in to comment.