Testing a gen_server module using Common Test - erlang
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),
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.
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 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).