[erlang-questions] Learning OTP - Application, Supervisor, FSM, Server
Jeff Crane
jefcrane@REDACTED
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) <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)
More information about the erlang-questions
mailing list