Просмотр исходного кода

Add dynamic pool creation via pooler:new_pool/1

Mostly for testing purposes, also added pooler:rm_pool/1.

Updating overview doc, added doc for new_pool (documenting pool config
proplist) and adding basic test coverage for dynamic pools.
Seth Falcon 12 лет назад
Родитель
Сommit
668b5b6aa4
4 измененных файлов с 94 добавлено и 65 удалено
  1. 1 62
      doc/overview.edoc
  2. 52 2
      src/pooler.erl
  3. 24 1
      src/pooler_sup.erl
  4. 17 0
      test/pooler_tests.erl

+ 1 - 62
doc/overview.edoc

@@ -1,5 +1,5 @@
 @author Seth Falcon <seth@userprimary.net>
-@copyright 2011 Seth Falcon
+@copyright 2011-2013 Seth Falcon
 @title pooler - An OTP Process Pool Application
 @doc 
 The pooler application allows you to manage pools of OTP behaviors
@@ -8,64 +8,3 @@ with exclusive access to pool members using pooler:take_member.
 
 See the README.org file for a good introduction to what pooler is all
 about.
-
-== Pooler Configuration ==
-
-Pool configuration is specified in the pooler application's
-environment.  This can be provided in a config file using `-config' or
-set at startup using `application:set_env(pooler, pools, Pools)'.
-Here's an example config file that creates three pools of
-Riak pb clients each talking to a different node in a local cluster:
-
-```
-% pooler.config
-% Start Erlang as: erl -config pooler
-% -*- mode: erlang -*-
-% pooler app config
-[
- {pooler, [
-         {pools, [
-                  [{name, "rc8081"},
-                   {max_count, 5},
-                   {init_count, 2},
-                   {start_mfa,
-                    {riakc_pb_socket, start_link, ["localhost", 8081]}}],
-
-                  [{name, "rc8082"},
-                   {max_count, 5},
-                   {init_count, 2},
-                   {start_mfa,
-                    {riakc_pb_socket, start_link, ["localhost", 8082]}}],
-
-                  [{name, "rc8083"},
-                   {max_count, 5},
-                   {init_count, 2},
-                   {start_mfa,
-                    {riakc_pb_socket, start_link, ["localhost", 8083]}}]
-                 ]}
-        ]}
-].
-'''
-
-== Using pooler ==
-
-Here's an example session:
-
-```
-application:start(pooler).
-P = pooler:take_member(),
-% use P
-pooler:return_member(P, ok).
-'''
-
-Once started, the main interaction you will have with pooler is through
-two functions, `take_member/0' and `return_member/2'.
-
-Call `pooler:take_member()' to obtain a member from a randomly
-selected pool.  When you are done with it, return it to the pool using
-`pooler:return_member(Pid, ok)'.  If you encountered an error using
-the member, you can pass `fail' as the second argument.  In this case,
-pooler will permanently remove that member from the pool and start a
-new member to replace it.  If your process is short lived, you can
-omit the call to `return_member'.  In this case, pooler will detect
-the normal exit of the consumer and reclaim the member.

+ 52 - 2
src/pooler.erl

@@ -1,5 +1,5 @@
 %% @author Seth Falcon <seth@userprimary.net>
-%% @copyright 2011-2012 Seth Falcon
+%% @copyright 2011-2013 Seth Falcon
 %% @doc This is the main interface to the pooler application
 %%
 %% To integrate with your application, you probably want to call
@@ -36,7 +36,9 @@
          return_member/2,
          return_member/3,
          pool_stats/1,
-         manual_start/0]).
+         manual_start/0,
+         new_pool/1,
+         rm_pool/1]).
 
 %% ------------------------------------------------------------------
 %% gen_server Function Exports
@@ -65,6 +67,54 @@ manual_start() ->
     application:start(sasl),
     application:start(pooler).
 
+%% @doc Start a new pool described by the proplist `PoolConfig'. The
+%% following keys are required in the proplist:
+%%
+%% <dl>
+%% <dt>`name'</dt>
+%% <dd>An atom giving the name of the pool.</dd>
+%% <dt>`init_count'</dt>
+%% <dd>Number of members to add to the pool at start. When the pool is
+%% started, `init_count' members will be started in parallel.</dd>
+%% <dt>`max_count'</dt>
+%% <dd>Maximum number of members in the pool.</dd>
+%% <dt>`start_mfa'</dt>
+%% <dd>A tuple of the form `{Mod, Fun, Args}' describing how to start
+%% new pool members.</dd>
+%% </dl>
+%%
+%% In addition, you can specify any of the following optional
+%% configuration options:
+%%
+%% <dl>
+%% <dt>`group'</dt>
+%% <dd>An atom giving the name of the group this pool belongs
+%% to. Pools sharing a common `group' value can be accessed using
+%% {@link take_group_member/1} and {@link return_group_member/2}.</dd>
+%% <dt>`cull_interval'</dt>
+%% <dd>Time between checks for stale pool members. Specified as
+%% `{Time, Unit}' where `Time' is a non-negative integer and `Unit'
+%% is one of `min', `sec', `ms', or `mu'. The default value of `{0,
+%% min}' disables stale member checking. When `Time' is greater than
+%% zero, a message will be sent to the pool at the configured interval
+%% to trigger the removal of members that have not been accessed in
+%% `max_age' time units.</dd>
+%% <dt>`max_age'</dt>
+%% <dd>Members idle longer than `max_age' time units are removed from
+%% the pool when stale checking is enabled via
+%% `cull_interval'. Culling of idle members will never reduce the pool
+%% below `init_count'. The value is specified as `{Time, Unit}'. Note
+%% that timers are not set on individual pool members and may remain
+%% in the pool beyond the configured `max_age' value since members are
+%% only removed on the interval configured via `cull_interval'.</dd>
+%% </dl>
+new_pool(PoolConfig) ->
+    pooler_sup:new_pool(PoolConfig).
+
+%% @doc Terminate the named pool.
+rm_pool(PoolName) ->
+    pooler_sup:rm_pool(PoolName).
+
 %% @doc For INTERNAL use. Adds `MemberPid' to the pool.
 -spec accept_member(atom() | pid(), pid() | {noproc, _}) -> ok.
 accept_member(PoolName, MemberPid) ->

+ 24 - 1
src/pooler_sup.erl

@@ -2,7 +2,10 @@
 
 -behaviour(supervisor).
 
--export([start_link/0, init/1]).
+-export([init/1,
+         new_pool/1,
+         rm_pool/1,
+         start_link/0]).
 
 -include("pooler.hrl").
 
@@ -18,6 +21,26 @@ init([]) ->
     ets:new(?POOLER_GROUP_TABLE, [set, public, named_table, {write_concurrency, true}]),
     {ok, {{one_for_one, 5, 60}, [starter_sup_spec() | PoolSupSpecs]}}.
 
+%% @doc Create a new pool from proplist pool config `PoolConfig'. The
+%% public API for this functionality is {@link pooler:new_pool/1}.
+new_pool(PoolConfig) ->
+    MetricsConfig = {metrics_mod, metrics_module()},
+    NewPool = pooler_config:list_to_pool([MetricsConfig | PoolConfig]),
+    Spec = pool_sup_spec(NewPool),
+    supervisor:start_child(?MODULE, Spec).
+
+%% @doc Shutdown the named pool.
+rm_pool(Name) ->
+    SupName = pool_sup_name(Name),
+    case supervisor:terminate_child(?MODULE, SupName) of
+        {error, not_found} ->
+            ok;
+        ok ->
+            supervisor:terminate_child(?MODULE, SupName);
+        Error ->
+            Error
+    end.
+
 starter_sup_spec() ->
     {pooler_starter_sup, {pooler_starter_sup, start_link, []},
      transient, 5000, supervisor, [pooler_starter_sup]}.

+ 17 - 0
test/pooler_tests.erl

@@ -240,6 +240,23 @@ pooler_basics_test_() ->
        end
       },
 
+      {"dynamic pool creation",
+       fun() ->
+               {ok, SupPid} = pooler:new_pool([{name, dyn_pool_1},
+                                               {max_count, 3},
+                                               {init_count, 2},
+                                               {start_mfa,
+                                                {pooled_gs, start_link, [{"dyn-0"}]}}]),
+               ?assert(is_pid(SupPid)),
+               M = pooler:take_member(dyn_pool_1),
+               ?assertMatch({"dyn-0", _Id}, pooled_gs:get_id(M)),
+               ?assertEqual(ok, pooler:rm_pool(dyn_pool_1)),
+               ?assertExit({noproc, _}, pooler:take_member(dyn_pool_1)),
+               %% remove non-existing pool
+               ?assertEqual(ok, pooler:rm_pool(dyn_pool_X)),
+               ?assertEqual(ok, pooler:rm_pool(dyn_pool_1))
+       end},
+
       {"metrics have been called",
        fun() ->
                %% exercise the API to ensure we have certain keys reported as metrics