Starting a child with simple_one_for_one supervisor - erlang

How do you start a child with simple_one_for_one supervisor?
I'm going through the LYSE book and currently on the Dynamic Supervision section: http://learnyousomeerlang.com/supervisors#dynamic-supervision
The author starts the child as follows, but I have no idea where djembe is defined:
1> supervisor:start_child(band_supervisor, [djembe, good]).
Musician Janet Tennelli, playing the djembe entered the room
{ok,<0.690.0>}
2> supervisor:start_child(band_supervisor, [djembe, good]).
{error,{already_started,<0.690.0>}}
Here is my attempt:
2> supervisor:start_link(band_supervisor, jamband).
{ok,<0.40.0>}
3> supervisor:start_child(band_supervisor, [djembe, good]).
=ERROR REPORT==== 28-Feb-2016::03:52:56 ===
** Generic server <0.40.0> terminating
** Last message in was {'EXIT',<0.33.0>,
{{noproc,
{gen_server,call,
[band_supervisor,
{start_child,[djembe,good]},
infinity]}},
[{gen_server,call,3,
[{file,"gen_server.erl"},{line,212}]},
{erl_eval,do_apply,6,
[{file,"erl_eval.erl"},{line,673}]},
{shell,exprs,7,[{file,"shell.erl"},{line,686}]},
{shell,eval_exprs,7,
[{file,"shell.erl"},{line,641}]},
{shell,eval_loop,3,
[{file,"shell.erl"},{line,626}]}]}}
** When Server state == {state,
{<0.40.0>,band_supervisor},
simple_one_for_one,
[{child,undefined,jam_musician,
{musicians,start_link,[]},
temporary,1000,worker,
[musicians]}],
undefined,3,60,[],band_supervisor,jamband}
** Reason for termination ==
** {{noproc,{gen_server,call,
[band_supervisor,
{start_child,[djembe,good]},
infinity]}},
[{gen_server,call,3,[{file,"gen_server.erl"},{line,212}]},
{erl_eval,do_apply,6,[{file,"erl_eval.erl"},{line,673}]},
{shell,exprs,7,[{file,"shell.erl"},{line,686}]},
{shell,eval_exprs,7,[{file,"shell.erl"},{line,641}]},
{shell,eval_loop,3,[{file,"shell.erl"},{line,626}]}]}
** exception exit: {noproc,{gen_server,call,
[band_supervisor,
{start_child,[djembe,good]},
infinity]}}
in function gen_server:call/3 (gen_server.erl, line 212)
Here is how the supervisor looks:
-module(band_supervisor).
-behaviour(supervisor).
-export([start_link/1]).
-export([init/1]).
start_link(Type) ->
supervisor:start_link({local,?MODULE}, ?MODULE, Type).
%% The band supervisor will allow its band members to make a few
%% mistakes before shutting down all operations, based on what
%% mood he's in. A lenient supervisor will tolerate more mistakes
%% than an angry supervisor, who'll tolerate more than a
%% complete jerk supervisor
init(lenient) ->
init({one_for_one, 3, 60});
init(angry) ->
init({rest_for_one, 2, 60});
init(jerk) ->
init({one_for_all, 1, 60});
init(jamband) ->
{ok, {{simple_one_for_one, 3, 60},
[{jam_musician,
{musicians, start_link, []},
temporary, 1000, worker, [musicians]}
]}};
init({RestartStrategy, MaxRestart, MaxTime}) ->
{ok, {{RestartStrategy, MaxRestart, MaxTime},
[{singer,
{musicians, start_link, [singer, good]},
permanent, 1000, worker, [musicians]},
{bass,
{musicians, start_link, [bass, good]},
temporary, 1000, worker, [musicians]},
{drum,
{musicians, start_link, [drum, bad]},
transient, 1000, worker, [musicians]},
{keytar,
{musicians, start_link, [keytar, good]},
transient, 1000, worker, [musicians]}
]}}.
here are the musicians:
-module(musicians).
-behaviour(gen_server).
-export([start_link/2, stop/1]).
-export([init/1, handle_call/3, handle_cast/2,
handle_info/2, code_change/3, terminate/2]).
-record(state, {name="", role, skill=good}).
-define(DELAY, 750).
start_link(Role, Skill) ->
gen_server:start_link({local, Role}, ?MODULE, [Role, Skill], []).
stop(Role) -> gen_server:call(Role, stop).
init([Role, Skill]) ->
%% To know when the parent shuts down
process_flag(trap_exit, true),
%% sets a seed for random number generation for the life of the process
%% uses the current time to do it. Unique value guaranteed by now()
random:seed(now()),
TimeToPlay = random:uniform(3000),
Name = pick_name(),
StrRole = atom_to_list(Role),
io:format("Musician ~s, playing the ~s entered the room~n",
[Name, StrRole]),
{ok, #state{name=Name, role=StrRole, skill=Skill}, TimeToPlay}.
handle_call(stop, _From, S=#state{}) ->
{stop, normal, ok, S};
handle_call(_Message, _From, S) ->
{noreply, S, ?DELAY}.
handle_cast(_Message, S) ->
{noreply, S, ?DELAY}.
handle_info(timeout, S = #state{name=N, skill=good}) ->
io:format("~s produced sound!~n",[N]),
{noreply, S, ?DELAY};
handle_info(timeout, S = #state{name=N, skill=bad}) ->
case random:uniform(5) of
1 ->
io:format("~s played a false note. Uh oh~n",[N]),
{stop, bad_note, S};
_ ->
io:format("~s produced sound!~n",[N]),
{noreply, S, ?DELAY}
end;
handle_info(_Message, S) ->
{noreply, S, ?DELAY}.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
terminate(normal, S) ->
io:format("~s left the room (~s)~n",[S#state.name, S#state.role]);
terminate(bad_note, S) ->
io:format("~s sucks! kicked that member out of the band! (~s)~n",
[S#state.name, S#state.role]);
terminate(shutdown, S) ->
io:format("The manager is mad and fired the whole band! "
"~s just got back to playing in the subway~n",
[S#state.name]);
terminate(_Reason, S) ->
io:format("~s has been kicked out (~s)~n", [S#state.name, S#state.role]).
%% Yes, the names are based off the magic school bus characters
%% 10 names!
pick_name() ->
%% the seed must be set for the random functions. Use within the
%% process that started with init/1
lists:nth(random:uniform(10), firstnames())
++ " " ++
lists:nth(random:uniform(10), lastnames()).
firstnames() ->
["Valerie", "Arnold", "Carlos", "Dorothy", "Keesha",
"Phoebe", "Ralphie", "Tim", "Wanda", "Janet"].
lastnames() ->
["Frizzle", "Perlstein", "Ramon", "Ann", "Franklin",
"Terese", "Tennelli", "Jamal", "Li", "Perlstein"].

learnyousomeerlang.com is a good source of information but sometimes I find it too elaborate. I learned that part of OTP directly by reading the supervisor OTP documentation. You probably don't need the gen_server if you just want to understand dynamic children. See my simple implementation:
-module(estp_proj_sup).
-behaviour(supervisor).
%% API
-export([start_link/0]).
%% Supervisor callbacks
-export([init/1, add/2]).
-define(SERVER, ?MODULE).
start_link() ->
supervisor:start_link({local, ?SERVER}, ?MODULE, []).
init([]) ->
Child = ?WORKER(estp_project),
{ok, {{simple_one_for_one, 3, 30}, [Child]}}.
add(Name, Cfg) ->
Server = estp_project:server_name(Name),
State = [{name, Name}|Cfg],
case whereis(Server) of
undefined ->
add_child(Server, State);
Pid ->
delete_child(Server, Pid),
add_child(Server, State)
end.
add_child(Server, State) ->
supervisor:start_child(?SERVER, [Server, State]).
delete_child(Server, Pid) ->
ok = supervisor:terminate_child(?SERVER, Pid).
where the dynamic child name estp_project:server_name/1 is created like this:
server_name(Name) ->
Module = atom_to_binary(?MODULE, utf8),
Binary = atom_to_binary(Name, utf8),
binary_to_atom(<<Module/binary, <<"$">>/binary, Binary/binary>>, utf8).
and worker is defined as:
-define(SHUTDOWN_TIMEOUT, 5000).
-define(WORKER(I), {I, {I, start_link, []}, permanent, ?SHUTDOWN_TIMEOUT, worker, [I]}).
You then add children simply by calling estp_proj_sup:add(Name, Cfg) like in this code:
process(Res) ->
%% Res contains parsed list of project names and their configurations
[set_project(Name, Cfg) || {Name, {ok, Cfg}} <- Res].
set_project(Name, Cfg) ->
case estp_proj_sup:add(Name, Cfg) of
{ok, _Pid} -> ok;
{error, _} = Err -> Err
end.
Anyways, I tried your example and it seems to be working:
4> {ok, S} = band_supervisor:start_link(jamband).
{ok,<0.47.0>}
5> supervisor:start_child(band_supervisor, [djembe, good]).
Musician Wanda Terese, playing the djembe entered the room
{ok,<0.49.0>}
Wanda Terese produced sound!
Wanda Terese produced sound!
Wanda Terese produced sound!
Wanda Terese produced sound!
Wanda Terese produced sound!
6> supervisor:start_child(band_supervisor, [djembe, good]).
{error,{already_started,<0.49.0>}}
Wanda Terese produced sound!
Wanda Terese produced sound!

Related

Can't start process when starting erlag node

I was trying to start a supervisor and a gen_server process while one node is created from the command-line, I use the following command to start the node:
erl -name vm01#192.168.146.129 -s ets_sup start vm02#192.168.146.129 calc
However, I found the process is undefined when I use whereis to check the process on the newly created node. I got no problems running ets_sup and ets_srv on the node shell directly, but starting the node from command line doesn't work. I want to know why this happening?
ets_sup.erl:
-module(ets_sup).
-behaviour(supervisor).
-export([start/1, start_link/1, init/1]).
start([A, B]) ->
start_link([A, B]).
start_link([A, B]) ->
supervisor:start_link({local, ?MODULE}, ?MODULE, [A, B]).
init([A, B]) ->
{ok, {{one_for_all, 0, 1},
[{ets_srv, {ets_srv, start_link, [A, B]}, permanent, 5000, worker, [ets_srv]}]}}.
ets_srv.erl:
-module(ets_srv).
-behaviour(gen_server).
-compile(export_all).
-record(state, {a, b}).
start_link(A,B) ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [A, B], []).
init([A, B]) ->
{ok, #state{a = A, b = B}}.
check_sys() ->
gen_server:call(?MODULE, check_sys).
handle_call(check_sys, _From, #state{a = A, b = B} = State) ->
{reply, {A, B}, State}.
handle_info(_Info, State) -> {noreply, State}.
handle_cast(_Req, State) -> {noreply, State}.
code_change(_Ol, State, _Ex) -> {ok, State}.
terminate(_R, _S) -> ok.
I think that you really start the function ets_sup:start/1 with your parameter list. You can verify it by adding an io:format(...) before the ets_sup:start_link/1.
but the process who executes the function ets_sup:start/1 is an early one who dies very soon with reason shutdown, and as your supervisor is linked to it, it dies too, with all its children.
You have to call this function from a process that will not die (usually, it is the role of the application manager). For example, do:
start([A, B]) ->
% spawn a new process
spawn(fun () ->
start_link([A, B]),
% add a loop to keep it alive
loop()
end).
loop() ->
receive
stop -> ok;
_ -> loop()
end.
Edit, but not an answer
I have modified your code:
add process_flag(trap_exit, true), in the init server, in order to catch an exit message,
add io:format("server terminate with reason ~p, process ~p~n",[_R,self()]), in the server terminate function in order to print an exit reason eventually sent by the supervisor (Note: if an exit message is sent by another process, handle_info will be triggered).
add ets_srv:check_sys() in the supervisor, just after the start of the server in order to check if it did star correctly.
Here is the modified code.
-module(ets_sup).
-behaviour(supervisor).
-export([start/1, start_link/1, init/1]).
start([A, B]) ->
start_link([A, B]).
start_link([A, B]) ->
supervisor:start_link({local, ?MODULE}, ?MODULE, [A, B]),
ets_srv:check_sys().
init([A, B]) ->
{ok, {{one_for_all, 0, 1},
[{ets_srv, {ets_srv, start_link, [A, B]}, permanent, 5000, worker, [ets_srv]}]}}.
-module(ets_srv).
-behaviour(gen_server).
-compile(export_all).
-record(state, {a, b}).
start_link(A,B) ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [A, B], []).
init([A, B]) ->
process_flag(trap_exit, true),
{ok, #state{a = A, b = B}}.
check_sys() ->
gen_server:call(?MODULE, check_sys).
handle_call(check_sys, _From, #state{a = A, b = B} = State) ->
io:format("check_sys state ~p, process ~p~n",[State,self()]),
{reply, {A, B}, State}.
handle_info(_Info, State) ->
{noreply, State}.
handle_cast(_Req, State) ->
{noreply, State}.
code_change(_Ol, State, _Ex) ->
{ok, State}.
terminate(_R, _S) ->
io:format("server terminate with reason ~p, process ~p~n",[_R,self()]),
ok.
Running this version shows that the supervisor starts the server correctly, and then it sends a shutdown message to it. This does not occurs if the supervisor is started in the shell.
C:\src>erl -s ets_sup start vm02#192.168.146.129 calc
check_sys state {state,'vm02#192.168.146.129',calc}, process <0.56.0>
server terminate with reason shutdown, process <0.56.0>
Eshell V8.2 (abort with ^G)
1> whereis(ets_srv).
undefined
2> ets_sup:start(['vm02#192.168.146.129',calc]).
check_sys state {state,'vm02#192.168.146.129',calc}, process <0.61.0>
{'vm02#192.168.146.129',calc}
3> whereis(ets_srv).
<0.61.0>
4> ets_srv:check_sys().
check_sys state {state,'vm02#192.168.146.129',calc}, process <0.61.0>
{'vm02#192.168.146.129',calc}
5> exit(whereis(ets_srv),shutdown).
true
6> whereis(ets_srv).
<0.61.0>
7> exit(whereis(ets_srv),kill).
** exception exit: shutdown
8> whereis(ets_srv).
undefined
9>
I have verified that if you start an ordinary process (not a supervisor) the same way, using spawn_link, it doesn't receive any exit message.
-module (st).
-compile([export_all]).
start(Arg) ->
do_start(Arg).
do_start(Arg) ->
io:format("spawn from ~p~n",[self()]),
register(?MODULE,spawn_link(fun () -> init(Arg) end)).
init(Arg) ->
io:format("init with ~p in ~p~n",[Arg,self()]),
process_flag(trap_exit, true),
Pid = self(),
spawn(fun() -> monitor(process,Pid), receive M -> io:format("loop received ~p~n",[M]) end end),
loop(Arg).
loop(Arg) ->
receive
state ->
io:format("state is ~p~n",[Arg]),
loop(Arg);
stop ->
io:format("stopping~n");
_ ->
loop(Arg)
end.
The execution gives:
C:\src>erl -s st start vm02#192.168.146.129 calc
spawn from <0.3.0>
init with ['vm02#192.168.146.129',calc] in <0.55.0>
Eshell V8.2 (abort with ^G)
1> whereis(st).
<0.55.0>
2> exit(whereis(st),shutdown).
true
3> whereis(st).
<0.55.0>
4> st ! state.
state is ['vm02#192.168.146.129',calc]
state
5> st ! stop.
stopping
loop received {'DOWN',#Ref<0.0.4.66>,process,<0.55.0>,normal}
stop
6> whereis(st).
undefined
7>
Edit, another way to "detach" the supervisor
All the tests I have done show that the supervisor receives a shutdown message. I don't know why, usually I use the application mechanism to start a supervision tree, and I never met this situation.
I propose you to unlink the supervisor from its parent, so it will not receive a shutdown message:
-module(ets_sup).
-behaviour(supervisor).
-export([start/1, start_link/1, init/1]).
start([A, B]) ->
start_link([A, B]).
start_link([A, B]) ->
supervisor:start_link({local, ?MODULE}, ?MODULE, [A, B]),
ets_srv:check_sys().
init([A, B]) ->
{links,[Parent]} = process_info(self(),links),
unlink(Parent),
{ok, {{one_for_all, 0, 1},
[{ets_srv, {ets_srv, start_link, [A, B]}, permanent, 5000, worker, [ets_srv]}]}}.
An now it works:
C:\src>erl -s ets_sup start vm02#192.168.146.129 calc
check_sys state {state,'vm02#192.168.146.129',calc}, process <0.56.0>
Eshell V8.2 (abort with ^G)
1> ets_srv:check_sys().
check_sys state {state,'vm02#192.168.146.129',calc}, process <0.56.0>
{'vm02#192.168.146.129',calc}
2>

How to spawn a variable number of gen_servers in Erlang

Currently I am using lists:foreach in conjunction with spawn_link to start a variable number of "workers" for a project, the number of workers determined at start up. I'd like the workers to each be a gen_server, so that I can call asynchronous or synchronous messages in them (gen_server:cast, etc.) Is this possible?
Yes, this is possible.
You can use simple_one_for_one:
http://erlang.org/doc/man/supervisor.html#start_child-2
a simplified one_for_one supervisor, where all child processes are
dynamically added instances of the same process type.
Here is a code example:
master.erl is a supervisor:
-module(master).
-behaviour(supervisor).
%% API
-export([start_link/0]).
%% Supervisor callbacks
-export([init/1]).
-define(SERVER, ?MODULE).
start_link() ->
supervisor:start_link({local, ?SERVER}, ?MODULE, []).
init([]) ->
RestartStrategy = simple_one_for_one,
MaxRestarts = 1000,
MaxSecondsBetweenRestarts = 3600,
SupFlags = {RestartStrategy, MaxRestarts, MaxSecondsBetweenRestarts},
Restart = permanent,
Shutdown = 2000,
Type = worker,
AChild = {'worker', {'worker', start_link, []},
Restart, Shutdown, Type, ['worker']},
{ok, {SupFlags, [AChild]}}.
worker.erl is child worker:
-module(worker).
-behaviour(gen_server).
%% API
-export([start_link/0]).
-export([start_link/1]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-define(SERVER, ?MODULE).
-record(state, {}).
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
start_link(I) ->
ServerName = lists:flatten(io_lib:format("~p~p", [?SERVER, I])),
io:format("I am ~p~n", [list_to_atom(ServerName)]),
gen_server:start_link({local, list_to_atom(ServerName)}, ?MODULE, [], []).
init([]) ->
{ok, #state{}}.
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.
handle_cast(calc, State) ->
io:format("result 2+2=4~n"),
{noreply, State};
handle_cast(calcbad, State) ->
io:format("result 1/0~n"),
1 / 0,
{noreply, State};
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
In the erlang shell:
22> master:start_link().
{ok,<0.2475.0>}
23> lists:map(fun(X) -> supervisor:start_child(master, [X]) end, lists:seq(1, 10)).
Nothing prevents you from calling my_worker:start_link instead of spawn_link.
In worker
-module(my_worker).
-behaviour(gen_server).
%% API
-export([start_link/1]).
%% gen_server callbacks
-export([init/1, ...]).
%% API
start_link(Arg) ->
gen_server:start_link(?MODULE, Arg, []).
%% gen_server callbacks
init(Arg) ->
...
Then you can just launch it
[ {ok, _Pid} = my_worker:start_link(Arg) || Arg <- Args ].
If you like to put them under supervisor:
-module(my_sup).
-behaviour(supervisor).
%% API
-export([start_link/1]).
%% supervisor callbacks
-export([init/1]).
%% API
start_link(Argg) ->
gen_server:start_link(?MODULE, Args).
%% supervisor callbacks
init(Args) ->
Sup_flags = #{strategy => one_for_one, intensity => 1, period => 5},
Child_specs = [ #{id => Id, start => MFA}
|| {Id, {_M, _F, _A} = MFA} <- Args ],
{ok, {Sup_flags, Child_specs}}.
You can read their configuration from application:get_env/1,2,3 or database or whatever. You can start them afterward using supervisor:start_child/2. You can use simple_one_for_one and so on. It is just a process.

RabbitMQ work queue is blocking consumers

I'm following the example here
https://www.rabbitmq.com/tutorials/tutorial-two-python.html
only in Erlang with amqp_client
The full code is here
https://github.com/jhw/rabbit_worker
As I understand it, to get worker- queue style behaviour you need to define your RabbitMQ topology as follows -
'direct' exchange
single queue to which multiple consumers (workers) bind
routing key equal to queue name
workers to ack' each message
pre- fetch count of 1
Now I have this working fine, and can see the exchange passing messages round- robin fashion to the workers
The only problem is that there's no parallelism; only one worker is called at a time; it's as if a request to one worker is blocking the exchange from sending messages to any of the others
So I think I probably have my RabbitMQ topology set up incorrectly; but the problem is I don't know where.
Any thoughts ?
TIA.
Core code below -
-module(pool_router).
-behaviour(gen_server).
-include_lib("amqp_client/include/amqp_client.hrl").
%% API.
-export([start_link/0]).
-export([subscribe/1]).
-export([unsubscribe/1]).
-export([publish/2]).
-export([acknowledge/1]).
%% gen_server.
-export([init/1]).
-export([handle_call/3]).
-export([handle_cast/2]).
-export([handle_info/2]).
-export([terminate/2]).
-export([code_change/3]).
-record(state, {rabbit_conn, rabbit_chan, queues}).
-define(RABBIT_USERNAME, <<"guest">>).
-define(RABBIT_PASSWORD, <<"Hufton123">>).
-define(EXCHANGE_NAME, <<"worker_exchange">>).
-define(EXCHANGE_TYPE, <<"direct">>).
-define(QUEUE_NAMES, [<<"worker_queue">>]).
%% API.
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
%% [stak_worker_sup:spawn_worker() || _ <- lists:seq(1, 3)].
%% [pool_router:publish(<<"worker_queue">>, {<<"fibonacci">>, <<"fibonacci">>, [40]}) || _ <- lists:seq(1, 9)].
publish(QueueName, MFA) ->
gen_server:call(?MODULE, {publish, {QueueName, MFA}}).
acknowledge(Tag) ->
gen_server:call(?MODULE, {acknowledge, Tag}).
subscribe(QueueName) ->
gen_server:call(?MODULE, {subscribe, QueueName}).
unsubscribe(Tag) ->
gen_server:call(?MODULE, {unsubscribe, Tag}).
%% gen_server.
init([]) ->
RabbitParams=#amqp_params_network{username=?RABBIT_USERNAME,
password=?RABBIT_PASSWORD},
{ok, RabbitConn}=amqp_connection:start(RabbitParams),
{amqp_channel, {ok, RabbitChan}}={amqp_channel,
amqp_connection:open_channel(RabbitConn)},
Exchange=#'exchange.declare'{exchange=?EXCHANGE_NAME,
type=?EXCHANGE_TYPE,
auto_delete=false},
#'exchange.declare_ok'{}=amqp_channel:call(RabbitChan, Exchange),
InitQueueFn=fun(QueueName) ->
Queue=#'queue.declare'{queue=QueueName},
#'queue.declare_ok'{}=amqp_channel:call(RabbitChan, Queue),
Binding=#'queue.bind'{queue=Queue#'queue.declare'.queue,
exchange=?EXCHANGE_NAME,
routing_key=QueueName},
#'queue.bind_ok'{}=amqp_channel:call(RabbitChan, Binding),
Queue
end,
Queues=[{QueueName, InitQueueFn(QueueName)} || QueueName <- ?QUEUE_NAMES],
#'basic.qos_ok'{}=amqp_channel:call(RabbitChan, #'basic.qos'{prefetch_count=1}),
io:format("router started~n"),
{ok, #state{rabbit_conn=RabbitConn,
rabbit_chan=RabbitChan,
queues=Queues}}.
handle_call({publish, {QueueName, {Mod, Fn, Args}=MFA}}, {_From, _}, #state{rabbit_chan=RabbitChan}=State) ->
io:format("Publishing ~p~n", [MFA]),
Payload=jsx:encode([{<<"M">>, Mod},
{<<"F">>, Fn},
{<<"A">>, Args}]),
Publish=#'basic.publish'{exchange=?EXCHANGE_NAME,
routing_key=QueueName},
ok=amqp_channel:cast(RabbitChan, Publish, #amqp_msg{payload=Payload}),
{reply, ok, State};
handle_call({acknowledge, Tag}, {From, _}, #state{rabbit_chan=RabbitChan}=State) ->
ok=amqp_channel:cast(RabbitChan, #'basic.ack'{delivery_tag=Tag}),
io:format("~p [~p] acknowledged~n", [From, Tag]),
{reply, ok, State};
handle_call({subscribe, QueueName}, {From, _}, #state{queues=Queues, rabbit_chan=RabbitChan}=State) ->
io:format("~p subscribed~n", [From]),
{_, Queue}=proplists:lookup(QueueName, Queues), %% NB no error checking
#'basic.consume_ok'{consumer_tag=Tag}=amqp_channel:subscribe(RabbitChan, #'basic.consume'{queue=Queue#'queue.declare'.queue}, From),
{reply, {ok, Tag}, State};
handle_call({unsubscribe, Tag}, {From, _}, #state{rabbit_chan=RabbitChan}=State) ->
io:format("~p [~p] unsubscribed~n", [From, Tag]),
#'basic.cancel_ok'{}=amqp_channel:call(RabbitChan, #'basic.cancel'{consumer_tag=Tag}),
{reply, ok, State};
handle_call(_Request, _From, State) ->
{reply, ignored, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, State) ->
ok=amqp_channel:close(State#state.rabbit_chan),
ok=amqp_connection:close(State#state.rabbit_conn),
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%% internal functions
-module(stak_worker).
-behaviour(gen_server).
-include_lib("amqp_client/include/amqp_client.hrl").
%% API.
-export([start_link/0]).
-export([stop/1]).
%% gen_server.
-export([init/1]).
-export([handle_call/3]).
-export([handle_cast/2]).
-export([handle_info/2]).
-export([terminate/2]).
-export([code_change/3]).
-record(state, {rabbit_tag}).
-define(QUEUE_NAME, <<"worker_queue">>).
%% API.
%% {ok, Pid}=stak_worker:start_link().
%% stak_worker:stop(Pid).
start_link() ->
gen_server:start_link(?MODULE, [], []).
stop(Pid) ->
gen_server:cast(Pid, stop).
%% gen_server.
%% don't run request automatically
%% workers should subscribe to router on startup and unsubscribe on termination
%% router then routes messages to workers
init([]) ->
io:format("starting worker ~p~n", [self()]),
{ok, #state{}, 0}.
handle_call(_Request, _From, State) ->
{reply, ignored, State}.
handle_cast(stop, State) ->
{stop, normal, State};
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info({#'basic.deliver'{delivery_tag=Tag}, #amqp_msg{payload=Payload}=_Content}, State) ->
%% io:format("~p received ~p~n", [self(), Payload]),
Struct=jsx:decode(Payload),
{_, ModBin}=proplists:lookup(<<"M">>, Struct),
{_, FnBin}=proplists:lookup(<<"F">>, Struct),
{_, Args}=proplists:lookup(<<"A">>, Struct),
Mod=list_to_atom(binary_to_list(ModBin)),
Fn=list_to_atom(binary_to_list(FnBin)),
Mod:Fn(Args),
ok=pool_router:acknowledge(Tag),
{noreply, State};
handle_info(timeout, State) ->
%% io:format("~p subscribing~n", [self()]),
{ok, RabbitTag}=pool_router:subscribe(?QUEUE_NAME),
{noreply, State#state{rabbit_tag=RabbitTag}};
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, State) ->
io:format("~p shutting down~n", [self()]),
ok=pool_router:unsubscribe(State#state.rabbit_tag),
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
Avoid use of prefetch_count; it limits the number of un- ack'ed messages on a single channel to one; so if have a worker pool, thus will cause it to operate sequentially rather than in parallel (because every request sent to a worker is only ack'ed once the worker has completed; ie you have multiple un- ack'ed messages at a given point in time)

Erlang: OTP - properly passing children arguments; workers exiting with exceptions

I have a supervisor / worker tree organized as follows:
game_super -> {keyserver: worker, loginserver: super}
loginserver -> loginlistener: worker (socket pool)
The key server works okay. It just returns the string of random bytes it uses to construct login tokens. It is periodically replaced.
My login server use to work under R16. Then I upgraded to R18 because I wanted to use maps. IDK if that is the issue... I doubt it though. The other change I made is the reason I need maps was for the convenience of setting up children.
I have multiple redis servers, each with their own purpose. One is rate limiting. That's the only one operating right now. I call game_core:redis_init() in game_supervisor:init([]). It returns {ok,{#MapPids, #MapScriptKeys}. Where #MapScriptKeys values are tuples containing all Sha hashes of LUA scripts.
I'm getting exception exit: {shutdown,{failed_to_start_child,login,<0.178.0>}} Pids obviously change but that was the last result after calling game_supervisor:start_inshell().
I've inserted an io:format("~p\n",[State]), just before the {ok, State} in login_listener:init(State) and it prints out everything... once. Considering that's right before returning.... how does it fail?
The code is based on: http://learnyousomeerlang.com/buckets-of-sockets
Is this similar to: Erlang supervisor exception on starting worker , where the same name was reused for children?
Game Supervisor
-module(game_supervisor).
-behavior(supervisor).
-include("constants.hrl").
-export([start/0, start/1, start_inshell/0, init/1]).
start(Args) ->
spawn(fun() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, Args)
end).
start_inshell() ->
{ok, Pid} = supervisor:start_link({local, ?MODULE}, ?MODULE, []),
unlink(Pid).
start() -> start([]).
init([]) ->
{ok, RedisState} = game_core:redis_init(),
{ok, {
{one_for_one, 2, 10}, [ %more than 2 restarts w/i 10 seconds all killed
{key,
{key_server, start_link, []},
permanent,
10000,
worker,
[key_server]},
{login,
{login_server, start_link, [RedisState]},
permanent,
10000,
supervisor,
[login_server]}%,
% {game,
% {game_server, start_link, [RedisState]},
% permanent,
% 10000,
% supervisor,
% [game_server]}
]}}.
Login Server
-module(login_server).
-behavior(supervisor).
-include("constants.hrl").
-export([start_link/0, start_link/1, init/1, start_socket/0]).
start_link(Args) ->
spawn(fun() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, Args)
end).
start_link() -> start_link([]).
init({Pid,Scripts}) ->
process_flag(trap_exit, true),
{ok, Socket} = gen_tcp:listen(?PORT_LISTEN_LOGIN, [{active,once}, {reuseaddr, true}]),
%Construct socket pool
spawn_link(fun start_pool/0),
%Put all redis information for login clients here
RedisState = {maps:get(limiter,Pid),maps:get(limiter,Scripts)},
%Child specification
{ok, {
{simple_one_for_one, 2, 10}, [ %more than 2 restarts w/i 10 seconds all killed
{listener,
{login_listener, start_link, [{Socket, RedisState}]},
temporary,
1000,
worker,
[login_listener]}
]}}.
start_pool() ->
[start_socket() || _ <- lists:seq(1,32)],
ok.
start_socket() -> supervisor:start_child(?MODULE, []).
Login Handler
-module(login_listener).
-behavior(gen_server).
-include("constants.hrl").
-export([start_link/1]).
%required callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
start_link(State) ->
gen_server:start_link(?MODULE, State,[]).
init(State) ->
process_flag(trap_exit, true),
gen_server:cast(self(), accept),
{ok, State}.
handle_cast(accept, State) -> %Param(s): Msg, State
{Socket, RedisState} = State,
{ok, AcceptSocket} = gen_tcp:accept(Socket),
login_server:start_socket(),
{noreply, {AcceptSocket, RedisState}}.
handle_info({tcp, Socket, Str}, State) -> %Param(s): Info
%%Login
{_, {PidLimiter,{ShaLimiter}}} = State,
{ok, {ClientIP, _ }} = inet:peername(Socket),
{ok, Result} = game_core:rate_limit(PidLimiter, ShaLimiter, ClientIP),
ok = inet:setopts(Socket, [{active, once}]),
{noreply, State}.
%%Surpress warnings
handle_call(_, _, State) -> {noreply, State}. %Param(s): Request, From
terminate(_, _) -> ok. %Param(s): Reason, State
code_change(_, State,_) -> {ok, State}. %Param(s): OldVsn, Extra
Why are your supervisor start functions calling spawn/1? For game_supervisor, change this:
start(Args) ->
spawn(fun() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, Args)
end).
to this:
start(Args) ->
supervisor:start_link({local, ?MODULE}, ?MODULE, Args).
And for login_server, change this:
start_link(Args) ->
spawn(fun() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, Args)
end).
to this:
start_link(Args) ->
supervisor:start_link({local, ?MODULE}, ?MODULE, Args).

simple_one_for_one child can not start

I write some code to test simple_one_for_one supervisor, but it can not work, the code is:
-module(test_simple_one_for_one).
-behaviour(supervisor).
%% API
-export([start_link/0, start_fun_test/0]).
%% Supervisor callbacks
-export([init/1]).
-define(SERVER, ?MODULE).
%%--------------------------------------------------------------------
start_link() ->
{ok, Pid} = supervisor:start_link({local, ?SERVER}, ?MODULE, []).
start_fun_test() ->
supervisor:start_child(test_simple_one_for_one, []).
init([]) ->
RestartStrategy = simple_one_for_one,
MaxRestarts = 1000,
MaxSecondsBetweenRestarts = 3600,
SupFlags = {RestartStrategy, MaxRestarts, MaxSecondsBetweenRestarts},
Restart = permanent,
Shutdown = 2000,
Type = worker,
AChild = {fun_test_sup, {fun_test, run, []},
Restart, Shutdown, Type, [fun_test]},
io:format("start supervisor------ ~n"),
{ok, {SupFlags, [AChild]}}.
When I run
test_simple_one_for_one:start_link().
and
test_simple_one_for_one:start_fun_test().
in erl shell, it gives me error:
test_simple_one_for_one:start_fun_test().
** exception exit: {noproc,{gen_server,call,
[test_simple_one_for_one,{start_child,[]},infinity]}}
in function gen_server:call/3 (gen_server.erl, line 188)
If it's all the code you've written for the test, beware that when you register a supervisor child you suplly a {M, F, A} tuple which represent the function called when you start a child.
In you're case, I think it cannot simply find the fun_test:run/1 function.

Resources