gen_server implementation in Erlang R19 - erlang
I am copy a gen_server implementation code from Learn you some erlang for great good, and create a file named kitty_gen_server.erl. then I run following command in erlang shell:
$ erl
Erlang/OTP 19 [erts-8.0.2] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]
Eshell V8.0.2 (abort with ^G)
1> c(kitty_gen_server).
{ok,kitty_gen_server}
2> P = kitty_gen_server:start_link().
{ok,<0.64.0>}
3> kitty_gen_server:close_shop(P).
** exception exit: {{function_clause,
[{gen,do_for_proc,
[{ok,<0.64.0>},#Fun<gen.0.62797829>],
[{file,"gen.erl"},{line,253}]},
{gen_server,call,2,[{file,"gen_server.erl"},{line,200}]},
{erl_eval,do_apply,6,[{file,"erl_eval.erl"},{line,674}]},
{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}]}]},
{gen_server,call,[{ok,<0.64.0>},terminate]}}
in function gen_server:call/2 (gen_server.erl, line 204)
4>
I am sure the code is right download from learn you some erlang for greate good, But I don know why cannot excute.
My system is OS X 10.11, Erlang 19.
this following is code:
-module(kitty_gen_server).
-behaviour(gen_server).
-export([start_link/0, order_cat/4, return_cat/2, close_shop/1]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-record(cat, {name, color=green, description}).
%%% Client API
start_link() ->
gen_server:start_link(?MODULE, [], [{debug,[trace]}]).
%% Synchronous call
order_cat(Pid, Name, Color, Description) ->
gen_server:call(Pid, {order, Name, Color, Description}).
%% This call is asynchronous
return_cat(Pid, Cat = #cat{}) ->
gen_server:cast(Pid, {return, Cat}).
%% Synchronous call
close_shop(Pid) ->
gen_server:call(Pid, terminate).
%%% Server functions
init([]) -> {ok, []}. %% no treatment of info here!
handle_call({order, Name, Color, Description}, _From, Cats) ->
if Cats =:= [] ->
{reply, make_cat(Name, Color, Description), Cats};
Cats =/= [] ->
{reply, hd(Cats), tl(Cats)}
end;
handle_call(terminate, _From, Cats) ->
{stop, normal, ok, Cats}.
handle_cast({return, Cat = #cat{}}, Cats) ->
{noreply, [Cat|Cats]}.
handle_info(Msg, Cats) ->
io:format("Unexpected message: ~p~n",[Msg]),
{noreply, Cats}.
terminate(normal, Cats) ->
[io:format("~p was set free.~n",[C#cat.name]) || C <- Cats],
ok.
code_change(_OldVsn, State, _Extra) ->
%% No change planned. The function is there for the behaviour,
%% but will not be used. Only a version on the next
{ok, State}.
%%% Private functions
make_cat(Name, Col, Desc) ->
#cat{name=Name, color=Col, description=Desc}.
kitty_gen_server:start_link/0 returns a tuple {ok, Pid} on success which you're storing directly in P, so your next call passes {ok, Pid} to kitty_gen_server:close_shop/1 instead of just the Pid. You need to use pattern matching to only store the Pid in P:
1> c(kitty_gen_server).
{ok,kitty_gen_server}
2> {ok, P} = kitty_gen_server:start_link().
{ok,<0.64.0>}
3> kitty_gen_server:close_shop(P).
ok
Related
Is the mochiweb's reloader module is workable in the elixir's related project?
Because iex -S mix phx.server can't be run twice, port 4000's restriction. For erlang, mochiweb's reloader module can autoreload all module. reloader:start(). Is it possible for elixir to reload without' mix -S mix phx.server? mochiweb's reloader code is as follows: %% #copyright 2007 Mochi Media, Inc. %% #author Matthew Dempsky <matthew#mochimedia.com> %% %% #doc Erlang module for automatically reloading modified modules %% during development. -module(reloader). -author("Matthew Dempsky <matthew#mochimedia.com>"). -include_lib("kernel/include/file.hrl"). -behaviour(gen_server). -export([start/0, start_link/0]). -export([stop/0]). -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -export([all_changed/0]). -export([is_changed/1]). -export([reload_modules/1]). -record(state, {last, tref}). %% External API %% #spec start() -> ServerRet %% #doc Start the reloader. start() -> gen_server:start({local, ?MODULE}, ?MODULE, [], []). %% #spec start_link() -> ServerRet %% #doc Start the reloader. start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). %% #spec stop() -> ok %% #doc Stop the reloader. stop() -> gen_server:call(?MODULE, stop). %% gen_server callbacks %% #spec init([]) -> {ok, State} %% #doc gen_server init, opens the server in an initial state. init([]) -> {ok, TRef} = timer:send_interval(timer:seconds(1), doit), {ok, #state{last = stamp(), tref = TRef}}. %% #spec handle_call(Args, From, State) -> tuple() %% #doc gen_server callback. handle_call(stop, _From, State) -> {stop, shutdown, stopped, State}; handle_call(_Req, _From, State) -> {reply, {error, badrequest}, State}. %% #spec handle_cast(Cast, State) -> tuple() %% #doc gen_server callback. handle_cast(_Req, State) -> {noreply, State}. %% #spec handle_info(Info, State) -> tuple() %% #doc gen_server callback. handle_info(doit, State) -> Now = stamp(), _ = doit(State#state.last, Now), {noreply, State#state{last = Now}}; handle_info(_Info, State) -> {noreply, State}. %% #spec terminate(Reason, State) -> ok %% #doc gen_server termination callback. terminate(_Reason, State) -> {ok, cancel} = timer:cancel(State#state.tref), ok. %% #spec code_change(_OldVsn, State, _Extra) -> State %% #doc gen_server code_change callback (trivial). code_change(_Vsn, State, _Extra) -> {ok, State}. %% #spec reload_modules([atom()]) -> [{module, atom()} | {error, term()}] %% #doc code:purge/1 and code:load_file/1 the given list of modules in order, %% return the results of code:load_file/1. reload_modules(Modules) -> [begin code:purge(M), code:load_file(M) end || M <- Modules]. %% #spec all_changed() -> [atom()] %% #doc Return a list of beam modules that have changed. all_changed() -> [M || {M, Fn} <- code:all_loaded(), is_list(Fn), is_changed(M)]. %% #spec is_changed(atom()) -> boolean() %% #doc true if the loaded module is a beam with a vsn attribute %% and does not match the on-disk beam file, returns false otherwise. is_changed(M) -> try module_vsn(M:module_info()) =/= module_vsn(code:get_object_code(M)) catch _:_ -> false end. %% Internal API module_vsn({M, Beam, _Fn}) -> {ok, {M, Vsn}} = beam_lib:version(Beam), Vsn; module_vsn(L) when is_list(L) -> {_, Attrs} = lists:keyfind(attributes, 1, L), {_, Vsn} = lists:keyfind(vsn, 1, Attrs), Vsn. doit(From, To) -> [case file:read_file_info(Filename) of {ok, #file_info{mtime = Mtime}} when Mtime >= From, Mtime < To -> reload(Module); {ok, _} -> unmodified; {error, enoent} -> %% The Erlang compiler deletes existing .beam files if %% recompiling fails. Maybe it's worth spitting out a %% warning here, but I'd want to limit it to just once. gone; {error, Reason} -> io:format("Error reading ~s's file info: ~p~n", [Filename, Reason]), error end || {Module, Filename} <- code:all_loaded(), is_list(Filename)]. reload(Module) -> io:format("Reloading ~p ...", [Module]), code:purge(Module), case code:load_file(Module) of {module, Module} -> io:format(" ok.~n"), case erlang:function_exported(Module, test, 0) of true -> io:format(" - Calling ~p:test() ...", [Module]), case catch Module:test() of ok -> io:format(" ok.~n"), reload; Reason -> io:format(" fail: ~p.~n", [Reason]), reload_but_test_failed end; false -> reload end; {error, Reason} -> io:format(" fail: ~p.~n", [Reason]), error end. stamp() -> erlang:localtime(). %% %% Tests %% -ifdef(TEST). -include_lib("eunit/inclu de/eunit.hrl"). -endif. After test, :reloader.start() works for elixir module.
Testing a gen_server module using Common Test
I have this (very simple) gen_server implementation: -module(rand_gen). -behaviour(gen_server). -define(BASE, 1000). %% Module Functionality -export([start/0]). -export([stop/1]). -export([uniform/1, uniform/2]). %% Callback Functions -export([code_change/3]). -export([init/1]). -export([handle_call/3]). -export([handle_cast/2]). -export([handle_info/2]). -export([terminate/2]). %%%======================================================================= %%% Export %%%======================================================================= -spec start() -> {ok, pid()} | {error, Reason :: term()}. start() -> gen_server:start(?MODULE, [], []). -spec stop(Pid) -> ok when Pid :: pid(). stop(Pid) -> gen_server:stop(Pid). -spec uniform(Pid, Max) -> number() when Pid :: pid(), Max :: non_neg_integer(). uniform(Pid, Max) -> gen_server:call(Pid, {uniform, #{max => Max}}). -spec uniform(Pid) -> number() when Pid :: pid(). uniform(Pid) -> gen_server:call(Pid, uniform). %%%======================================================================= %%% Callback Functions %%%======================================================================= code_change(_OldVsn, State, _Extra) -> {ok, State}. handle_call({uniform, Input = #{max := Max}}, _From, _State) -> {reply, rand:uniform(Max), Input}; handle_call(uniform, _From, State = #{max := Max}) -> {reply, rand:uniform(Max), State}; handle_call(Msg, From, State) -> io:format("Unexpected call [~p] from [~p]~n", [Msg, From]), {reply, {error, no_call, Msg}, State}. handle_cast(Msg, State) -> io:format("Unexpected cast [~p]~n", [Msg]), {noreply, State}. handle_info(Msg, State) -> io:format("Unexpected message ~p~n", [Msg]), {noreply, State}. init([]) -> io:format("Crypto strong seed initialized! Using [~p] as base value.~n", [?BASE]), <<I1:32/unsigned-integer, I2:32/unsigned-integer, I3:32/unsigned-integer>> = crypto:strong_rand_bytes(?BASE), _ = rand:seed(exsplus, {I1, I2, I3}), {ok, #{max => ?BASE}}. terminate(_Reason, _StateData) -> ok. %%%======================================================================= %%% Internal %%%======================================================================= Now I want to write a Common Test suite for testing it ‒ I just started learning this. -module(rand_gen_SUITE). -include_lib("common_test/include/ct.hrl"). -compile(export_all). %%%======================================================================= %%% Setup %%%======================================================================= all() -> [ {group, generate} ]. groups() -> [ {generate, %%[parallel, {repeat, 3}], [uniform] } ]. init_per_suite(Config) -> {ok, Pid} = rand_gen:start(), [{pid, Pid} | Config]. end_per_suite(_Config) -> ok. %%%======================================================================= %%% Test Cases %%%======================================================================= uniform(Config) -> Pid = ?config(pid, Config), true = is_number(rand_gen:uniform(Pid)). ...but every time I run the (super-small) suite/file I get back a weird message saying: ===> Running Common Test suites... %%% rand_gen_SUITE ==> init_per_suite: FAILED %%% rand_gen_SUITE ==> {{badmatch,<<3,217,51,126,122,17,213,209,222,82,29,106,213,128,218,98,129,12, 123,22,27,194,123,120,74,110,1,254,132,243,147,87,181,74,43,159, 163,94,58,224,18,40,46,190,134,198,43,225,57,81,109,216,64,245, 103,131,46,47,234,165,210,2,78,97,52,44,131,49,0,94,168,207,176, 214,38,69,235,105,116,240,92,37,112,17,16,47,86,0,179,4,167,61, 61,167,88,84,227,247,74,132,86,64,33,92,175,51,130,242,155,174, 167,126,142,100,123,35,77,159,198,113,105,182,43,61,130,173,169, 155,135,0,6,19,90,65,139,70,236,217,253,76,223,86,228,175,145, 198,89,40,251,158,6,193,177,66,202,199,110,39,130,232,28,37,176, 215,45,251,65,220,229,124,204,99,126,132,119,41,83,226,235,117, 152,69,179,211,10,97,194,128,147,134,26,240,175,218,188,204,82, 55,34,35,49,195,183,78,150,125,137,141,76,92,253,1,46,203,37,142, 224,236,8,252,94,129,89,108,84,133,88,213,56,127,255,48,172,26, 246,51,29,79,56,33,59,122,135,80,137,63,37,124,168,90,76,46,12, 155,241,182,16,218,147,227,16,110,9,51,213,74,216,18,51,219,12, 111,229,159,231,33,12,87,114,134,113,79,147,35,122,227,114,154, 52,164,223,95,66,162,136,231,174,47,93,10,66,62,63,76,196,232, 100,240,49,16,122,81,200,222,66,180,212,16,185,117,31,111,152, 216,10,125,212,138,203,219,31,67,94,170,181,160,225,189,182,10, 110,207,197,177,198,199,83,227,121,235,12,186,90,212,160,102,187, 99,61,127,123,255,76,36,63,190,197,175,167,175,132,230,187,162, 68,183,220,9,198,29,18,191,199,182,10,227,139,38,25,113,153,199, 80,244,13,55,62,153,250,165,218,137,211,210,129,123,35,67,226,20, 153,85,25,193,46,53,40,49,134,67,92,198,106,151,195,242,46,153, 223,187,163,100,205,108,253,191,192,220,65,101,15,32,161,41,25, 148,203,153,134,55,33,88,107,222,49,120,44,173,167,82,238,185, 213,107,164,45,64,28,180,253,34,131,92,49,112,202,188,1,36,59,81, 223,0,200,93,34,253,3,214,240,211,51,135,43,70,105,98,125,186, 131,129,85,246,147,167,238,32,176,54,92,250,200,73,63,246,72,159, 64,105,119,246,58,5,153,54,126,4,33,82,153,245,79,163,187,61,45, 172,173,30,45,148,252,230,74,80,213,132,45,52,202,101,234,98,30, 68,18,117,175,231,41,153,243,243,132,184,98,234,72,134,140,229, 239,205,136,228,223,52,189,118,250,195,178,126,33,56,142,247,242, 235,160,129,75,221,73,21,148,139,110,194,87,127,147,9,71,25,111, 218,6,206,38,3,208,154,128,236,127,96,155,223,25,164,139,80,170, 82,230,130,118,88,198,254,109,249,252,146,143,6,108,134,49,29, 152,215,67,74,96,240,109,6,97,36,145,250,234,48,145,17,144,217, 198,236,60,68,213,30,7,120,228,117,41,231,72,208,245,73,153,251, 64,195,16,201,250,12,143,23,138,23,215,74,5,180,228,133,236,61, 227,123,188,100,81,77,254,187,69,213,127,22,38,143,84,184,4,47, 47,124,245,230,221,105,105,36,220,28,52,200,195,104,7,78,155,250, 36,8,42,228,166,212,106,147,247,101,21,230,78,236,91,217,115,84, 76,125,193,18,136,103,85,104,33,102,7,235,250,197,139,238,187, 154,12,84,152,174,133,92,165,72,76,45,70,90,115,32,161,156,251, 197,103,70,141,168,220,223,251,252,14,249,172,40,122,53,69,137, 45,29,129,144,146,238,181,104,28,117,166,19,238,44,162,174,117, 250,21,62,155,39,149,221,207,48,168,19,35,209,53,188,31,122,99, 83,74,138,164,103,72,106,27,175,239,240,3,100,118,152,180,225,82, 227,60,108,66,223,165,173,183,46,166,23,85,236,141,238,166,160, 114,31,7,14,23,220,90,226,79,217,203,240,126,128,241,41,149,224, 148,130,86,130,244,12,27,237,123,234,0,68,164,165,250,235,87,243, 217,253,215,249,13,206,155,197,138,192,249,154,104,131,109,229, 126,77,216,127,254,55,170,192,100,90,49,30,78,209,249,140,184,81, 121,164,178,50,204,131,144,214,8,12,130,32,164,149,92,200,235, 145,48,162,244,238,114,47,251,31,79,242,186,111,117,207,16,178, 212,234,209,161,143,21,241,111,218,32,255,191,70,177,203,5,117, 182,148,199,231,93,118,76,72>>}, [{rand_gen,init,1, [{file,"/home/x80486/Workshop/erlang/erlang_basics/_build/test/lib/erlang_basics/src/rand_gen.erl"}, {line,74}]}, {rand_gen_SUITE,init_per_suite,1, [{file,"/home/x80486/Workshop/erlang/erlang_basics/_build/test/lib/erlang_basics/test/rand_gen_SUITE.erl"}, {line,34}]}, {test_server,ts_tc,3,[{file,"test_server.erl"},{line,1546}]}, {test_server,run_test_case_eval1,6,[{file,"test_server.erl"},{line,1147}]}, {test_server,run_test_case_eval,9,[{file,"test_server.erl"},{line,994}]}]} Any clues? Thanks in advance!
That’s because the result of crypto:strong_rand_bytes(?BASE) doesn’t match <<I1:32/unsigned-integer, I2:32/unsigned-integer, I3:32/unsigned-integer>> since it’s (as you can see in the error) longer than that. You can try changing that line in your code to something like: <<I1:32/unsigned-integer, I2:32/unsigned-integer, I3:32/unsigned-integer, _/bitstring>> = crypto:strong_rand_bytes(?BASE),
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)