RabbitMQ work queue is blocking consumers - erlang
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)
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),
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.
Erlang gen_server communication
If I have several instances of server, how can I pass info from one to another, for example: I have this: ... -record(id,{name,hash}). -record(state, {id ,m, succ, pred}). start(Name, M) -> gen_server:start_link({local, Name}, ?MODULE, {new_ring, Name, M}, []). join_ring(Name, Other) -> gen_server:start_link({local, Name}, ?MODULE, {join_ring, Name, Other}, []). ... init({new_ring, Name, M}) -> Me=#id{name=Name,hash=M} {ok, #state{ id = Me, m = M, succ = Me, pred = nil, }}; init({join_ring, Name, Other}) -> Me=#id{name=Name,hash=M} {ok, #state{ id = Me, m = Other, succ = Me, pred = nil, }}. Let say that I have two servers, one and two. How can I access from the server one info from the state of server two?
-module(wy). -compile(export_all). -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -behaviour(gen_server). -record(state, {id ,m, succ, pred}). start(Name, M) -> gen_server:start_link({local, Name}, ?MODULE, [Name, M], []). init([Name, M]) -> {ok, #state{id = Name, m = M}}. handle_call({get_server_info}, _Frome, State) -> {reply, State, State}; handle_call(_Request, _From, State) -> Reply = ok, {reply, Reply, State}. handle_cast(_Msg, State) -> {noreply, State}. handle_info(_Info, State) -> {noreply, State}. terminate(_Reason, _State) -> ok. code_change(_OldVsn, State, _Extra) -> {ok, State}. get_server_info(ServerName) -> gen_server:call(ServerName, {get_server_info}). I think you can refer this code sampe. You can give the server a name, for example server1 & server2. Then you use gen_server:call/2 to send the message to the server you wanted. just start two gen_serber: 4> wy:start(server1, server1). {ok,<0.50.0>} 5> wy:start(server2, server2). {ok,<0.52.0>} To get the state info from the server: 24> wy:get_server_info(server2). {state,server2,server2,undefined,undefined} 25> wy:get_server_info(server1). {state,server1,server1,undefined,undefined}
recv message and write to file
I have made a server here which receives messages from the terminal. What I want to do is when the server receives the messages from the terminal it should write it into a file. But I dont know how I should do this. I thought of writing the file methods file:open, file:write in the receive functions but it didnt work out as I thought. -module(tcp). -behaviour(gen_server). -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3, start/0, stop/0, connect/0, send/1, recv/0]). -export([start_link/0]). %% ============================================================================= %% EXPORTED GEN_SERVER CALLBACKS %% ============================================================================= init([]) -> {ok, {}}. handle_call({send, Packet}, _From, State) -> gen_tcp:send(State, Packet), io:format("Send Working\n"), {reply, ok, State}; handle_call(recv, _From, State) -> Message = gen_tcp:recv(State, 0), io:format("~w~n", [Message]), {reply, Message, State}. handle_cast(connect, _) -> case gen_tcp:listen(6888, [binary]) of {ok, LSocket}-> io:format("~w~n", [LSocket]), case gen_tcp:accept(LSocket) of {ok, Socket} -> inet:setopts(Socket, [{active, false}]), io:format("accepted\n"), {noreply, Socket}; Other -> error_logger:error_report(["An error occurred which is",Other,"in line",?LINE,"of module",?MODULE]) end; {error, Reason} -> error_logger:error_report("An error occurred", Reason, [?LINE,?MODULE]) end; handle_cast(stop, State) -> {stop, normal, State}. handle_info(_Info, State) -> {noreply, State}. terminate(_Reason, _State) -> ok. code_change(_OldVsn, State, _Extra) -> {ok, State}. %% ============================================================================= %% EXPORTED CONVENIENCE FUNCTIONS TO START AND STOP THE SERVER %% ============================================================================= start() -> case gen_server:start({local, ?MODULE}, ?MODULE, [], []) of {ok, Pid} -> Pid; Reason -> error_logger:error_report("An error occurred", Reason, [?LINE,?MODULE]) end. stop() -> case gen_server:cast(?MODULE, stop) of ok -> ok; _ -> {error, stop_error} end. %% ============================================================================= %% PUBLIC API FUNCTIONS %% ============================================================================= connect() -> gen_server:cast(?MODULE, connect). send(Packet) -> gen_server:call(?MODULE, {send, Packet}). recv() -> gen_server:call(?MODULE, recv). write_file(Filename) -> {ok, IoDevice} = file:open(test.txt, [write, append]), ok. start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). %% ============================================================================= %% LOCAL FUNCTIONS %% ============================================================================= I did as you suggested and implemented it as a handle_call but when i run i get an error, function handle_call/3 already defined error I quite dont understand why I get this error since i have 3 arguments for the handle_call and it should work. handle_call(write_file, Filename, S) -> {ok, File} = file:open(Filename, [append]), ok = file:write(File, S), ok = file:close(File). My API Function write_file() -> gen_server:call(?MODULE, write_file).
Your handle call doesn't work because you have two definitions of it. You have written: handle_call(...) -> do_something. handle_call(M, F, S) -> do_some_other_thing. You should make them into the same function, by changing: do_something. into: do_something; handle_call(M, F, S) -> do_some_other_thing.
write_file should do something like: write_file(Fname, S) -> ok = file:write_file(Fname, S, [append]). or write_file(Fname, S) -> {ok, File} = file:open(Fname, [append]), ok = file:write(File, S), ok = file:close(File).