[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