How to spawn a variable number of gen_servers in Erlang - 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.

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.

RabbitMQ work queue is blocking consumers

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

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

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

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