Erlang: how to get result from init() in gen_server - erlang

My init() function creates UDP Socket and Returns Socket value as a State.
start() ->
{ok, ServerPid} = gen_server:start_link(?MODULE, [], []).
%%% gen_server API
init([]) ->
{ok, Socket} = gen_udp:open(8888, [list, {active,false}]),
{ok, Socket}.
How can I get Socket in my function start()?

You need to fetch the socket by making a gen_server:call to the newly created gen_server process. e.g.:
start() ->
{ok, ServerPid} = gen_server:start_link(?MODULE, [], []),
Socket = gen_server:call(ServerPid, fetch_socket),
... Use Socket ...
And in the gen_server add something like:
handle_call(fetch_socket, _From, State) ->
{reply, State, State}. %% State == Socket

If you need the udp socket int your start function, you can also create it in the start function, and pass it to the start link call as a parameter. That way you don't have to call the server after you created it.
rvirding points out that this will cause the starting process to receive messages from the udp socket, not the newly spawned server. See the comments for more information. It's not clear from the question what exactly the socket is needed for in the start method, but make sure this is the behavior you want.
start() ->
{ok, Socket} = gen_udp:open(8888, [list, {active,false}]),
{ok, ServerPid} = gen_server:start_link(?MODULE, Socket, []).
%%% gen_server API
init(Socket) ->
{ok, Socket}.

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.

How to make a gen_server reply with a message?

I have the gen_server shown below. It works for the most part. However when I start it from the shell the replies come right back to the shell prompt. I would have expected them to be sent as messages back to the shells pid and then I would use flush() to see them.
What do I have to change in order to have the foo_worker send its replies as messages ?
-module(foo_worker).
-behaviour(gen_server).
%% API
-export([start_link/1, start/1, init/1, send/3, die/1]).
-export([handle_call/3, handle_cast/2, handle_info/2, terminate/2]).
%%%-------------------------------------------------------------------
send(Worker, Ref, Counter) ->
gen_server:call(Worker, {inc, Ref, Counter}).
die(Worker) ->
gen_server:cast(Worker, die).
%%%-------------------------------------------------------------------
start_link(Limit) ->
gen_server:start_link(?MODULE, [Limit], []).
start(Limit) ->
gen_server:start(?MODULE, [Limit], []).
init([Limit]) ->
{ok, Limit}.
handle_call(_, _, Limit) when Limit =< 0 ->
exit({worker, eol});
handle_call({inc, Ref, Data}, From, Limit) ->
io:format("From ~p~n", [From]),
{reply, {Ref, updated, Data+1}, Limit - 1}.
handle_cast(die, _) ->
io:format("~p Dying ~n",[self()]),
exit(normal).
handle_info(Info, State) ->
io:format("Unkown message ~p for state ~p~n", [Info, State]).
terminate(Reason, State) ->
io:format("~p Died because ~p with state ~p~n", [self(), Reason, State]).
The whole point of gen_server:call/2,3 is to wrap into a function call the passing of a message into a gen_server process and the reception of its reply. If you want to deal only with messages, don't use gen_server:call/2,3 but rather have the caller invoke gen_server:cast/2 and include the caller pid in the message:
send(Worker, Ref, Counter) ->
gen_server:cast(Worker, {inc, Ref, Counter, self()}).
Then have gen_server:handle_cast/2 understand that message and use the pid send the reply back to the caller:
handle_cast({inc, Ref, Data, From}, Limit) ->
From ! {Ref, updated, Data+1},
{noreply, Limit-1}.
By the way, note that when you choose this sort of approach, you need to deal with possible failure. If you pass a message to the gen_server process but it dies before it sends you a reply, you need to make sure the caller doesn't sit and wait forever for a reply that will never arrive. The best way to do this is with a monitor — you can have the caller monitor the gen_server process before sending it a message and demonitor it once it receives the reply. If the gen_server process dies, the caller will get a DOWN message instead (see the monitor documentation for details). Also note that by doing this you're reimplementing a bunch of what gen_server:call/2,3 already does for you.

how this kind of erlang gen_server should be tested?

I have a gen_server which exports a function like this:
my_function(Param) ->
gen_server:cast(?SERVER, {forward, Param}).
and has an handle_cast like this:
handle_cast({forward, Param}, #state{peer=Socket} = State) ->
gen_tcp:send(Socket, Param),
{noreply, State}.
In most of cases the peer that is connected using gen_tcp will reply with one among different messages, and I handle the reply in the handle_info
handle_info({tcp, Socket, Data}) ->
io:format("Received : ~p~n", [Data]),
{noreply, State}.
Is there any recommended way to test this kind of scenario in erlang?
You can use setup/teardown functions in order to do something like that:
my_test_() -> {
setup,
fun() -> my_server:start_link() end,
fun(_) -> my_server:stop() end,
fun() ->
%% here you do all the message sending and such
end
}.
You also need to export a stop/0 function which will be responsible for call/cast the gen_server in order to stop it.
You can find out more infos on fixtures here
HTH,
Alin

What OTP behaviors should I use for such module?

I have simple erlang module and I want to rewrite it based on OTP principles. But I can not determine what opt template I should use.
Module's code:
-module(main).
-export([start/0, loop/0]).
start() ->
Mypid = spawn(main, loop, []),
register( main, Mypid).
loop() ->
receive
[Pid, getinfo] -> Pid! [self(), welcome],
io:fwrite( "Got ~p.~n", [Pid] ),
// spawn new process here
loop();
quit -> ok;
X ->
io:fwrite( "Got ~p.~n", [ X ] ),
// spawn new process here
loop()
end.
gen_server would be fine.
Couple things:
it is a bad practice to send message to yourself
messages are usually tuples not lists because they are not dynamic
despite your comment, you do not spawn the new process.
Call to loop/0 enters the same loop.
Gen_server init would hold your start/0 body. API calls sequence and proxy your calls via gen_server to handle_calls. To spawn new process on function call, add spawn function to the body of desired handle_call. Do not use handle_info to handle incoming messages -- instead of sending them call the gen_server API and 'translate' your call into gen_server:call or cast. e.g.
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
init(_) ->
{ok, #state{}}
welcome(Arg) ->
gen_server:cast(?MODULE, {welcome, Arg}).
handle_cast({welcome, Arg},_,State) ->
io:format("gen_server PID: ~p~n", [self()]),
spawn(?MODULE, some_fun, [Arg]),
{noreply, State}
some_fun(Arg) ->
io:format("Incoming arguments ~p to me: ~p~n",[Arg, self()]).
I have never compiled above, but it should give you the idea.

Erlang missing messages

I'm running the following code with dbg:p(client, r):
-module(client).
-export([start/0, start/2, send/1, net_client/1]).
start() ->
start("localhost", 7000).
start(Host, Port) ->
io:format("Client connecting to ~p:~p.~n", [Host, Port]),
register(ui, spawn(fun() -> gui_control([]) end)),
case gen_tcp:connect(Host, Port, [binary, {packet, 0}]) of
{ok, Socket} ->
Pid = spawn(client, net_client, [Socket]),
register(client, Pid),
gen_tcp:controlling_process(Socket, Pid);
Error ->
io:format("Error connecting to server: ~p~n", [Error]),
erlang:error("Could not connect to server.")
end,
ok.
send(Msg) ->
client!{send, Msg}.
%% Forwards messages to either the GUI controller or the server.
net_client(Socket) ->
receive
{tcp, Socket, Message} ->
Msg = binary_to_term(Message),
io:format("Received TCP message on ~p: ~p~n", [Socket, Msg]),
ui!{server, Msg};
{send, Message} ->
io:format("Sending ~p.~n", [Message]),
gen_tcp:send(Socket, term_to_binary(Message));
close ->
gen_tcp:close(Socket),
exit(normal);
{tcp_closed, Socket} ->
io:format("Server terminated connection.~n"),
exit(normal); %% Reconnect?
timeout -> %% This
io:format("Timed out?~n");
{inet_reply, Socket, Message} -> %% and this never happen.
io:format("inet_reply: ~p~n", Message);
Error ->
io:format("Net client got bad message: ~p.~n", [Error])
after 10000 ->
refresh %% gen_tcp:send(Socket, term_to_binary(keepalive))
end,
?MODULE:net_client(Socket).
gui_control(Data) ->
receive
%% This will hang the gui until the sync is done. Not sure if
%% that's okay.
{server, {sync, Datum}} -> % Resync command from server.
gui_control(resync([Datum]));
{client, refresh} -> % Refresh request from display.
display:update(Data);
{server, Msg} ->
io:format("UI Rx: ~p~n", [Msg])
end,
gui_control(Data).
resync(Data) ->
receive
{server, {sync, Datum}} ->
resync([Datum|Data]);
{server, {done, Num}} ->
case length(Data) of
Num ->
Data;
_ ->
io:format("Got done before all the data were received.~n"),
send({sync})
end
after 5000 ->
io:format("Timed out waiting for data.~n"),
send({sync})
end.
It communicates with a server I wrote with gen_tcp and gen_server, following this. My main problem is that I don't reliably receive all my messages. Sometimes I'll get
(<0.2.0>) << {tcp,#Port<0.517>,
<<131,104,6,100,0,4,99,97,114,100,100,0,7,117,110,107,110,
111,119,110,100,0,7,117,110,107,110,111,119,110,106,106,
104,3,107,0,6,83,101,99,111,110,100,100,0,4,100,114,97,
119,97,2,131,104,6,100,0,4,99,97,114,100,100,0,7,117,110,
107,110,111,119,110,100,0,7,117,110,107,110,111,119,110,
106,106,104,3,107,0,6,83,101,99,111,110,100,100,0,4,100,
114,97,119,97,3,131,104,6,100,0,4,99,97,114,100,100,0,7,
117,110,107,110,111,119,110,100,0,7,117,110,107,110,111,
119,110,106,106,104,3,107,0,5,70,105,114,115,116,100,0,4,
100,114,97,119,97,0>>}
from the debugging output, but no corresponding Received TCP message on #Port<0.517>:... message. I'll also see things like this:
(<0.2.0>) << {io_reply,<0.24.0>,ok}
(<0.2.0>) << timeout
(<0.2.0>) << {io_reply,<0.24.0>,ok}
(<0.2.0>) << timeout
(<0.2.0>) << {io_reply,<0.24.0>,ok}
but nothing from net_client's receive. I've watched the network traffic with wireshark and I know the packets are getting where they're supposed to go and being ACKed. What am I doing wrong?
Edit: I'm invoking this with erl -smp enable -eval "client:start()." in case it matters.
I guess the basic problem is that 'net_client' should be spawned off as a separate process..
In the start method, change
register(client, self()),
net_client(Socket);
to
register(client, fun() -> net_client(Socket) end);
that should solve it..
Also, I recommend using redbug (part of eper) https://github.com/massemanet/eper when tracing. It protects you from drowning your system in trace output and provides a dead simple syntax, eg: redbug:start("mymod:foo -> return", [{msgs,10}]). trace all calls to mymod:foo and what those calls return but give me no more than 10 trace messages.
Turns out {packet, 0} was my problem. Replace that with {packet, 2} and all is well.

Resources