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).

Resources