cowboy_websocket:websocket_send argument problem - erlang

I am trying to use cowboy to send a notification to multiple clients using the socket connected to them. The problem is that I cannot find anything in the documentation about the argument to be passed to the function, the one I used in the code seems to be incorrect.
The socket is saved in a variable called Req that is given when a new client connects in the init function:
% Called to know how to dispatch a new connection.
init(Req, _Opts) ->
?LOG_INFO("New client"),
?LOG_DEBUG("Request: ~p", [Req]),
% "upgrade" every request to websocket,
% we're not interested in serving any other content.
Req2=Req,
{cowboy_websocket, Req, #state{socket = Req2}}.
The sockets are used in this way
send_message_to_sockets([Socket | Sockets], Msg) ->
cowboy_websocket:websocket_send(Socket, {text, Msg}),
send_message_to_sockets(Sockets, Msg).
This is the error:
Error in process <0.202.0> with exit value:
{undef,[{cowboy_websocket,websocket_send, [#{bindings => #{},...}
I have tried different argument to be passed to the function websocket_send but nothing worked.
Here is the code of the websocket_send:
transport_send(#state{socket=Stream={Pid, _}, transport=undefined}, IsFin, Data) ->
Pid ! {Stream, {data, IsFin, Data}},
ok;
transport_send(#state{socket=Socket, transport=Transport}, _, Data) ->
Transport:send(Socket, Data).
-spec websocket_send(cow_ws:frame(), #state{}) -> ok | stop | {error, atom()}.
websocket_send(Frames, State) when is_list(Frames) ->
websocket_send_many(Frames, State, []);
websocket_send(Frame, State) ->
Data = frame(Frame, State),
case is_close_frame(Frame) of
true ->
_ = transport_send(State, fin, Data),
stop;
false ->
transport_send(State, nofin, Data)
end.
websocket_send_many([], State, Acc) ->
transport_send(State, nofin, lists:reverse(Acc));
websocket_send_many([Frame|Tail], State, Acc0) ->
Acc = [frame(Frame, State)|Acc0],
case is_close_frame(Frame) of
true ->
_ = transport_send(State, fin, lists:reverse(Acc)),
stop;
false ->
websocket_send_many(Tail, State, Acc)
end.

The socket is saved in a variable called Req that is given when a new
client connects in the init function:
init(Req, _Opts) ->
?LOG_INFO("New client"),
?LOG_DEBUG("Request: ~p", [Req]),
% "upgrade" every request to websocket,
% we're not interested in serving any other content.
Req2=Req,
{cowboy_websocket, Req, #state{socket = Req2}}.
I don't think that is true. In Nine Nine's Cowboy User Guide, there is a section titled Getting Started, which shows this code:
Handling requests
...
...
init(Req0, State) ->
Req = cowboy_req:reply(200,
#{<<"content-type">> => <<"text/plain">>},
<<"Hello Erlang!">>,
Req0),
{ok, Req, State}.
In that code, init() is passed a map that is assigned/bound to the Req0 variable. You can read about the Request map here:
https://ninenines.eu/docs/en/cowboy/2.9/guide/req/
The map contains the usual HTTP Request information, e.g. the HTTP Request method, the HTTP version number, scheme, host, port, path, etc. Then the docs say:
Any other field is internal and should not be accessed.
Generally, a socket is defined like this:
A socket is one endpoint of a two-way communication link between two
programs running on the network.
And:
An endpoint is a combination of an IP address and a port number.
https://docs.oracle.com/javase/tutorial/networking/sockets/definition.html
In this line:
cowboy_websocket:websocket_send(Socket, {text, Msg}),
websocket_send() is defined to take a cow_ws:frame() type as the first argument:
-spec websocket_send(cow_ws:frame(), #state{}) -> ok | stop | {error, atom()}.
websocket_send(Frames, State) when is_list(Frames) ->
In the cow_ws module, the frame() type is defined like this:
frame() :: {text, iodata()}
| {binary, iodata()}
| ping | {ping, iodata()}
| pong | {pong, iodata()}
| close | {close, iodata()} | {close, close_code(), iodata()}
...which is a tuple with the first element being an atom and possibly a second element being an iodata() type, which is a built in erlang type that is a binary or a list (containing integers, binaries and other lists), and it is defined here:
I'm not sure how you go from a Request map to a two-tuple.

Related

How to use efficiently receive clause in erlang gen_server to resolve timeout error?

Sometimes my loop returns ok because of timeout how to write this code in proper way. When there is a timeout it just returns ok but not my actual value that I am assuming. In handle call I am calling a function loop() in the loop() function i am receiving a message with receive clause. Now I am sending this data to my database using loop2 function returns response from database whether data has been successfully saved or not and giving response back to loop(). But if there is a timeout my loop function returns ok but not actual value.
% #Author: ZEESHAN AHMAD
% #Date: 2020-12-22 05:06:12
% #Last Modified by: ZEESHAN AHMAD
% #Last Modified time: 2021-01-10 04:42:59
-module(getAccDataCons).
-behaviour(gen_server).
-include_lib("deps/amqp_client/include/amqp_client.hrl").
-export([start_link/0, stop/0]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, code_change/3,
terminate/2]).
-export([get_account/0]).
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
stop() ->
gen_server:cast(?MODULE, stop).
get_account() ->
gen_server:call(?MODULE, {get_account}).
init(_Args) ->
{ok, Connection} = amqp_connection:start(#amqp_params_network{host = "localhost"}),
{ok, Channel} = amqp_connection:open_channel(Connection),
{ok, Channel}.
handle_call({get_account}, _From, State) ->
amqp_channel:call(State, #'exchange.declare'{exchange = <<"get">>, type = <<"topic">>}),
amqp_channel:call(State, #'queue.declare'{queue = <<"get_account">>}),
Binding =
#'queue.bind'{exchange = <<"get">>,
routing_key = <<"get.account">>,
queue = <<"get_account">>},
#'queue.bind_ok'{} = amqp_channel:call(State, Binding),
io:format(" [*] Waiting for logs. To exit press CTRL+C~n"),
amqp_channel:call(State,#'basic.consume'{queue = <<"get_account">>, no_ack = true}),
Returned =loop(),
io:format("~nReti=~p",[Returned]),
{reply, Returned, State};
handle_call(Message, _From, State) ->
io:format("received other handle_call message: ~p~n", [Message]),
{reply, ok, State}.
handle_cast(stop, State) ->
{stop, normal, State};
handle_cast(Message, State) ->
io:format("received other handle_cast call : ~p~n", [Message]),
{noreply, State}.
handle_info(Message, State) ->
io:format("received handle_info message : ~p~n", [Message]),
{noreply, State}.
code_change(_OldVer, State, _Extra) ->
{ok, State}.
terminate(Reason, _State) ->
io:format("server is terminating with reason :~p~n", [Reason]).
loop()->
receive
#'basic.consume_ok'{} -> ok
end,
receive
{#'basic.deliver'{}, Msg} ->
#amqp_msg{payload = Payload} = Msg,
Value=loop2(Payload),
Value
after 2000->
io:format("Server timeout")
end.
loop2(Payload)->
Result = jiffy:decode(Payload),
{[{<<"account_id">>, AccountId}]} = Result,
Doc = {[{<<"account_id">>, AccountId}]},
getAccDataDb:create_AccountId_view(),
Returned=case getAccDataDb:getAccountNameDetails(Doc) of
success ->
Respo = getAccDataDb:getAccountNameDetails1(Doc),
Respo;
details_not_matched ->
user_not_exist
end,
Returned.
This is too long for an edit, I put it in a new answer.
The reason why you receive ok when a timeout occurs is in the loop() code. In the second receive block, after 2000 ms, you return
immediately after the io:format/1 statement.
io:format returns ok and it is what you get in the Returned variable. You should change this code with
loop()->
ok = receive
#'basic.consume_ok'{} -> ok
end,
receive
{#'basic.deliver'{}, #amqp_msg{payload = Payload}} -> {ok,loop2(Payload)}
after 2000 ->
io:format("Server timeout"),
{error,timeout}
end.
With this code your client will receive either {ok,Value}, either {error,timeout} and will be able to react accordingly.
But there are still issues with this version:
- the 2 seconds timeout is maybe too short and you are missing valid answer
- as you are using pattern matching in the receive blocks and do not check the result of each amqp_channel:call there are many different problems that could occur and appear as a timeout
First lets have a look at the timeout. It is possible that the 4 calls to amqp_channel really need more than 2 seconds in total to complete successfully. The simple solution is to increase your timeout, changing after 2000 to after 3000 or more.
But then you will have 2 issues:
Your gen_server is blocked during all this time, and if it is not dedicated to a single client, it will be unavailable to
serve any other request while it is waiting for the answer.
If you need to increase the timeout above 5 second, you will hit another timeout, managed internally by the gen_server: a request must be answered in less than 5 seconds.
The gen_server offers some interface functions to solve this kind of problem: 'send_request', 'wait_response' and reply. Here is a basic
gen_server which can handle 3 kind of requests:
stop ... to stop the server, useful to update the code.
{blocking,Time,Value} the server will sleep during Time ms end then return Value. This simulates your case, and you can tweak how
long it takes to get an answer.
{non_blocking,Time,Value} the server will delegate the job to another process and return immediately without answer (therefore
it is available for another request). the new process will sleep during Time ms end then return Value using gen_server:reply.
The server module implements several user interfaces:
the standard start(), stop()
blocking(Time,Value) to call the server with the request {blocking,Time,Value} using gen_server:call
blocking_catch(Time,Value) same as the previous one, but catching the result of gen_server:call to show the hidden timeout
non_blocking(Time,Value,Wait) to call the server with the request {non_blocking,Time,Value} using gen_server:send_request and waiting for the answer for Wait ms maximum
Finally it includes 2 test functions
test([Type,Time,Value,OptionalWait]) it spawns a process which will send a reqest of type with the corresponding parameters. The answer is sent back to the calling process. The answer can be retreive with flush() in the shell.
parallel_test ([Type,Time,NbRequests,OptionalWait]) it calls NbRequests times test with the corresponding parameters. It collects all
the answers and print them using the local function collect(NbRequests,Timeout).
Code below
-module (server_test).
-behaviour(gen_server).
%% API
-export([start/0,stop/0,blocking/2,blocking_catch/2,non_blocking/3,test/1,parallel_test/1]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-define(SERVER, ?MODULE).
%%%===================================================================
%%% API
%%%===================================================================
start() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
stop() ->
gen_server:cast(?SERVER, stop).
blocking(Time,Value) ->
gen_server:call(?SERVER, {blocking,Time,Value}).
blocking_catch(Time,Value) ->
catch {ok,gen_server:call(?SERVER, {blocking,Time,Value})}.
non_blocking(Time,Value,Wait) ->
ReqId = gen_server:send_request(?SERVER,{non_blocking,Time,Value}),
gen_server:wait_response(ReqId,Wait).
test([Type,Time,Value]) -> test([Type,Time,Value,5000]);
test([Type,Time,Value,Wait]) ->
Start = erlang:monotonic_time(),
From = self(),
F = fun() ->
R = case Type of
non_blocking -> ?MODULE:Type(Time,Value,Wait);
_ -> ?MODULE:Type(Time,Value)
end,
From ! {request,Type,Time,Value,got_answer,R,after_microsec,erlang:monotonic_time() - Start}
end,
spawn(F).
parallel_test([Type,Time,NbRequests]) -> parallel_test([Type,Time,NbRequests,5000]);
parallel_test([Type,Time,NbRequests,Wait]) ->
case Type of
non_blocking -> [server_test:test([Type,Time,X,Wait]) || X <- lists:seq(1,NbRequests)];
_ -> [server_test:test([Type,Time,X]) || X <- lists:seq(1,NbRequests)]
end,
collect_answers(NbRequests,Time + 1000).
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
init([]) ->
{ok, #{}}.
handle_call({blocking,Time,Value}, _From, State) ->
timer:sleep(Time),
Reply = {ok,Value},
{reply, Reply, State};
handle_call({non_blocking,Time,Value}, From, State) ->
F = fun() ->
do_answer(From,Time,Value)
end,
spawn(F),
{noreply, State};
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.
handle_cast(stop, State) ->
{stop,stopped, State};
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(OldVsn, State, _Extra) ->
io:format("changing code replacing version ~p~n",[OldVsn]),
{ok, State}.
%%%===================================================================
%%% Internal functions
%%%===================================================================
do_answer(From,Time,Value) ->
timer:sleep(Time),
gen_server:reply(From, Value).
collect_answers(0,_Timeout) ->
got_all_answers;
collect_answers(NbRequests,Timeout) ->
receive
A -> io:format("~p~n",[A]),
collect_answers(NbRequests - 1, Timeout)
after Timeout ->
missing_answers
end.
Session in the shell:
44> c(server_test).
{ok,server_test}
45> server_test:start().
{ok,<0.338.0>}
46> server_test:parallel_test([blocking,200,3]).
{request,blocking,200,1,got_answer,{ok,1},after_microsec,207872}
{request,blocking,200,2,got_answer,{ok,2},after_microsec,415743}
{request,blocking,200,3,got_answer,{ok,3},after_microsec,623615}
got_all_answers
47> % 3 blocking requests in parallel, each lasting 200ms, they are executed in sequence but no timemout is reached
47> % All the clients get their answers
47> server_test:parallel_test([blocking,2000,3]).
{request,blocking,2000,1,got_answer,{ok,1},after_microsec,2063358}
{request,blocking,2000,2,got_answer,{ok,2},after_microsec,4127740}
missing_answers
48> % 3 blocking requests in parallel, each lasting 2000ms, they are executed in sequence and the last answer exceeds the gen_server timeout.
48> % The client for this request don't receive answer. The client should also manage its own timeout to handle this case
48> server_test:parallel_test([blocking_catch,2000,3]).
{request,blocking_catch,2000,1,got_answer,{ok,1},after_microsec,2063358}
{request,blocking_catch,2000,2,got_answer,{ok,2},after_microsec,4127740}
{request,blocking_catch,2000,3,got_answer,
{'EXIT',{timeout,{gen_server,call,[server_test,{blocking,2000,3}]}}},
after_microsec,5135355}
got_all_answers
49> % same thing but catching the exception. After 5 seconds the gen_server call throws a timeout exception.
49> % The information can be forwarded to the client
49> server_test:parallel_test([non_blocking,200,3]).
{request,non_blocking,200,1,got_answer,{reply,1},after_microsec,207872}
{request,non_blocking,200,2,got_answer,{reply,2},after_microsec,207872}
{request,non_blocking,200,3,got_answer,{reply,3},after_microsec,207872}
got_all_answers
50> % using non blocking mechanism, we can see that all the requests were managed in parallel
50> server_test:parallel_test([non_blocking,5100,3]).
{request,non_blocking,5100,1,got_answer,timeout,after_microsec,5136379}
{request,non_blocking,5100,2,got_answer,timeout,after_microsec,5136379}
{request,non_blocking,5100,3,got_answer,timeout,after_microsec,5136379}
got_all_answers
51> % if we increase the answer delay above 5000ms, all requests fail in default timeout
51> server_test:parallel_test([non_blocking,5100,3,6000]).
{request,non_blocking,5100,1,got_answer,{reply,1},after_microsec,5231611}
{request,non_blocking,5100,2,got_answer,{reply,2},after_microsec,5231611}
{request,non_blocking,5100,3,got_answer,{reply,3},after_microsec,5231611}
got_all_answers
52> % but thanks to the send_request/wait_response/reply interfaces, the client can adjust the timeout to an accurate value
52> % for each request
The next reason why the request could not complete is that one of the amqp_channel:call fails. Depending on what you want to do, there are several
possibilities from doing nothing, let crash, catch the exception or manage all cases. the next proposal uses a global catch
handle_call({get_account,Timeout}, From, State) ->
F = fun() ->
do_get_account(From,State,Timeout)
end,
spawn(F), % delegate the job to another process and free the server
{noreply, State}; % I don't see any change of State in your code, this should be enough
...
do_get_account(From,State,Timeout) ->
% this block of code asserts all positive return values from amqp_channel calls. it will catch any error
% and return it as {error,...}. If everything goes well it return {ok,Answer}
Reply = try
ok = amqp_channel:call(State, #'exchange.declare'{exchange = <<"get">>, type = <<"topic">>}),
ok = amqp_channel:call(State, #'queue.declare'{queue = <<"get_account">>}),
Binding = #'queue.bind'{exchange = <<"get">>,
routing_key = <<"get.account">>,
queue = <<"get_account">>},
#'queue.bind_ok'{} = amqp_channel:call(State, Binding),
ok = amqp_channel:call(State,#'basic.consume'{queue = <<"get_account">>, no_ack = true}),
{ok,wait_account_reply(Timeout)}
catch
Class:Exception -> {error,Class,Exception}
end,
gen_server:reply(From, Reply).
wait_account_reply(Timeout) ->
receive
% #'basic.consume_ok'{} -> ok % you do not handle this message, ignore it since it will be garbaged when the process die
{#'basic.deliver'{}, #amqp_msg{payload = Payload}} -> extract_account(Payload)
after Timeout->
server_timeout
end.
extract_account(Payload)->
{[{<<"account_id">>, AccountId}]} = jiffy:decode(Payload),
Doc = {[{<<"account_id">>, AccountId}]},
getAccDataDb:create_AccountId_view(), % What is the effect of this function, what is the return value?
case getAccDataDb:getAccountNameDetails(Doc) of
success ->
getAccDataDb:getAccountNameDetails1(Doc);
details_not_matched ->
user_not_exist
end.
And the client should looks like:
get_account() ->
ReqId = gen_server:send_request(server_name,{get_account,2000}),
gen_server:wait_response(ReqId,2200).
Without the loop and loop2 code, it is hard to give an answer, and if the timeout is detected by one of these 2 functions, you must first change their behavior to avoid any timeout, or increase it to a value that works. If a timeout is necessary, then ensure that the return value is explicit wet it occurs, for example {error,RequestRef,timeout} rather than ok.
Nevertheless the gen_server should not wait too long for an answer, you can modify your code doing:
Instead of using gen_server:call(ServerRef,Request) in the client process, you could use:
RequestId = send_request(ServerRef, Request),
Result = wait_response(RequestId, Timeout),
And remove the timeout in loop and/or loop2. Doing this you can control the timeout on the client side, you can even set it to infinity (not a good idea!).
Or you can split your function in two parts
gen_server:cast(ServerRef,{Request,RequestRef}),
% this will not wait for any answer, RequestRef is a tag to identify later
% if the request was fulfilled, you can use make_ref() to generate it
and later, or in another client process (this need to pass at least the RequestRef to this process) Check the result for request:
Answer = gen_server:call(ServerRef,{get_answer,RequestRef}),
case Answer of
no_reply -> ... % no answer yet
{ok,Reply} -> ... % handle the answer
end,
finally you must modify the loop code to handle the RequestRef, send back a message (using again gen_server:cast) to the server with the result and the RequestRef, and store this result in the server state.
I don't think this second solution is valuable since it is more or less the same than the first one, but hand made, and it let you to manage many error cases (such as client death) that could end into a kind of memory leak.

Erlang: How to properly dispatch a gen_server with start_child in a supervisor and call the API

I have a gen_server in my cavv application that I need to start first to execute a call to. I want to use a command dispatcher for this. For a short example, this it the gen_server's API:
a gen_server: cavv_user
-module(cavv_user).
-behavior(gen_server).
-define(SERVER(UserId), {via, gproc, {n, l, {?MODULE, UserId}}}).
start_link(UserId) ->
gen_server:start_link(?SERVER(UserId), ?MODULE, [UserId], []).
change_email_address(UserId, EmailAddress) ->
gen_server:call(?SERVER(AggregateId), {execute_command, #change_user_email_address{user_id=UserId, email_address=EmailAddress}}).
Before I can call cavv_user:change_email_address(). I need to start the cavv_user. I do this is as a simple_one_for_one child in a supervisor, like so:
a supervisor: cavv_user_sup
-module(cavv_user_sup).
-behaviour(supervisor).
-define(CHILD(ChildName, Type, Args), {ChildName, {ChildName, start_link, Args}, temporary, 5000, Type, [ChildName]}).
start_link() ->
supervisor:start_link({local, ?SERVER}, ?MODULE, []).
start_child(UserId) ->
supervisor:start_child(?SERVER, [UserId]).
init([]) ->
RestartStrategy = {simple_one_for_one, 1, 5},
Children = [?CHILD(cavv_user, worker, [])],
{ok, { RestartStrategy, Children} }.
The problem I am now facing is how to dispatch commands to a cavv_user. I want to make sure the proper user is started first using start_child, and then call the cavv_user:change_email_address().
I have found this anwser, to use a dispatcher: Erlang: what supervision tree should I end with writing a task scheduler?
So I created a command dispatcher and end up with a cavv_user_dispatcher and a cavv_user_dispatcher_sup that in turn contains the cavv_user_dispatcher and the earlier cavv_user_sup:
cavv_user_dispatch_sup
| |
cavv_user_dispatcher |
(gen_server) |
|
|
cavv_user_sup
| | |
cavv_user_1...cavv_user_N
The cavv_user_dispatcher
This works beautifully.
The problem I am facing now is, how do I properly write the code in cavv_user_dispatcher? I am facing a problem with code duplication. How to properly call start_child and call the appropriate API of cavv_user?
Should I use some kind of Fun like so?
-module(cavv_user_dispatcher).
dispatch_command(UserId, Fun) ->
gen_server:call(?SERVER, {dispatch_command, {UserId, Fun}}).
handle_call({dispatch_command, {UserId, Fun}}, _From, State) ->
cavv_user_sup:start_child(UserId),
Fun(), %% How to pass: cavv_user:change_email_address(..,..)?
{reply, ok, State};
Or duplicate the cavv_user's API like so?
-module(cavv_user_dispatcher).
change_user_email_address(UserId, EmailAddress) ->
gen_server:call(?SERVER, {change_user_email_address, {UserId, EmailAddress}}).
handle_call({change_user_email_address, {UserId, EmailAddress}}, _From, State) ->
cavv_user_sup:start_child(UserId),
cavv_user:change_email_address(UserId, EmailAddress),
{reply, ok, State};
Or should I re-use the command records from cavv_user into some kind of util to properly build them and pass them around? Maybe some better way to pass the function I want to call at cavv_user?
I would like to solve the problem in the best Erlang way as possible, without code duplication.
Is your dispatcher supposed to handle other commands?
If yes then then how will the next command will come, I mean will the requester know the process pid of the user or not?
if yes then you need 2 functions, one to create a user, it will return the pid to the requester for next call, and one to handle next requests by sending the command to the given pid
if no, then you need also 2 functions, one to create the a user and store the user_id along with the user process pid and one to handle next request by retrieving the process pid and then forward it the command (I suppose this is what you want to do).
if no then you don't need to handle any command and should pass directly the email address when creating the user process. Note that this is true for all cases since you need a different interface to create a user.
I would modify your code this way (not tested, it is too late :o) !)
-module(cavv_user_dispatcher).
create_user(UserId,UserMail) ->
gen_server:call(?SERVER,{new_user,UserId,UserMail}).
% Args is a list of argument, empty if
% F needs only one argument (the user Pid)
dispatch_command(UserId, Fun, Args) ->
gen_server:call(?SERVER, {dispatch_command, {UserId, Fun,Args}}).
handle_call({dispatch_command, {UserId, Fun,Args}}, _From, State) ->
Pid = get_pid(UserId,State),
Answer = case Pid of
unknown_user_id -> unknown_user_id;
_ -> apply(Fun,[Pid|Args]),
ok
end,
{reply, Answer, State};
handle_call({new_user,UserId,UserMail},_From,State) ->
% verify that the user id does not already exists
CheckId = check_id(UserId,State),
{Answer,NewState} = case CheckId of
false -> {already_exist,State};
true -> {ok,Pid} = cavv_user_sup:start_child(UserId,UserMail)
{ok,[{UserId,Pid}|State]}
% State must be initialized as an empty list in the init function.
{reply, Answer, NewState};
...
get_pid(UserId,State) ->
proplists:get_value(UserId, State, unknown_user_id).
check_id(UserId,State) ->
not proplists:is_defined(UserId, State).
and the user supervisor mus be modified this way:
start_child(UserId,UserMail) -> % change arity in the export
supervisor:start_child(?SERVER, [UserId,UserMail]).
and then the user server:
start_link(UserId,UserMail) ->
gen_server:start_link(?SERVER(UserId), ?MODULE, [UserId,UserMail],[]).
init([UserId,UserMail]) ->
{ok,[{user_id,UserId},{user_mail,UserMail}]}.

Erlang: Chat server crashing on startup

I got a set of tests that the program should pass and all the local tests works just fine with my server it's when I try to run the remote tests that the server crashes.
The crash message is the following:
=ERROR REPORT==== 23-Jul-2015::23:59:17 === Error in process <0.39.0> on
node 'nodeS#127.0.0.1' with exit value:
{undef,[{genserver,start,[server, {server_st,[],[]},#Fun<server.loop.2>],[]}]}
My start-up function looks as following:
loop(St, {From, Nick, connection_wanted}) ->
case lists:keymember(Nick, 2, St#server_st.users) of
false -> {ok, St#server_st{users = St#server_st.users ++ [{From, Nick}]}};
true -> {{user_already_connected, St}, St}
end;
With the record "server_st" is defined as:
-record(server_st, {users = [], channels = []}).
Finally the genserver start&loop function is:
start(Name, State, F) ->
Pid = spawn(fun() -> loop(State, F) end),
register(Name, Pid),
Pid.
loop(State, F) ->
receive
{request, From, Ref, Data} ->
case catch(F(State, Data)) of
{'EXIT', Reason} ->
From!{exit, Ref, Reason},
loop(State, F);
{R, NewState} ->
From!{result, Ref, R},
loop(NewState, F)
end;
{update, From, Ref, NewF} ->
From ! {ok, Ref},
loop(State, NewF);
stop ->
true
end.
Then genserver functions I'm not allowed to change. If needed I can post the whole testsuite too.
Edit
Digging a bit further into the test cases and I'm unsure if it really is the server that's causing the issue, my remote connect function looks as following:
loop(St, {connect, {_Server, _Machine}}) ->
ServerPID = {list_to_atom(_Server), list_to_atom(_Machine)},
case genserver:request(ServerPID, {self(), St#cl_st.nick, connection_wanted}) of
ok -> {ok, St#cl_st{connected_to = ServerPID}};
_ -> {{error, user_already_connected, "A user with the nick " ++ St#cl_st.nick ++ "is already connected to" ++ _Server}, St}
end;
Edit 2
Found the specific row inside the testsuite that's causing the error:
-define(HOST, '127.0.0.1').
new_client(Nick, GUIName) ->
ClientName = test_client:find_unique_name("client_"),
ClientAtom = list_to_atom(ClientName),
% Row below is causing the error
Result = slave:start(?HOST, ClientAtom),
assert_ok("start client node "++ClientName, element(1,Result)),
ClientNode = element(2,Result),
InitState = client:initial_state(Nick, GUIName),
Result2 = spawn(ClientNode, genserver, start, [ClientAtom, InitState, fun client:loop/2]),
assert("client startup "++ClientName, is_pid(Result2)),
{Nick, ClientAtom, ClientNode}.
Your function genserver:start/3 is most probably not exported or module genserver is not available at the node where you run code which calls it.
Solved it, was in a completely unrelated part where the client is communicating with other users. Still used the whereis command to locate other users from an older version of the program.

Erlang: push and pull from a shared queue

I need to maintain a shared queue where I can push data and a separate thread will periodically check and pull data from the queue if its not empty. I came up with the below solution where I can send data to the process and it adds up to a list. However, is there a cleaner / easier solution to do this ?
And im not sure how I may pull data from the below code.
-module(abc).
-export(queue/0).
queue() ->
receive
{push, Xmpp} ->
io:format("Push"),
queue(Xmpp);
{pull} ->
io:format("pull"),
queue()
end.
queue(E) ->
receive
{push, Xmpp} ->
io:format("Push ~w",[E]),
E1 = lists:append([E],[Xmpp]),
queue(E1);
{reset} ->
queue([])
end.
The code probably won't do exactly what you want as written. When you call queue/1 from the receive block ( queue(Xmpp); in line 7), queue/1 will fire and then wait for a message. Because this is not spawned in a separate process, queue/0 will block (since queue/1 is now waiting for a message that is never sent).
Also, queue/0 won't ever send anything back to the process sending it messages. It has no way of returning data to the sender.
The following will work (you will need to send messages to the pid returned by queue/0).
-module(abc).
-export([queue/0,queue/1]).
queue() ->
%% initialize an empty queue,
%% return the Pid to the caller
spawn(abc,queue,[[]]).
queue(E) when is_list(E) ->
receive
%% append the message value to the existing list
{push, Xmpp} ->
io:format("Pushing ~w to ~w~n",[Xmpp,E]),
E1 = lists:append(E,[Xmpp]),
queue(E1);
%% reset the queue
{reset} ->
queue([]);
%% return the value to the caller
%% "Pid" must be a Pid
{pull, Pid} when is_pid(Pid) ->
io:format("pull~n"),
Pid ! E,
queue(E)
end.
That's a problem with a straightforward solution in Erlang. Most of the time every erlang module you will be programming will be like a server, which will expect messages and will answer, you can have 0, 1 or mulitple servers running the same erlang module code. At the same time, you will be programming in the same module a client, which is an easy way to send messages to the server without having to know all the message format the server expect, instead you use functions, so instead of doing stuff like
Server ! {put, Value},
receive
{Server, {ok, Value}} ->
everything_ok;
{Server, {error, Reason}} ->
handle_error
end,
you end doing something like
my_module:put(Server, Value).
So you can create a server process in erlang with the code:
-module(abc).
-export([start/0, put/2, pop/1, reset/1, is_empty/1, loop/1]).
%% Client
start() ->
spawn(?MODULE, loop, [[]]).
put(Pid, Item) ->
Pid ! {self(), {put, Item}},
receive
{Pid, {ok, Item}} ->
Item;
{Pid, {error, Reason}} ->
{error, Reason}
after 500 ->
timeout
end.
pop(Pid) ->
Pid ! {self(), {pop}},
receive
{Pid, {ok, Item}} ->
Item;
{Pid, {error, Reason}} ->
{error, Reason}
after 500 ->
timeout
end.
reset(Pid) ->
Pid ! {self(), {reset}},
receive
{Pid, {ok}} ->
ok;
_ ->
error
after 500 ->
timeout
end.
is_empty(Pid) ->
Pid ! {self(), {is_empty}},
receive
{Pid, {true}} ->
true;
{Pid, {false}} ->
false;
_ ->
error
after 500 ->
timeout
end.
%% Server
loop(Queue) ->
receive
{From, {put, Item}} when is_pid(From) ->
From ! {self(), {ok, Item}},
loop(Queue ++ [Item]);
{From, {pop}} when is_pid(From) ->
case Queue of
[] ->
From ! {self(), {error, empty}},
loop(Queue);
[H|T] ->
From ! {self(), {ok, H}},
loop(T)
end;
{From, {reset}} when is_pid(From) ->
From ! {self(), {ok}},
loop([]);
{From, {is_empty}} when is_pid(From) ->
case Queue of
[] ->
From ! {self(), {true}},
loop(Queue);
_ ->
From ! {self(), {false}},
loop(Queue)
end;
_ ->
loop(Queue)
end.
and the code you end writting to use is simple as well:
(emacs#rorra)1> c("/Users/rorra/abc", [{outdir, "/Users/rorra/"}]).
{ok,abc}
(emacs#rorra)2> Q = abc:start().
<0.44.0>
(emacs#rorra)3> abc:is_empty(Q).
true
(emacs#rorra)4> abc:pop(Q).
{error,empty}
(emacs#rorra)5> abc:put(Q, 23).
23
(emacs#rorra)6> abc:is_empty(Q).
false
(emacs#rorra)7> abc:pop(Q).
23
(emacs#rorra)8> abc:pop(Q).
{error,empty}
(emacs#rorra)9> abc:put(Q, 23).
23
(emacs#rorra)10> abc:put(Q, 50).
50
(emacs#rorra)11> abc:reset(Q).
ok
(emacs#rorra)12> abc:is_empty(Q).
true
At the end to avoid all that repeated code you end using OTP and writting a gen_server for it.
I'm assuming you are building a queue on your own for learning, otherwise Erlang already has a good implementation for a Queue:
http://www.erlang.org/doc/man/queue.html
And the source code:
https://github.com/erlang/otp/blob/master/lib/stdlib/src/queue.erl

Erlang basic general server debugger output interpretation

I'm having some trouble with an Erlang module. Here is the one that I wrote:
-module(basic_gen_server).
-export([start/1, call/2, cast/2]).
start(Module) ->
register(server, spawn(basic_gen_server,gen_server_loop,[Module, Module:init()])), server.
call(Pid,Request) ->
Pid ! {call, self(), Request},
receive
Reply -> Reply
end.
cast(Pid,Request) ->
Pid ! {cast, self(), Request},
receive
_ -> ok
end.
gen_server_loop(Module, CurrentState) ->
io:fwrite("gen_server_loop~n", []),
receive
{call, CallPid, Request} ->
{reply, Reply, NewState} = Module:handle_call(Request,self(),CurrentState),
CallPid ! Reply,
gen_server_loop(Module, NewState);
{cast, CastPid, Request} ->
{noReply, NewState} = Module:handle_cast(Request, CurrentState),
CastPid ! noReply,
gen_server_loop(Module, NewState)
end.
And here is the callback module that was defined:
% Written by Caleb Helbling
% Last updated Oct 10, 2014
-module(name_server).
-export([init/0, add/3, whereis/2, handle_cast/2,
handle_call/3, handle_swap_code/1]).
%% client routines
add(ServerPid, Person, Place) ->
basic_gen_server:cast(ServerPid, {add, Person, Place}).
whereis(ServerPid, Person) ->
basic_gen_server:call(ServerPid, {whereis, Person}).
%% callback routines
init() ->
maps:new().
handle_cast({add, Person, Place}, State) ->
NewState = maps:put(Person, Place, State),
{noreply, NewState}.
handle_call({whereis, Person}, _From, State) ->
Reply = case maps:find(Person, State) of
{ok, Place} -> Place;
error -> error
end,
NewState = State,
{reply, Reply, NewState}.
handle_swap_code(State) ->
{ok, State}.
Upon trying to initialize the server with the following command:
MyServer = basic_gen_server:start(name_server).
I get the following debug output:
=ERROR REPORT==== 29-Oct-2014::12:41:42 ===
Error in process <0.70.0> with exit value: {undef,[{basic_gen_server,gen_server_loop,[name_server,#{}],[]}]}
Conceptually, I understand the notion of making serial code into a basic server system, but I believe that I have a syntax error that I haven't been able to find using either syntax highlighting or Google. Thanks in advance for the help!
Function gen_server_loop is not exported. So you can not call basic_gen_server:gen_server_loop(Module, Module:init()), which is what is happening inside spawn(basic_gen_server,gen_server_loop,[Module, Module:init()]).
If you read your error message it tells you that function you are trying to call in undefined (trougn undef atom). Function being {basic_gen_server,gen_server_loop,[name_server,#{}],[]}, or where you have {Module, Function, ListOfArgs, ...}. You always should check that
there are no types module or function name
function arity match number of arguments in call (List in error message)
function is exported
All local calls (like loop(SomeArgs), without module specified) will not compile if function is not defined. And you can do local call dynamically (FuntionName(SomeArgs) again without module name).
EDIT after comment about need of local calls.
You actually could use lambda for this. There is spawn/1 funciton, which takes lambda (or fun if you like), so you can call spawn( fun local_functino/0).. Only issue with that is fact that your fun can not take any arguments, but there is a way around it, with use of closures.
spawn(fun () ->
gen_server_loop(Module, Module:init())
end).
And gen_serve_loop stays local call.

Resources