Skip to content
Browse files

Add start_child/3 to limit the number of children

A supervisor behaviour process may have dynamically added children
started by other processes.  The count of the number of children
could be retrieved from the process with count_children/1 before
starting another with start_child/2 if a maximum number of children
is to be maintained.  This introduces an overhead of a round trip
message and the possibility of a race condition.

With the addition of a Limit argument in start_child/3 the supervisor
will either start a child or return {error,child_limit}.

This is quite useful where a fixed size pool of processes might
otherwise be used and is suitable for cases where child workers are
started with high frequency.
  • Loading branch information...
1 parent 843d028 commit 04f94f86e5495f29b61654d7f744ae3eeaca9297 @vances committed Apr 3, 2013
Showing with 98 additions and 50 deletions.
  1. +3 −0 lib/stdlib/doc/src/supervisor.xml
  2. +52 −26 lib/stdlib/src/supervisor.erl
  3. +43 −24 lib/stdlib/test/supervisor_SUITE.erl
View
3 lib/stdlib/doc/src/supervisor.xml
@@ -296,6 +296,7 @@ child_spec() = {Id,StartFunc,Restart,Shutdown,Type,Modules}
</func>
<func>
<name name="start_child" arity="2"/>
+ <name name="start_child" arity="3"/>
<fsummary>Dynamically add a child process to a supervisor.</fsummary>
<type name="child_spec"/>
<type name="startchild_ret"/>
@@ -319,6 +320,8 @@ child_spec() = {Id,StartFunc,Restart,Shutdown,Type,Modules}
supervisor, see below). The child process will be started by
using the start function as defined in the child
specification.</p>
+ <p>If <c><anno>Limit</anno></c> or more children are already specified
+ for the supervisor <c>start_child/3</c> returns <c>{error,child_limit}</c>.</p>
<p>If the case of a <c>simple_one_for_one</c> supervisor,
the child specification defined in <c>Module:init/1</c> will
be used and <c><anno>ChildSpec</anno></c> should instead be an arbitrary
View
78 lib/stdlib/src/supervisor.erl
@@ -22,10 +22,10 @@
%% External exports
-export([start_link/2, start_link/3,
- start_child/2, restart_child/2,
- delete_child/2, terminate_child/2,
- which_children/1, count_children/1,
- check_childspecs/1]).
+ start_child/2, start_child/3,
+ restart_child/2, delete_child/2,
+ terminate_child/2, which_children/1,
+ count_children/1, check_childspecs/1]).
%% Internal exports
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
@@ -136,7 +136,14 @@ start_link(SupName, Mod, Args) ->
SupRef :: sup_ref(),
ChildSpec :: child_spec() | (List :: [term()]).
start_child(Supervisor, ChildSpec) ->
- call(Supervisor, {start_child, ChildSpec}).
+ call(Supervisor, {start_child, ChildSpec, 0}).
+
+-spec start_child(SupRef, ChildSpec, Limit) -> startchild_ret() when
+ SupRef :: sup_ref(),
+ ChildSpec :: child_spec() | (List :: [term()]),
+ Limit :: pos_integer().
+start_child(Supervisor, ChildSpec, Limit) when Limit > 0 ->
+ call(Supervisor, {start_child, ChildSpec, Limit}).
-spec restart_child(SupRef, Id) -> Result when
SupRef :: sup_ref(),
@@ -342,21 +349,35 @@ do_start_child_i(M, F, A) ->
-type call() :: 'which_children' | 'count_children' | {_, _}. % XXX: refine
-spec handle_call(call(), term(), state()) -> {'reply', term(), state()}.
-handle_call({start_child, EArgs}, _From, State) when ?is_simple(State) ->
+handle_call({start_child, EArgs, Limit}, _From, State) when ?is_simple(State) ->
Child = hd(State#state.children),
- #child{mfargs = {M, F, A}} = Child,
- Args = A ++ EArgs,
- case do_start_child_i(M, F, Args) of
- {ok, undefined} when Child#child.restart_type =:= temporary ->
- {reply, {ok, undefined}, State};
- {ok, Pid} ->
- NState = save_dynamic_child(Child#child.restart_type, Pid, Args, State),
- {reply, {ok, Pid}, NState};
- {ok, Pid, Extra} ->
- NState = save_dynamic_child(Child#child.restart_type, Pid, Args, State),
- {reply, {ok, Pid, Extra}, NState};
- What ->
- {reply, What, State}
+ RestartType = Child#child.restart_type,
+ Limited = if
+ Limit > 0, RestartType =:= temporary ->
+ ?SETS:size(dynamics_db(temporary, State#state.dynamics)) >= Limit;
+ Limit > 0, RestartType =/= temporary ->
+ ?DICT:size(dynamics_db(RestartType, State#state.dynamics)) >= Limit;
+ true ->
+ false
+ end,
+ case Limited of
+ true ->
+ {reply, {error, child_limit}, State};
+ false ->
+ #child{mfargs = {M, F, A}} = Child,
+ Args = A ++ EArgs,
+ case do_start_child_i(M, F, Args) of
+ {ok, undefined} when RestartType =:= temporary ->
+ {reply, {ok, undefined}, State};
+ {ok, Pid} ->
+ NState = save_dynamic_child(RestartType, Pid, Args, State),
+ {reply, {ok, Pid}, NState};
+ {ok, Pid, Extra} ->
+ NState = save_dynamic_child(RestartType, Pid, Args, State),
+ {reply, {ok, Pid, Extra}, NState};
+ What ->
+ {reply, What, State}
+ end
end;
%% terminate_child for simple_one_for_one can only be done with pid
@@ -382,13 +403,18 @@ handle_call({terminate_child, Name}, _From, State) ->
handle_call({_Req, _Data}, _From, State) when ?is_simple(State) ->
{reply, {error, simple_one_for_one}, State};
-handle_call({start_child, ChildSpec}, _From, State) ->
- case check_childspec(ChildSpec) of
- {ok, Child} ->
- {Resp, NState} = handle_start_child(Child, State),
- {reply, Resp, NState};
- What ->
- {reply, {error, What}, State}
+handle_call({start_child, ChildSpec, Limit}, _From, State) ->
+ if
+ Limit > 0, length(State#state.children) >= Limit ->
+ {reply, {error, child_limit}, State};
+ true ->
+ case check_childspec(ChildSpec) of
+ {ok, Child} ->
+ {Resp, NState} = handle_start_child(Child, State),
+ {reply, Resp, NState};
+ What ->
+ {reply, {error, What}, State}
+ end
end;
handle_call({restart_child, Name}, _From, State) ->
View
67 lib/stdlib/test/supervisor_SUITE.erl
@@ -369,22 +369,26 @@ extra_return(Config) when is_list(Config) ->
ok.
%%-------------------------------------------------------------------------
-%% Test API functions start_child/2, terminate_child/2, delete_child/2
-%% restart_child/2, which_children/1, count_children/1. Only correct
-%% childspecs are used, handling of incorrect childspecs is tested in
-%% child_specs/1.
+%% Test API functions start_child/2, start_child/3, terminate_child/2,
+%% delete_child/2 restart_child/2, which_children/1, count_children/1.
+%% Only correct childspecs are used, handling of incorrect childspecs is
+%% tested in child_specs/1.
child_adm(Config) when is_list(Config) ->
process_flag(trap_exit, true),
- Child = {child1, {supervisor_1, start_child, []}, permanent, 1000,
- worker, []},
- {ok, _Pid} = start_link({ok, {{one_for_one, 2, 3600}, [Child]}}),
+ Child1 = {child1, {supervisor_1, start_child, []}, permanent, 1000,
+ worker, []},
+ Child2 = {child2, {supervisor_1, start_child, []}, permanent, 1000,
+ worker, []},
+ {ok, _Pid} = start_link({ok, {{one_for_one, 2, 3600}, [Child1]}}),
[{child1, CPid, worker, []}] = supervisor:which_children(sup_test),
[1,1,0,1] = get_child_counts(sup_test),
link(CPid),
%% Start of an already runnig process
{error,{already_started, CPid}} =
- supervisor:start_child(sup_test, Child),
+ supervisor:start_child(sup_test, Child1),
+ {error,{already_started, CPid}} =
+ supervisor:start_child(sup_test, Child1, 2),
%% Termination
{error, not_found} = supervisor:terminate_child(sup_test, hej),
@@ -398,7 +402,8 @@ child_adm(Config) when is_list(Config) ->
ok = supervisor:terminate_child(sup_test, child1),
%% Start of already existing but not running process
- {error,already_present} = supervisor:start_child(sup_test, Child),
+ {error,already_present} = supervisor:start_child(sup_test, Child1),
+ {error,already_present} = supervisor:start_child(sup_test, Child1, 2),
%% Restart
{ok, CPid2} = supervisor:restart_child(sup_test, child1),
@@ -420,15 +425,25 @@ child_adm(Config) when is_list(Config) ->
%% Start
{'EXIT',{noproc,{gen_server,call, _}}} =
- (catch supervisor:start_child(foo, Child)),
- {ok, CPid3} = supervisor:start_child(sup_test, Child),
+ (catch supervisor:start_child(foo, Child1)),
+ {ok, CPid3} = supervisor:start_child(sup_test, Child1),
[{child1, CPid3, worker, []}] = supervisor:which_children(sup_test),
[1,1,0,1] = get_child_counts(sup_test),
+ %% Start Limit
+ {'EXIT',{noproc,{gen_server,call, _}}} =
+ (catch supervisor:start_child(foo, Child2, 2)),
+ {error, child_limit} = supervisor:start_child(sup_test, Child2, 1),
+ {ok, CPid4} = supervisor:start_child(sup_test, Child2, 2),
+ [{child2, CPid4, worker, []},
+ {child1, CPid3, worker, []}] = supervisor:which_children(sup_test),
+ [2,2,0,2] = get_child_counts(sup_test),
+
%% Terminate with Pid not allowed when not simple_one_for_one
{error,not_found} = supervisor:terminate_child(sup_test, CPid3),
- [{child1, CPid3, worker, []}] = supervisor:which_children(sup_test),
- [1,1,0,1] = get_child_counts(sup_test),
+ [{child2, CPid4, worker, []},
+ {child1, CPid3, worker, []}] = supervisor:which_children(sup_test),
+ [2,2,0,2] = get_child_counts(sup_test),
{'EXIT',{noproc,{gen_server,call,[foo,which_children,infinity]}}}
= (catch supervisor:which_children(foo)),
@@ -441,7 +456,7 @@ child_adm(Config) when is_list(Config) ->
%% correct error message is returned.
child_adm_simple(Config) when is_list(Config) ->
Child = {child, {supervisor_1, start_child, []}, permanent, 1000,
- worker, []},
+ worker, []},
{ok, _Pid} = start_link({ok, {{simple_one_for_one, 2, 3600}, [Child]}}),
%% In simple_one_for_one all children are added dynamically
[] = supervisor:which_children(sup_test),
@@ -455,24 +470,28 @@ child_adm_simple(Config) when is_list(Config) ->
supervisor:which_children(sup_test),
[1,1,0,1] = get_child_counts(sup_test),
- {ok, CPid2} = supervisor:start_child(sup_test, []),
+ %% Start Limit
+ {error, child_limit} = supervisor:start_child(sup_test, [], 1),
+ {ok, CPid2} = supervisor:start_child(sup_test, [], 2),
+ {ok, CPid3} = supervisor:start_child(sup_test, [], 1000),
Children = supervisor:which_children(sup_test),
- 2 = length(Children),
+ 3 = length(Children),
+ true = lists:member({undefined, CPid3, worker, []}, Children),
true = lists:member({undefined, CPid2, worker, []}, Children),
true = lists:member({undefined, CPid1, worker, []}, Children),
- [1,2,0,2] = get_child_counts(sup_test),
+ [1,3,0,3] = get_child_counts(sup_test),
%% Termination
{error, simple_one_for_one} = supervisor:terminate_child(sup_test, child1),
- [1,2,0,2] = get_child_counts(sup_test),
+ [1,3,0,3] = get_child_counts(sup_test),
ok = supervisor:terminate_child(sup_test,CPid1),
- [_] = supervisor:which_children(sup_test),
- [1,1,0,1] = get_child_counts(sup_test),
+ [_,_] = supervisor:which_children(sup_test),
+ [1,2,0,2] = get_child_counts(sup_test),
false = erlang:is_process_alive(CPid1),
%% Terminate non-existing proccess is ok
ok = supervisor:terminate_child(sup_test,CPid1),
- [_] = supervisor:which_children(sup_test),
- [1,1,0,1] = get_child_counts(sup_test),
+ [_,_] = supervisor:which_children(sup_test),
+ [1,2,0,2] = get_child_counts(sup_test),
%% Terminate pid which is not a child of this supervisor is not ok
NoChildPid = spawn_link(fun() -> receive after infinity -> ok end end),
{error, not_found} = supervisor:terminate_child(sup_test, NoChildPid),
@@ -1370,13 +1389,13 @@ simple_one_for_one_scale_many_temporary_children(_Config) ->
{ok, _SupPid} = start_link({ok, {{simple_one_for_one, 2, 3600}, [Child]}}),
C1 = [begin
- {ok,P} = supervisor:start_child(sup_test,[]),
+ {ok,P} = supervisor:start_child(sup_test, [], 1000),
P
end || _<- lists:seq(1,1000)],
{T1,done} = timer:tc(?MODULE,terminate_all_children,[C1]),
C2 = [begin
- {ok,P} = supervisor:start_child(sup_test,[]),
+ {ok,P} = supervisor:start_child(sup_test, [], 11000),
P
end || _<- lists:seq(1,10000)],
{T2,done} = timer:tc(?MODULE,terminate_all_children,[C2]),

0 comments on commit 04f94f8

Please sign in to comment.
Something went wrong with that request. Please try again.