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

Ulf Wiger (TN/EAB) ulf.wiger@REDACTED
Mon Sep 1 23:27:04 CEST 2008


This code doesn't follow the guidelines for generic
servers.

The easiest way to fix this is probably to:

- remove the -behaviour(gen_server).
- change the start_link/0 function to

start_link() ->
   proc_lib:spawn_link(
      fun() ->
          process_flag(trap_exit, true),
          server_loop([])
      end).

I haven't really checked the rest of the code, but
I did see one fishy line in server_loop/1:

 >	server_loop([ChanList]);

I assume that you intended to add an item to the list?

BR,
Ulf W

Jeff Crane skrev:
> 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) <ulf.wiger@REDACTED> wrote:
> 
>> From: Ulf Wiger (TN/EAB) <ulf.wiger@REDACTED>
>> Subject: Re: [erlang-questions] Learning OTP - Application, Supervisor, FSM, Server
>> To: jefcrane@REDACTED
>> Cc: erlang-questions@REDACTED
>> 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
> erlang-questions@REDACTED
> http://www.erlang.org/mailman/listinfo/erlang-questions



More information about the erlang-questions mailing list