[erlang-questions] Learning OTP - Application, Supervisor, FSM, Server

Serge Aleynikov <>
Mon Sep 1 23:36:27 CEST 2008


It rightfully hangs.  If you read documentation of gen_server more 
closely, you'll find that the init/1 function is supposed to return:

   {ok, State} | {stop, Reason}.

Your implementation blocks in the receive call without sending any 
response to the caller, and therefore the caller (i.e. the supervisor) 
is infinitely blocked.

gen_* behaviors' callbacks are not supposed to execute blocking calls or 
else the entire server instance (not to be confused with the virtual 
machine) will block and be unable to serve other clients' requests.

The error you are getting upon trying to connect a client is because the 
tcp_server_app:start_chat_server/0 function that's being called by the 
listener process is not exported or misnamed.

Serge

Jeff Crane wrote:
> The chat_server source follows:
> 
> -module(chat_server).
> -import(lists, [delete/2,foreach/2,map/2,member/2,reverse/2]).
> 
> -behaviour(gen_server).
> 
> -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, ?MODULE}, ?MODULE, [], []).
>     
> init([]) ->
> 	process_flag(trap_exit, true),
> 	server_loop([]).
> 
> %% This chat_server routes and SENDS these messages to chat_channel modules
> %% Message Formats:
> %%	{join, Connection, Nick}
> %%	{Connection, {chat, Nick, Msg}}
> %%	{Connection, {whisper, Nick, TargetNick, Msg}}
> %%	{Connection, {emote, Nick, Msg}}
> %%	{Connection, {part}}
> %%	{Connection, {quit}}
> 
> server_loop(ChanList) ->
> 	receive
> 		{client, Connection, {Type, Args}} ->
> 			case Type of
> 				join ->
> 					{Nick,Channel,Args} = list_to_tuple(Args),
> 					case lookupChannel(Channel, ChanList) of 
> 						{ok, Pid, _UserList} ->
> 							Pid ! {join, Connection, Nick};
> 						channel_full ->
> 							io:format("Chat Server received Join for Channel:~p at Capacity.~n",[Channel]),
> 							self() ! {client, Connection, {join,[Nick|[Channel++"-"]]}};
> 						channel_not_found ->
> 							% Create a new channel
> 							Pid = spawn_link(fun() -> chat_channel:start(Connection, Nick, Channel) end),
> 							server_loop([{Channel,Pid,{[{Nick,Connection}]}}|ChanList]);
> 						Any ->
> 							io:format("lookupChannel:join received unexpected case:~p~n",[Any])
> 					end;
> 				chat ->
> 					{Nick,Channel,Msg,Args} = list_to_tuple(Args),
> 					case lookupChannel(Channel,ChanList) of
> 						{ok, Pid} ->
> 							Pid ! {Connection, {chat, Nick, Msg}};
> 						channel_not_found ->
> 							self() ! {client, Connection, {join,[Nick|[Channel]]}},
> 							self() ! {client, Connection, {chat,Args}};
> 						Any ->
> 							io:format("lookupChannel:chat received unexpected case:~p~n",[Any])
> 					end;
> 				whisper ->
> 					{Nick,TargetNick,Msg,Args} = list_to_tuple(Args),
> 					case lookupNickInChannel(Nick,ChanList) of
> 						{ok, Pid} ->
> 							Pid ! {Connection, {whisper, Nick, TargetNick, Msg}};
> 						user_cannot_be_found ->
> 							send(Connection,{whisper_target_not_found, TargetNick});
> 						Any ->
> 							io:format("lookupNickInChannel:whisper received unexpected case:~p~n",[Any])
> 					end;
> 				emote ->
> 					{Nick,Channel,Msg,Args} = list_to_tuple(Args),
> 					case lookupChannel(Channel,ChanList) of
> 						{ok, Pid} ->
> 							Pid ! {Connection, {emote, Nick, Msg}};
> 						channel_not_found ->
> 							self() ! {client, Connection, {join,[Nick|[Channel]]}},
> 							self() ! {client, Connection, {emote,[Nick,Channel|[Msg]]}};
> 						Any ->
> 							io:format("lookupChannel:emote received unexpected case:~p~n",[Any])
> 					end;
> 				part ->
> 					% find connection return a channel
> 					UserChanList = lookupConnectionInChannel(Connection,ChanList,[]),
> 					case length(UserChanList) > 1 of
> 						{ok,Pid} ->
> 							% send message to channel
> 							Pid ! {Connection,{part}};
> 						_Any ->
> 							send(Connection, {denied_part, "To leave this channel, you must quit."}),
> 							io:format("lookupConnectionInChannel:quit could not find a Connection that was supposed to be dropped~n",[])
> 					end;
> 				quit ->
> 					% find connection return a channel
> 					case lookupConnectionInChannel(Connection,ChanList,[]) of
> 						{ok,Pid} ->
> 							Pid ! {Connection,{quit}};
> 						_Any ->
> 							io:format("lookupConnectionInChannel:quit could not find a Connection that was supposed to be dropped~n",[])
> 					end
> 			end,
> 			server_loop([ChanList]);
> 			
> 		{client_closed, _Connection, _Why} ->
> 			server_loop(ChanList);
> 			
> 		{channel_empty, Pid} ->
> 			ChanList1 = remove_channel(Pid,ChanList),
> 			server_loop(ChanList1);
> 			
> 		Msg ->
> 			io:format("Chat Server received unexpected Msg=~p~n",[Msg]),
> 			server_loop(ChanList)
> 	end.
> 
> % If Channel matched the head term in the list, return {ok, Pid, UserList} if not full.
> lookupChannel(Channel,[{Channel,Pid,UserList}|_]) ->
> 	case UserList > 50 of
> 		true ->
> 			channel_full;
> 		false ->
> 			{ok,Pid}
> 	end;
> % If Channel does not match the head term in the list, go to next chunk
> lookupChannel(Channel,[_|ChanList]) ->
> 	lookupChannel(Channel,ChanList);
> % If lookup is called on an empty list, regardless of the pattern to match, return error
> lookupChannel(_,[]) ->
> 	channel_not_found.
> 	
> remove_channel(Pid,[{G,Pid,_UserList}|T]) ->
> 	io:format("~p removed~n",[G]),
> 	T;
> remove_channel(Pid,[H|T]) ->
> 	[H|remove_channel(Pid,T)];
> remove_channel(_,[]) ->
> 	[].
> 
> % If Nick matched the head term in the list, return {ok, Pid, UserList}
> lookupNickInChannel(Nick,[{Channel,Pid,UserList}|T]) ->
> 	case lookupNick(Nick, UserList) of
> 		nick_found ->
> 			{ok,Pid,Channel};
> 		% If Nick is not in the head term in the list, go to next chunk (what's left is called the Tail or T)
> 		nick_not_found ->
> 			lookupNickInChannel(Nick,T)
> 	end;
> % If lookup is called on an empty list, regardless of the pattern to match, return error
> lookupNickInChannel(_,[]) ->
> 	user_cannot_be_found.
> 
> lookupNick(Nick,[{Nick,_Connection}|_UserList]) ->
> 	nick_found;
> lookupNick(Nick,[_|UserList]) ->
> 	lookupNick(Nick,UserList);
> lookupNick(_,[]) ->
> 	nick_not_found.
> 	
> % If Connection matched the head term in the list, return {ok, Pid, UserList}
> lookupConnectionInChannel(Connection,[{_Channel,Pid,UserList}|T],UserChannelPids) ->
> 	case lookupConnection(Connection, UserList) of
> 		% If Connection is in the head term in the list, add to aggregate and go to next chunk (what's left is called the Tail or T)
> 		connection_found ->
> 			lookupConnectionInChannel(Connection,T,[Pid|UserChannelPids]);
> 		% If Connection is not in the head term in the list, go to next chunk (what's left is called the Tail or T)
> 		connection_not_found ->
> 			lookupConnectionInChannel(Connection,T,UserChannelPids)
> 	end;
> % If lookup is called on an empty list, regardless of the pattern to match, return error
> lookupConnectionInChannel(_,[],UserChannelPids) ->
> 	UserChannelPids.
> 	
> lookupConnection(Connection,[{_Nick,Connection}|_UserList]) ->
> 	connection_found;
> lookupConnection(Connection,[_|UserList]) ->
> 	lookupNick(Connection,UserList);
> lookupConnection(_,[]) ->
> 	connection_not_found.	
> 
> send(_Connection, _OutgoingMessage) ->
> 	ok.
> 	
> %%-------------------------------------------------------------------------
> %% @spec (Request, From, State) -> {reply, Reply, State}          |
> %%                                 {reply, Reply, State, Timeout} |
> %%                                 {noreply, State}               |
> %%                                 {noreply, State, Timeout}      |
> %%                                 {stop, Reason, Reply, State}   |
> %%                                 {stop, Reason, State}
> %% @doc Callback for synchronous server calls.  If `{stop, ...}' tuple
> %%      is returned, the server is stopped and `terminate/2' is called.
> %% @end
> %% @private
> %%-------------------------------------------------------------------------
> handle_call(Request, _From, State) ->
>     {stop, {unknown_call, Request}, State}.
> 
> %%-------------------------------------------------------------------------
> %% @spec (Msg, State) ->{noreply, State}          |
> %%                      {noreply, State, Timeout} |
> %%                      {stop, Reason, State}
> %% @doc Callback for asyncrous server calls.  If `{stop, ...}' tuple
> %%      is returned, the server is stopped and `terminate/2' is called.
> %% @end
> %% @private
> %%-------------------------------------------------------------------------
> handle_cast(_Msg, State) ->
>     {noreply, State}.
>     
> %%-------------------------------------------------------------------------
> %% @spec (Reason, State) -> any
> %% @doc  Callback executed on server shutdown. It is only invoked if
> %%       `process_flag(trap_exit, true)' is set by the server process.
> %%       The return value is ignored.
> %% @end
> %% @private
> %%-------------------------------------------------------------------------
> terminate(Reason, State) ->
>     io:format("Terminated~nReason:~p~nState:~p~n",[Reason,State]),
>     ok.
> 
> %%-------------------------------------------------------------------------
> %% @spec (OldVsn, State, Extra) -> {ok, NewState}
> %% @doc  Convert process state when code is changed.
> %% @end
> %% @private
> %%-------------------------------------------------------------------------
> code_change(_OldVsn, State, _Extra) ->
>     {ok, State}.
>     
> handle_info(_Info, State) ->
>     {noreply, State}.
> 
> 
> The problem was that I had no start_link method at all. After adding it, the application just hangs. Normally I would see the supervisor tree in appmon, but currently it doesn't appear and I only have access to the sub-console (cntrl-g) until I kill the job. Trying to connect to the tcp_server results in a crash that I don't understand.
> 
> =ERROR REPORT==== 1-Sep-2008::13:08:01 ===
> ** Generic server tcp_listener terminating 
> ** Last message in was {inet_async,#Port<0.127>,1,{ok,#Port<0.140>}}
> ** When Server state == {state,#Port<0.127>,1,prechat_fsm}
> ** Reason for termination == 
> ** {{badmatch,
>         {error,
>             {undef,
>                 [{tcp_server_app,start_chat_server,[]},
>                  {prechat_fsm,init,1},
>                  {gen_fsm,init_it,6},
>                  {proc_lib,init_p,5}]}}},
>     [{tcp_listener,handle_info,2},
>      {gen_server,handle_msg,5},
>      {proc_lib,init_p,5}]}
> 
> --- On Mon, 9/1/08, Ulf Wiger (TN/EAB) <> wrote:
> 
>> From: Ulf Wiger (TN/EAB) <>
>> Subject: Re: [erlang-questions] Learning OTP - Application, Supervisor, FSM, Server
>> To: 
>> Cc: 
>> Date: Monday, September 1, 2008, 3:37 AM
>> Jeff Crane skrev:
>>
>>  > My chat_server.erl is a gen_server, and I think that
>> if
>>  > someone can explain this last bit of the child
>>  > specification, I might understand why I'm getting
>>  > this at runtime (startup):
>>  > =INFO REPORT==== 1-Sep-2008::00:39:12 ===
>> application: tcp_server
>>  > exited: {shutdown,{tcp_server_app,start,[normal,[]]}}
>> type: temporary
>>  > 
>> {error,{shutdown,{tcp_server_app,start,[normal,[]]}}}
>>  >
>>  > Apologies for the inability to read specs, but I just
>> don't
>>  > understand.>
>>
>> That particular error message is one of the most annoying
>> messages produced by Erlang/OTP IMHO. It really doesn't
>> tell you anything except which application failed to start.
>>
>> I believe it's due to an error caught in
>> supervisor:do_start_child(). The error reason isn't
>> propagated,
>> but if you want to see it, you should make sure that the
>> 'sasl'
>> application is started. That should give you an error
>> report
>> from the supervisor, telling you what went wrong.
>>
>> So instead of just typing application:start(tcp_server),
>> try:
>>
>> application:start(sasl).
>> application:start(tcp_server).
>>
>> (or start erlang with erl -boot start_sasl)
> 
> 
> 
>       
> _______________________________________________
> erlang-questions mailing list
> 
> http://www.erlang.org/mailman/listinfo/erlang-questions
> 




More information about the erlang-questions mailing list