Browse Source

Add optional `auto_grow_threshold` for anticipatory growth

Dan Checkoway 10 years ago
parent
commit
7c4a3be9db
4 changed files with 95 additions and 3 deletions
  1. 12 3
      src/pooler.erl
  2. 7 0
      src/pooler.hrl
  3. 1 0
      src/pooler_config.erl
  4. 75 0
      test/pooler_tests.erl

+ 12 - 3
src/pooler.erl

@@ -564,9 +564,18 @@ take_member_from_pool(#pool{init_count = InitCount,
             {error_no_members, Pool2};
         [Pid|Rest] ->
             Pool2 = take_member_bookkeeping(Pid, From, Rest, Pool1),
-            send_metric(Pool, in_use_count, Pool2#pool.in_use_count, histogram),
-            send_metric(Pool, free_count, Pool2#pool.free_count, histogram),
-            {Pid, Pool2}
+            Pool3 = case Pool2#pool.auto_grow_threshold of
+                        N when is_integer(N) andalso
+                               Pool2#pool.free_count =< N andalso
+                               NumCanAdd > 0 ->
+                            NumToAdd = max(min(InitCount - NonStaleStartingMemberCount, NumCanAdd), 0),
+                            add_members_async(NumToAdd, Pool2);
+                        _ ->
+                            Pool2
+                    end,
+            send_metric(Pool, in_use_count, Pool3#pool.in_use_count, histogram),
+            send_metric(Pool, free_count, Pool3#pool.free_count, histogram),
+            {Pid, Pool3}
     end.
 
 -spec take_member_from_pool_queued(#pool{},

+ 7 - 0
src/pooler.hrl

@@ -2,6 +2,7 @@
 -define(DEFAULT_CULL_INTERVAL, {1, min}).
 -define(DEFAULT_MAX_AGE, {30, sec}).
 -define(DEFAULT_MEMBER_START_TIMEOUT, {1, min}).
+-define(DEFAULT_AUTO_GROW_THRESHOLD, undefined).
 -define(POOLER_GROUP_TABLE, pooler_group_table).
 -define(DEFAULT_POOLER_QUEUE_MAX, 50).
 
@@ -73,6 +74,12 @@
           %% The maximum amount of time to allow for member start.
           member_start_timeout = ?DEFAULT_MEMBER_START_TIMEOUT :: time_spec(),
 
+          %% The optional threshold at which more members will be started if
+          %% free_count drops to this value.  Normally undefined, but may be
+          %% set to a non-negative integer in order to enable "anticipatory"
+          %% behavior (start members before they're actually needed).
+          auto_grow_threshold = ?DEFAULT_AUTO_GROW_THRESHOLD :: undefined | non_neg_integer(),
+
           %% The module to use for collecting metrics. If set to
           %% 'pooler_no_metrics', then metric sending calls do
           %% nothing. A typical value to actually capture metrics is

+ 1 - 0
src/pooler_config.erl

@@ -20,6 +20,7 @@ list_to_pool(P) ->
        cull_interval     = ?gv(cull_interval, P, ?DEFAULT_CULL_INTERVAL),
        max_age           = ?gv(max_age, P, ?DEFAULT_MAX_AGE),
        member_start_timeout = ?gv(member_start_timeout, P, ?DEFAULT_MEMBER_START_TIMEOUT),
+       auto_grow_threshold = ?gv(auto_grow_threshold, P, ?DEFAULT_AUTO_GROW_THRESHOLD),
        metrics_mod       = ?gv(metrics_mod, P, pooler_no_metrics),
        metrics_api       = ?gv(metrics_api, P, folsom),
        queue_max         = ?gv(queue_max, P, ?DEFAULT_POOLER_QUEUE_MAX)}.

+ 75 - 0
test/pooler_tests.erl

@@ -985,6 +985,81 @@ pooler_integration_test_() ->
      ]
     }.
 
+pooler_auto_grow_disabled_by_default_test_() ->
+    {setup,
+     fun() ->
+             application:set_env(pooler, metrics_module, fake_metrics),
+             fake_metrics:start_link()
+     end,
+     fun(_X) ->
+             fake_metrics:stop()
+     end,
+    {foreach,
+     % setup
+     fun() ->
+             Pool = [{name, test_pool_1},
+                     {max_count, 5},
+                     {init_count, 2},
+                     {start_mfa,
+                      {pooled_gs, start_link, [{"type-0"}]}}],
+             application:unset_env(pooler, pools),
+             error_logger:delete_report_handler(error_logger_tty_h),
+             application:start(pooler),
+             pooler:new_pool(Pool)
+     end,
+     fun(_X) ->
+             application:stop(pooler)
+     end,
+     [
+      {"take one, and it should not auto-grow",
+       fun() ->
+               ?assertEqual(2, (dump_pool(test_pool_1))#pool.free_count),
+               P = pooler:take_member(test_pool_1),
+               ?assertMatch({"type-0", _Id}, pooled_gs:get_id(P)),
+               timer:sleep(100),
+               ?assertEqual(1, (dump_pool(test_pool_1))#pool.free_count),
+               ok, pooler:return_member(test_pool_1, P)
+       end}
+     ]}}.
+
+pooler_auto_grow_enabled_test_() ->
+    {setup,
+     fun() ->
+             application:set_env(pooler, metrics_module, fake_metrics),
+             fake_metrics:start_link()
+     end,
+     fun(_X) ->
+             fake_metrics:stop()
+     end,
+    {foreach,
+     % setup
+     fun() ->
+             Pool = [{name, test_pool_1},
+                     {max_count, 5},
+                     {init_count, 2},
+                     {auto_grow_threshold, 1},
+                     {start_mfa,
+                      {pooled_gs, start_link, [{"type-0"}]}}],
+             application:unset_env(pooler, pools),
+             error_logger:delete_report_handler(error_logger_tty_h),
+             application:start(pooler),
+             pooler:new_pool(Pool)
+     end,
+     fun(_X) ->
+             application:stop(pooler)
+     end,
+     [
+      {"take one, and it should grow by 2",
+       fun() ->
+               ?assertEqual(2, (dump_pool(test_pool_1))#pool.free_count),
+               P = pooler:take_member(test_pool_1),
+               ?assertMatch({"type-0", _Id}, pooled_gs:get_id(P)),
+               timer:sleep(100),
+               ?assertEqual(3, (dump_pool(test_pool_1))#pool.free_count),
+               ok, pooler:return_member(test_pool_1, P)
+       end}
+     ]}}.
+
 time_as_millis_test_() ->
     Zeros = [ {{0, U}, 0} || U <- [min, sec, ms, mu] ],
     Ones = [{{1, min}, 60000},