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

Jeff Crane <>
Mon Sep 1 22:14:11 CEST 2008


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)



      



More information about the erlang-questions mailing list