What I'm trying to do is have a gen_server process accept a new client and immediately spawn a new child to handle the next one. The issue that I'm seeing is that when the socket is finished and consequentially terminates, it also closes the listening socket and I can't figure out why, even though it no longer references it.
Any idea what I am doing wrong?
gen_server:
-module(simple_tcp).
-behaviour(gen_server).
%% API
-export([start_link/1, stop/0, start/0, start/1]).
%% gen-server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
-define(SERVER, ?MODULE).
-define(DEFAULT_PORT, 1055).
-record(state, {port, lsock}).
start_link({port, Port}) ->
gen_server:start_link(?MODULE, [{port, Port}], []);
start_link({socket, Socket}) ->
gen_server:start_link(?MODULE, [{socket, Socket}], []).
start({port, Port}) ->
simple_tcp_sup:start_child({port, Port});
start({socket, Socket}) ->
simple_tcp_sup:start_child({socket, Socket}).
start() ->
start({port, ?DEFAULT_PORT}).
stop() ->
gen_server:cast(?SERVER, stop).
% Callback functions
init([{port, Port}]) ->
{ok, LSock} = gen_tcp:listen(Port, [{active, true},{reuseaddr, true}]),
init([{socket, LSock}]);
init([{socket, Socket}]) ->
io:fwrite("Starting server with socket: ~p~n", [self()]),
{ok, Port} = inet:port(Socket),
{ok, #state{port=Port, lsock=Socket}, 0}.
handle_call(_Msg, _From, State) ->
{noreply, State}.
handle_cast(stop, State) ->
{stop, ok, State}.
handle_info({tcp, Socket, RawData}, State) ->
gen_tcp:send(Socket, io_lib:fwrite("Received raw data: ~p~n", [RawData])),
{noreply, State};
handle_info({tcp_error, _Socket, Reason}, State) ->
io:fwrite("Error: ~p~n", [Reason]),
{stop, normal, State};
handle_info(timeout, #state{lsock = LSock} = State) ->
case gen_tcp:accept(LSock) of
{ok, Sock} ->
io:fwrite("Accepting connection...~p~n", [self()]),
start({socket, LSock}),
{noreply, #state{lsock=Sock}};
{error, Reason} ->
io:fwrite("Error: ~p, ~p~n", [Reason, self()]),
{stop, normal, State}
end;
handle_info({tcp_closed, _Port}, State) ->
io:fwrite("Socket closed: ~p~n", [self()]),
simple_tcp_sup:kill_child(self()),
{stop, normal, State}.
terminate(_Reason, _State) ->
io:fwrite("Shutting down server: ~p~n", [self()]),
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
supervisor:
-module(simple_tcp_sup).
-behaviour(supervisor).
-export([start_link/0,
start_child/1
]).
-export([init/1]).
-define(SERVER, ?MODULE).
start_link() ->
supervisor:start_link({local, ?SERVER}, ?MODULE, []).
start_child({socket, Socket}) ->
io:fwrite("Spawning child with socket...~n"),
supervisor:start_child(?SERVER, [{socket, Socket}]);
start_child({port, Port}) ->
io:fwrite("Spawning child with port...~n"),
supervisor:start_child(?SERVER, [{port, Port}]).
init([]) ->
Element = {simple_tcp, {simple_tcp, start_link, []},
temporary, brutal_kill, worker, [simple_tcp]},
Children = [Element],
RestartStrategy = {simple_one_for_one, 0, 1},
{ok, {RestartStrategy, Children}}.
Your third handle_info reverses the roles of Sock and LSock. It should pass Sock to the child process and leave its own state unmodified.
BTW: It's bad karma to rebuild State from scratch (#state{lsock=Sock}) you should always derive the new State from current State (State#state{lsock=Sock}), just in case you later add more state variables. Actually, this has a bug right now (albeit a benign one), since you are throwing away the port number.
Well, i suggest that you let the Socket stuff be handled by separate processes that communicate asynchronously with the gen_server and are linked with it. I have a sample code snippet that would show you how this could be done. The gen_server starts and spawns a TCP listener which after successfully obtaining a listening socket informs our gen_server as to change its internal state.
I have arranged the code from top down. All relevant functions have been showed.
Focus on the socket handling processes and how they interact with the gen_server
-define(PEER_CLIENT_TIMEOUT,timer:seconds(20)).
-define(PORT_RANGE,{10245,10265}).
-define(DEBUG(X,Y),error_logger:info_msg(X,Y)).
-define(ERROR(L),error_logger:error_report(L)).
-define(SOCKET_OPTS(IP),[inet,binary,{backlog,100},{packet,0},
{reuseaddr,true},{active,true},
{ip,IP}]).
%%----------------------------------------------------
%% gen_server starts here....
start(PeerName)->
gen_server:start_link({local,?MODULE},?MODULE,PeerName,[]).
%%%-------------------------------------------
%% Gen_server init/1 function
init(PeerName)->
process_flag(trap_exit,true),
%% starting the whole Socket chain below..
start_link_listener(),
%% Socket stuff started, gen_server can now wait for async
%% messages
{ok,[]}.
%%% ---- Socket handling functions ---------
%% Function: start_link_listener/0
%% Purpose: Starts the whole chain of listening
%% and waiting for connections. Executed
%% directly by the gen_server process, But
%% spawns a separate process to do the rest
start_link_listener()->
Ip_address = get_myaddr(),
spawn_link(fun() -> listener(?SOCKET_OPTS(Ip_address)) end).
%%%----------------------------------------------
%% Function: get_myaddr/0
%% Purpose: To pick the active IP address on my machine to
%% listen on
get_myaddr()->
?DEBUG("Server> Trying to extract My Local Ip Address....",[]),
{ok,Name} = inet:gethostname(),
{ok,IP} = inet:getaddr(Name,inet),
?DEBUG("Server> Found Alive Local IP address: ~p.....~n",[IP]),
IP.
%%%--------------------------------------------------
%% Function: listener/1, executed in a separate process
%% Purpose: Tries a given ?PORT_RANGE, with the given Socket Options
%% Once it acquires a ListenSocket, it will cast the gen_server!
listener(SocketOpts)->
process_flag(trap_exit,true),
Ports = lists:seq(element(1,?PORT_RANGE),element(2,?PORT_RANGE)),
case try_listening(SocketOpts,Ports) of
{ok,Port,LSocket}->
PP = proplists:get_value(ip,SocketOpts),
?MODULE:started_listener(Port,PP,LSocket),
accept_connection(LSocket);
{error,failed} -> {error,failed,SocketOpts}
end.
try_listening(_Opts,[])-> {error,failed};
try_listening(Opts,[Port|Rest])->
case gen_tcp:listen(Port,Opts) of
{ok,Listen_Socket} -> {ok,Port,Listen_Socket};
{error,_} -> try_listening(Opts,Rest)
end.
%%%---------------------------------------------------------
%% Helper Functions for Converting IP Address from tuple
%% to string and vice versa
str(X) when is_integer(X)-> integer_to_list(X).
formalise_ipaddress({A,B,C,D})->
str(A) ++ "." ++ str(B) ++ "." ++ str(C) ++ "." ++ str(D).
unformalise_address(String)->
[A,B,C,D] = string:tokens(String,"."),
{list_to_integer(A),list_to_integer(B),list_to_integer(C),list_to_integer(D)}.
%%%--------------------------------------------------
%% Function: get_source_connection/1
%% Purpose: Retrieving the IP and Port at the other
%% end of the connection
get_source_connection(Socket)->
try inet:peername(Socket) of
{ok,{IP_Address, Port}} ->
[{ipAddress,formalise_ipaddress(IP_Address)},{port,Port}];
_ -> failed_to_retrieve_address
catch
_:_ -> failed_to_retrieve_address
end.
%%%-----------------------------------------------------
%% Function: accept_connection/1
%% Purpose: waits for a connection and re-uses the
%% ListenSocket by spawning another thread
%% to take it and listen too. It casts the gen_server
%% at each connection and provides details about it.
accept_connection(ListenSocket)->
case gen_tcp:accept(ListenSocket,infinity) of
{ok, Socket}->
%% re-use the ListenSocket below.....
spawn_link(fun() -> accept_connection(ListenSocket) end),
OtherEnd = get_source_connection(Socket),
?MODULE:accepted_connection(OtherEnd),
loop(Socket,OtherEnd);
{error,_} = Reason ->
?ERROR(["Listener has failed to accept a connection",
{listener,self()},{reason,Reason}])
end.
%%%-------------------------------------------------------------------------
%% Function: loop/2
%% Purpose: TCP reception loop, it casts the gen_server
%% as soon as it receives something. gen_server
%% is responsible for generating reponse
%% OtherEnd ::= [{ipAddress,StringIPAddress},{Port,Port}] or 'failed_to_retrieve_address'
loop(Socket,OtherEnd)->
receive
{tcp, Socket, Data}->
?DEBUG("Acceptor: ~p has received a binary message from: ~p~n",[self(),OtherEnd]),
Reply = ?MODULE:incoming_binary_message(Data,OtherEnd),
gen_tcp:send(Socket,Reply),
gen_tcp:close(Socket),
exit(normal);
{tcp_closed, Socket} ->
?DEBUG("Acceptor: ~p. Socket closed by other end: ~p~n",[self(),OtherEnd]),
?MODULE:socket_closed(OtherEnd),
exit(normal);
Any -> ?DEBUG("Acceptor: ~p has received a message: ~p~n",[self(),Any])
end.
%%%----------------------------------------------
%% Gen_server Asynchronous APIs
accepted_connection(failed_to_retrieve_address)-> ok;
accepted_connection([{ipAddress,StringIPAddress},{Port,Port}])->
gen_server:cast(?MODULE,{connected,StringIPAddress,Port}).
socket_closed(failed_to_retrieve_address)-> ok;
socket_closed([{ipAddress,StringIPAddress},{Port,Port}])->
gen_server:cast(?MODULE,{socket_closed,StringIPAddress,Port}).
incoming_binary_message(Data,_OtherEnd)-> %% expecting a binary reply
case analyse_protocol(Data) of
wrong -> term_to_binary("protocol violation!");
Val -> gen_server:call(?MODULE,{request,Val},infinity)
end.
%%% -------------------- handle cast ------------------------------------------
handle_cast({listener_starts,_Port,_MyTupleIP,_LSocket} = Object,State)->
NewState = do_something_with_the_listen_report(Object),
{noreply,NewState};
handle_cast({connected,_StringIPAddress,_Port} = Object,State)->
NewState = do_something_with_the_connection_report(Object),
{noreply,NewState};
handle_cast({socket_closed,_StringIPAddress,_Port} = Object,State)->
NewState = do_something_with_the_closed_connection_report(Object),
{noreply,NewState};
handle_cast(Any,State)->
?DEBUG("Server> I have been casted some unknown message: ~p~n",[Any]),
{noreply,State}.
%%%% ---------------------- handle call --------------
handle_call({request,Val},_,State)->
{NewState,Reply} = req(Val,State),
{reply,Reply,NewState};
handle_call(_,_,State)-> {reply,[],State}.
req(Val,State)->
%% modify gen_server state and
%% build reply
{NewState,Reply} = modify_state_and_get_reply(State,Val),
{NewState,Reply}.
%%------------------- terminate/2 --------------------
terminate(_Reason,_State)-> ok.
%%----------------- code_change/3 ------------------
code_change(_,State,_)-> {ok,State}.
With the asynchronous capability of the gen_server, we can handle the socket details from separate linked processes. These processes then would communicate with the gen_server via cast and without blocking the gen_server from its concurrent nature.
Related
I am developing a chat server and it is distributed. I can spawn new users (using start_link) in multiple nodes and can send a message to a node, then received message will be shown on received node. I have no idea how should I use gen_statem(FSM) for this application. I need to add a FSM between two chat users(nodes). I have used a record to save states of sender, receiver and message also. Could someone suggest me how to add gen_statem here ?
I need to visualize the states received, sent respectively for send_message and receive message events.
chat server:
-module(message_server).
-behaviour(gen_server).
-export([send_message/2, receive_message/3, start_link/1, stop/0, receive_db_data/1]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
code_change/3]).
-define(SERVER, ?MODULE).
-record(message_server_state, {from, to, msgsent, msgreceived}).
%%%===================================================================
%%% Spawning and gen_server implementation
%%%===================================================================
start_link(Name) ->
gen_server:start_link({local, ?MODULE}, ?MODULE, Name, []).
init(Name) ->
io:format("~p is connected with the server...~n", [Name]),
{ok, #message_server_state{
from = node(),
to = [],
msgsent = [],
msgreceived = []
}}.
stop() ->
gen_server:stop(?MODULE).
receive_db_data(Sender) ->
database_server:getalldb(Sender).
send_message(To, Msg) ->
gen_server:call({?MODULE, node()}, {send, To, Msg}).
receive_message(Sender, To, Msg) ->
gen_server:call({?MODULE, list_to_atom(To)}, {reciv, Sender, Msg}).
handle_call({send, To, Msg}, _From, State = #message_server_state{to = Receivers, msgsent = Msgsent, from = Sender}) ->
io:format("Sent message: ~p~n", [Msg]),
receive_message(Sender, To, Msg),
{reply, ok, State#message_server_state{msgsent = [Msg | Msgsent], to = [To | Receivers]}};
handle_call({reciv, Sender, Msg}, _From, State = #message_server_state{msgreceived = Msgreceived}) ->
io:format("Sent by: ~p~n", [Sender]),
io:format("Message: ~p~n", [Msg]),
receive_db_data(Sender),
{reply, ok, State#message_server_state{msgreceived = [Msg | Msgreceived]}}.
handle_cast(_Request, State = #message_server_state{}) ->
{noreply, State}.
handle_info(_Info, State = #message_server_state{}) ->
{noreply, State}.
terminate(_Reason, _State = #message_server_state{}) ->
ok.
code_change(_OldVsn, State = #message_server_state{}, _Extra) ->
{ok, State}.
I implemented a tcp_listener as a gen_server(). I have a function called start_link(Port) creates a tcp_listener at that port. Now, I have trouble understanding how to tell the tcp_listener to stop listening through stop().
I tried calling a function like this with stop.
stop() -> gen_server:cast(?MODULE, shutdown)
in the terminate/2 function I tried closing the listening socket but failed.
terminate(_Reason, State = #state{lsocket = LSocket}) ->
gen_tcp:close(LSocket),
NewState = State#state{lsocket = null},
{ok, NewState}.
If I close the listening socket, what happens to the accepted connections that I spawned.
Thank you!
start_link(Port) when is_integer(Port) ->
State = #state{port = Port},
gen_server:start_link({local, ?MODULE}, ?MODULE, State, []).
init(State = #state{port = Port}) ->
io:format("Supervisor started me and I am listening at ~p ~n", [Port]),
case gen_tcp:listen(Port, ?TCP_OPTIONS) of
{ok, LSocket} ->
NewState = State#state{lsocket = LSocket},
spawn(fun() -> accept(NewState) end),
{ok, NewState};
{error, Reason} ->
{stop, Reason}
end.
accept(State = #state{lsocket = LSocket}) ->
case gen_tcp:accept(LSocket) of
{ok, Socket} ->
Pid = spawn(fun() ->
io:format("Connection accepted ~n"),
loop(Socket)
end),
gen_tcp:controlling_process(Socket, Pid),
accept(State);
{error, closed} -> error
end.
loop(Socket) ->
case gen_tcp:recv(Socket, 0) of
{ok, Data} ->
gen_tcp:send(Socket, Data),
loop(Socket);
{error, closed} ->
ok
end.
I recommend you have a look at this chapter in the Learn You Some Erlang book, in particular:
Both sockets can send messages in the same way, and can then be closed with gen_tcp:close(Socket). Note that closing an accept socket will close that socket alone, and closing a listen socket will close none of the related and established accept sockets, but will interrupt currently running accept calls by returning {error, closed}.
So it should be just a matter of calling gen_tcp:close(Socket) on all sockets.
I want to write a module which need to be able to push message to pusher in Erlang. I found this repo https://github.com/bradfordw/pusherl, which is not maintained anymore, but I think I can adapt to make it work. I followed the README, and I ran successfully all the Rebar command. Then I open the rebar console by the command
./rel/pusherl/bin/pusherl console
So the server should start. But then when I try to call
gen_server:call(pusherl_server, {push, {"ChannelName", "EventName", "Payload"}}).
Then it throws error:
exception exit: {noproc,{gen_server,call,
[pusherl_server,
{push,
{"ChannelName","EventName","Payload"}}]}}
in function gen_server:call/2 (gen_server.erl, line 182)
I'm quite new with Erlang and OTP, and it takes me half of the day to make it work but not successful. Please help me to solve this. I really appreciate.
By the way, if you know any other pusher client, please suggest me. Thanks a lot.
Here is the code to for gen_server callback:
-module(pusherl_server).
-behaviour(gen_server).
-define(SERVER, ?MODULE).
-define(JP, fun(K,V) -> string:join([K,V],"=") end).
-record(state,{app_id, key, secret}).
-export([start_link/0]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
init(_) ->
{ok, PusherAppId} = application:get_env(pusher_app_id),
{ok, PusherKey} = application:get_env(pusher_key),
{ok, PusherSecret} = application:get_env(pusher_secret),
{ok, #state{app_id=PusherAppId, key=PusherKey, secret=PusherSecret}}.
handle_call({push, {ChannelName, EventName, Payload}}, _From, State) ->
case http_request(ChannelName, EventName, Payload, State) of
{ok, _} -> {reply, ok, State};
{error, _} -> {reply, error, State}
end;
handle_call(_Request, _From, State) ->
{noreply, ok, State}.
handle_cast({push, {ChannelName, EventName, Payload}}, State) ->
case http_request(ChannelName, EventName, Payload, State) of
{ok, _} -> {noreply, ok, State};
{error, _} -> {noreply, error, State}
end;
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
http_request(ChannelName, EventName, Payload, Config) when is_list(ChannelName), is_record(Config, state) ->
{ok, ReqProps} = http_request_props(Payload, EventName, ChannelName, Config),
httpc:request(post, ReqProps, [], []).
http_request_props(Payload, EventName, ChannelName, #state{app_id=AppId, key=AppKey, secret=AppSecret}) ->
Md5String = lists:flatten([io_lib:format("~2.16.0b",[N]) || <<N>> <= crypto:md5(Payload)]),
ToSign = ["POST",
lists:flatten(["/apps/", AppId, "/channels/", ChannelName, "/events"]),
string:join([?JP("auth_key", AppKey),
?JP("auth_timestamp", get_time_as_string()),
?JP("auth_version", "1.0"),
?JP("body_md5", Md5String),
?JP("name", EventName)
],"&")
],
AuthSignature = signed_params(ToSign, AppSecret),
QueryParams = [
?JP("auth_key", AppKey),
?JP("auth_timestamp", get_time_as_string()),
?JP("auth_version","1.0"),
?JP("body_md5", Md5String),
?JP("auth_signature", AuthSignature),
?JP("name", EventName)
],
Url = http_api_url(AppId, ChannelName, QueryParams),
{ok, {Url, [], "application/x-www-form-urlencoded", Payload}}.
http_api_url(AppId, ChannelName, QueryParams) ->
QueryString = string:join(QueryParams,"&"),
lists:flatten(["http://api.pusherapp.com/apps/",AppId,"/channels/",ChannelName,"/events?", QueryString]).
get_time_as_string() ->
{M, S, _} = now(),
integer_to_list(((M * 1000000) + S)).
signed_params(Params, Secret) ->
lists:flatten([io_lib:format("~2.16.0b",[N]) || <<N:8>> <= sha2:hmac_sha256(Secret, string:join(Params,"\n"))]).
noproc means that the process you're trying to call is not running. You need to start it with:
pusherl_server:start_link().
Note that this will link the pusherl_server process to the calling process. If you're running this in the shell, and then do something that causes an error, the error will propagate across the link to the pusherl_server process and kill it, so then you have to start it again.
To avoid that, you can either unlink the process after starting it:
{ok, Pid} = pusherl_server:start_link().
unlink(Pid).
or add a start function to the module, that does the same as start_link except it calls gen_server:start instead of gen_server:start_link.
I'm writing something to process Erlang source code. Pretty much the first line of the program is:
{ok, Forms} = epp_dodger:parse_file(Filename)
However, I want to do some simple unit testing. So: how do I persuade epp_dodger to take its input from a string instead of a file?
Alternatively, it has epp_dodger:parse_form/2,3, which takes an IODevice, so how do I provide an IODevice over a string?
The code below (which is admittedly a bit hackish) starts a gen_server that takes a string as an argument, and then fulfills the Erlang I/O Protocol enough to satisfy epp_dodger:parse/2 to be able to read and parse the string from that process.
Here's an example of using it:
1> {ok,P} = iostr:start("-module(x).\n-export([f/0]).\nf() -> ok.\n").
2> epp_dodger:parse(P,1).
{ok,[{tree,attribute,
{attr,1,[],none},
{attribute,
{tree,atom,{attr,1,[],none},module},
[{tree,atom,{attr,1,[],none},x}]}},
{tree,attribute,
{attr,2,[],none},
{attribute,
{tree,atom,{attr,2,[],none},export},
[{tree,list,
{attr,2,[],none},
{list,
[{tree,arity_qualifier,
{attr,2,[],none},
{arity_qualifier,
{tree,atom,{attr,...},f},
{tree,integer,{...},...}}}],
none}}]}},
{tree,function,
{attr,3,[],none},
{func,
{tree,atom,{attr,3,[],none},f},
[{tree,clause,
{attr,3,[],none},
{clause,[],none,[{atom,3,ok}]}}]}}]}
The process dies once the string is exhausted.
Note that the code probably misses a few things — handling unicode properly, for example — but it should give you a good idea of how to make a more robust I/O server for this purpose if needed.
-module(iostr).
-behaviour(gen_server).
-export([start_link/1, start/1, stop/1]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-record(state, {
data,
line = 1,
lines
}).
start_link(Data) ->
gen_server:start_link(?MODULE, [Data], []).
start(Data) ->
gen_server:start(?MODULE, [Data], []).
stop(Pid) ->
gen_server:cast(Pid, stop).
init([Data0]) ->
Data = [Line++"\n" || Line <- string:tokens(Data0, "\n")],
{ok, #state{data=Data,lines=length(Data)}}.
handle_call(_Request, _From, State) ->
{reply, ok, State}.
handle_cast(stop, State) ->
{stop, normal, State};
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info({io_request,From,ReplyAs,{get_until,_,_,_,_,_}},
#state{data=[],lines=L}=State) ->
From ! {io_reply, ReplyAs, {eof,L}},
{stop, normal, State};
handle_info({io_request,From,ReplyAs,{get_until,_,_,M,F,Args}},
#state{data=Data,line=L}=State) ->
case handler(Data,L,[],M,F,Args) of
eof ->
Lines = State#state.lines,
From ! {io_reply, ReplyAs, {eof,Lines}},
{stop, normal, State#state{data=[]}};
{ok,Result,Rest,NData,NL} ->
From ! {io_reply, ReplyAs, Result},
case Rest of
[] ->
{noreply, State#state{data=NData,line=NL}};
_ ->
{noreply, State#state{data=[Rest|NData],line=NL}}
end
end;
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
handler([Input|Data],L,Cont,M,F,Extra) ->
case catch apply(M,F,[Cont,Input|Extra]) of
{done,eof,_} ->
eof;
{done,Result,Rest} ->
{ok,Result,Rest,Data,L+1};
{more,NCont} ->
case Data of
[] -> eof;
_ -> handler(Data,L+1,NCont,M,F,Extra)
end
end.
I got one error when starting my gen server, i want to know how to debug it, thanks!
I run "example:add_listener(self(), "127.0.0.1", 10999)." after start_link.
The error is :
=ERROR REPORT==== 11-May-2011::13:41:57 ===
** Generic server <0.37.0> terminating
** Last message in was {'EXIT',<0.35.0>,
{{timeout,
{gen_server,call,
[<0.35.0>,
{add_listener,"127.0.0.1",10999}]}},
[{gen_server,call,2},
{erl_eval,do_apply,5},
{shell,exprs,6},
{shell,eval_exprs,6},
{shell,eval_loop,3}]}}
** When Server state == {state,example,
{dict,0,16,16,8,80,48,
{[],[],[],[],[],[],[],[],[],[],[],[],[],
[],[],[]},
{{[],[],[],[],[],[],[],[],[],[],[],[],[],
[],[],[]}}},
{dict,0,16,16,8,80,48,
{[],[],[],[],[],[],[],[],[],[],[],[],[],
[],[],[]},
{{[],[],[],[],[],[],[],[],[],[],[],[],[],
[],[],[]}}},
[]}
** Reason for termination ==
** {{timeout,{gen_server,call,[<0.35.0>,{add_listener,"127.0.0.1",10999}]}},
[{gen_server,call,2},
{erl_eval,do_apply,5},
{shell,exprs,6},
{shell,eval_exprs,6},
{shell,eval_loop,3}]}
** exception exit: {timeout,{gen_server,call,
[<0.35.0>,{add_listener,"127.0.0.1",10999}]}}
in function gen_server:call/2
My code is :
-module(test_ess_tcp).
-export([start_link/0,
add_listener/3,
remove_listener/3]).
-export([init/2, handle_call/3, handle_cast/2, handle_info/2]).
-export([terminate/2, sock_opts/0, new_connection/4]).
-behavior(ess_tcp).
start_link() ->
ess_tcp:start_link(?MODULE, []).
add_listener(Pid, IpAddr, Port) ->
gen_server:call(Pid, {add_listener, IpAddr, Port}).
remove_listener(Pid, IpAddr, Port) ->
gen_server:call(Pid, {remove_listener, IpAddr, Port}).
init([], State) ->
%% Example storing callback module specific state
%% This modifies the server state
{ok, ess_tcp:store_cb_state([], State)}.
handle_call({add_listener, IpAddr, Port}, _From, State) ->
%% Example of getting callback module state
io:format("Not used here, but just an example"),
[] = ess_tcp:get_cb_state(State),
case ess_tcp:add_listen_socket({IpAddr, Port}, State) of
{ok, State1} ->
{reply, ok, State1};
Error ->
{reply, Error, State}
end;
handle_call({remove_listener, IpAddr, Port}, _From, State) ->
case ess_tcp:remove_listen_socket({IpAddr, Port}, State) of
{ok, State1} ->
{reply, ok, State1};
Error ->
{reply, Error, State}
end;
handle_call(_Msg, _From, State) ->
{reply, ignored, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info({tcp, Sock, Data}, State) ->
Me = self(),
P = spawn(fun() -> worker(Me, Sock, Data) end),
gen_tcp:controlling_process(Sock, P),
{noreply, State};
handle_info(_Msg, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
sock_opts() ->
[binary, {active, once}, {packet, 0}].
new_connection(_IpAddr, _Port, Sock, State) ->
Me = self(),
P = spawn(fun() -> worker(Me, Sock) end),
gen_tcp:controlling_process(Sock, P),
{ok, State}.
worker(Owner, Sock) ->
gen_tcp:send(Sock, "Hello\n"),
inet:setopts(Sock, [{active, once}]),
gen_tcp:controlling_process(Sock, Owner).
worker(Owner, Sock, Data) ->
gen_tcp:send(Sock, Data),
inet:setopts(Sock, [{active, once}]),
gen_tcp:controlling_process(Sock, Owner).
Well, your gen_server:call is getting a timeout when called. That means that the gen_server is either taking longer than the default 3 second timeout for a call or it is blocked somewhere.
using tracing to debug kind of behaviour is ideal. For instance if you type this in the shell before running the test:
dbg:tracer(),dbg:p(all,c),dbg:tpl(ess_tcp, x).
you will trace on all functions within ess_tcp to see what is going on in there. For more info about dbg see http://www.erlang.org/doc/man/dbg.html
actually best tool to debug in erlang is io:format statements. Put io:formats where you have doubts and see if you get the expected values.
Regarding above question it is mainly stuck somewhere !!