[thrift] improved error logging and handling for Erlang bindings

Summary:
 * custom, extensible error logger -- show only relevant stuff
 * clean up of errors in developer-supplied handler module
   now gives sane error messages and doesn't crash whole server
   (introduces tApplicationException_HANDLER_ERROR)
 * more precise catch in tErlProcessor (exits gracefully only if
   transport closes)

Reviewed By: iproctor

Test Plan: tutorial works

Revert Plan: ok


git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@665186 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Christopher Piro 2007-08-01 22:27:37 +00:00
parent 99e2c07815
commit 5b3a8f7a8b
18 changed files with 603 additions and 210 deletions

View File

@ -764,6 +764,7 @@ void t_erl_generator::generate_service_client(t_service* tservice) {
indent() << " throw(X);" << endl <<
indent() << " true ->" << endl <<
indent() << " Result = " << resultname << "_read(Iprot)," << endl <<
indent() << " Result, % suppress unused warnings" << endl <<
indent() << " ?R0(Iprot, readMessageEnd)," << endl <<
indent() << " if % time to figure out retval" << endl;
@ -960,6 +961,9 @@ void t_erl_generator::generate_process_function(t_service* tservice,
const std::vector<t_field*>& fields = arg_struct->get_members();
vector<t_field*>::const_iterator f_iter;
indent(f_service_) << "try" << endl;
indent_up();
indent(f_service_) << "Result = ";
if (xceptions.size() > 0) {
f_service_ << "try" << endl;
@ -1004,18 +1008,46 @@ void t_erl_generator::generate_process_function(t_service* tservice,
}
indent(f_service_) << "end," << endl;
if (tfunction->is_async()) {
indent(f_service_) << "% async, write nothing" << endl;
} else {
if (!tfunction->is_async()) {
f_service_ <<
indent() << "?R3(Oprot, writeMessageBegin, \"" << tfunction->get_name() << "\", ?tMessageType_REPLY, Seqid)," << endl <<
indent() << tfunction->get_name() << "_result_write(Result, Oprot)," << endl <<
indent() << "?R0(Oprot, writeMessageEnd)," << endl <<
indent() << tfunction->get_name() << "_result_write(Result, Oprot)," << endl;
}
indent(f_service_) << "Result" << endl;
indent_down();
// catch errors in the handler
indent(f_service_) << "catch" << endl <<
indent() << " _:Kind when Kind == undef; Kind == function_clause ->" << endl <<
indent() << " X = tApplicationException:new(?tApplicationException_HANDLER_ERROR, \"Handler doesn't implement "
<< tfunction->get_name() <<"\")," << endl <<
indent() << " ?R3(Oprot, writeMessageBegin, \"" << tfunction->get_name() << "\", ?tMessageType_EXCEPTION, Seqid)," << endl <<
indent() << " tApplicationException:write(X, Oprot)," << endl <<
indent() << " {error, X};" << endl <<
indent() << " _:_Kind ->" << endl <<
indent() << " X = tApplicationException:new(?tApplicationException_HANDLER_ERROR, \"Unknown handler error in "
<< tfunction->get_name() << "\")," << endl <<
indent() << " ?R3(Oprot, writeMessageBegin, \"" << tfunction->get_name() << "\", ?tMessageType_EXCEPTION, Seqid)," << endl <<
indent() << " tApplicationException:write(X, Oprot)," << endl <<
indent() << " {error, X}" << endl;
// 'after' block if we're expecting a result written
if (!tfunction->is_async()) {
f_service_ <<
indent() << "after" << endl;
indent_up();
indent(f_service_) << "?R0(Oprot, writeMessageEnd)," << endl <<
indent() << "Trans = ?R1(Oprot, get, trans)," << endl <<
indent() << "?R0(Trans, effectful_flush)," << endl;
indent() << "?R0(Trans, effectful_flush)" << endl;
indent_down();
}
indent(f_service_) << "Result." << endl << endl;
indent(f_service_) << "end." << endl;
indent_down();
}

View File

@ -1,10 +1,36 @@
make thrift a proper OTP application
* app-wide configuration (do DNS lookups?)
* default protocols / transports (forget this factory business)
* factor for elegance
tutorial client
find TODO(cpiro)s
make all methods effectful, remove the special casing (optionally, implement monads for Erlang)
inheritance
change objects from {record_tag, ...} to {oop_object, {record_tag, ...}, other_useful_stuff}
so 1) we know exactly what's an object (can write is_object/1) e.g.
is the tuple {tTransportException, ...} an object or a tuple that happens to start with that atom?
we can't check this using is_record/2 without include every header file
also, this makes it easy to pick objects out of deep tuples
2) we can build more functionality into oop later if need be
carry around the class/superclasses so is_a(Object, ClassOrSuperclass) is easy
3) maybe hack up io:format and friends to run objects through oop:inspect automatically
test suites
move as much as possible out of thrift_logger
make thrift_logger 100% robust
thrift_logger detects term width?
undisgustify codegen
move away from thrift_oop_server shim to straight-up gen_servers
move away from Factories
move away from ?L0, ?M0, and friends ... make calls in oop
better/worse yet, make an absyn transformer and introduce some slick syntax to make it less painful and more obvious what's going on
ONLY IF our gentle nudging away from oop and thrift_oop_server isn't good enough

View File

@ -11,5 +11,6 @@
-define(tApplicationException_WRONG_METHOD_NAME, 3).
-define(tApplicationException_BAD_SEQUENCE_ID, 4).
-define(tApplicationException_MISSING_RESULT, 5).
-define(tApplicationException_HANDLER_ERROR, 6).
-record(tApplicationException, {super, type}).

View File

@ -4,6 +4,11 @@
%%% See accompanying file LICENSE or visit the Thrift site at:
%%% http://developers.facebook.com/thrift/
-define(ERROR(F, D),
error_logger:format(F, D)).
-define(INFO(Type, Report),
error_logger:info_report({thrift_info, Type}, Report)).
% local (same process)
-define(L0(Method), oop:call(This, Method, [])).
-define(L1(Method, Arg1), oop:call(This, Method, [Arg1])).

View File

@ -0,0 +1,7 @@
%%% Copyright (c) 2007- Facebook
%%% Distributed under the Thrift Software License
%%%
%%% See accompanying file LICENSE or visit the Thrift site at:
%%% http://developers.facebook.com/thrift/
-record(thrift_logger_state, {term_width, force_one_line, omit, gen_server_messages, lookup}).

View File

@ -8,4 +8,4 @@ fi
echo "Compiling user/ and tutorial/gen-erl/..."
mkdir ebin-user
erlc -I include -I tutorial/gen-erl -o ebin-user user/*.erl tutorial/gen-erl/*.erl &&
erl -pa ebin -pa ebin-user -s application start thrift # -s nh start
erl +K true -pa ebin -pa ebin-user

View File

@ -6,9 +6,10 @@
-module(oop).
-export([get/2, set/3, call/2, call/3, inspect/1, start_new/2]).
-export([get/2, set/3, call/2, call/3, inspect/1, start_new/2, is_object/1, class/1]).
-export([behaviour_info/1]).
-include("thrift.hrl").
-include("oop.hrl").
%%%
@ -77,8 +78,19 @@ get_superobject(Obj) ->
none
end.
is_object(Obj) when is_tuple(Obj) ->
try
(?CLASS(Obj)):super(), %% if it's an object its first element will be a class name, and it'll have super/0
true
catch
error:Kind when Kind == undef; Kind == function_clause ->
false
end;
is_object(_) ->
false.
call(Obj, Method, ArgsProper) ->
%% io:format("call called: Obj=~p Method=~p ArgsProper=~p~n", [oop:inspect(Obj), Method, ArgsProper]),
%% error_logger:info_msg("call called: Obj=~p Method=~p ArgsProper=~p", [inspect(Obj), Method, ArgsProper]),
Args = [Obj|ArgsProper], %% prepend This to args
TryModule = ?CLASS(Obj),
call_loop(Obj, Method, Args, TryModule, [], Obj).
@ -88,7 +100,7 @@ call(Obj, Method) ->
call_loop(Obj, Method, Args, TryModule, TriedRev, FirstObj) ->
try
%% io:format("call_loop~n ~p~n ~p~n ~p~n ~p~n ~n", [inspect(Obj), Method, Args, TryModule]),
%% error_logger:info_msg("call_loop~n ~p~n ~p~n ~p~n ~p", [inspect(Obj), Method, Args, TryModule]),
apply(TryModule, Method, Args)
catch
error:Kind when Kind == undef; Kind == function_clause ->
@ -121,26 +133,52 @@ call_loop(Obj, Method, Args, TryModule, TriedRev, FirstObj) ->
end
end.
class(Obj) when is_tuple(Obj) ->
case is_object(Obj) of
true ->
?CLASS(Obj);
false ->
none
end;
class(_) ->
none.
%% careful: not robust against records beginning with a class name
%% (note: we can't just guard with is_record(?CLASS(Obj), Obj) since we
%% can't/really really shouldn't require all record definitions in this file
inspect(Obj) ->
DeepList = inspect_loop(Obj, "#<"),
lists:flatten(DeepList).
try
case is_object(Obj) of
true ->
DeepList = inspect_loop(Obj, "#<"),
lists:flatten(DeepList);
false ->
thrift_utils:sformat("~p", [Obj])
end
catch
_:E ->
thrift_utils:sformat("INSPECT_ERROR(~p) ~p", [E, Obj])
%% TODO(cpiro): bring this back once we're done testing:
%% _:E -> thrift_utils:sformat("~p", [Obj])
end.
inspect_loop(Obj, Str) ->
New =
atom_to_list(?CLASS(Obj)) ++
": " ++
(?CLASS(Obj)):inspect(Obj),
Class = ?CLASS(Obj),
Inspect = Class:inspect(Obj),
Current = atom_to_list(Class) ++ ": " ++ Inspect,
case get_superobject(Obj) of
{ ok, Superobj } ->
inspect_loop(Superobj, Str ++ New ++ " | ");
inspect_loop(Superobj, Str ++ Current ++ " | ");
none ->
Str ++ New ++ ">"
Str ++ Current ++ ">"
end.
%% TODO: voids take only ok as return?
start_new(none=Resv, _) ->
error_logger:format("can't instantiate ~p: class name is a reserved word", [Resv]),
error;
start_new(Class, Args) ->
case gen_server:start_link(thrift_oop_server, {Class, Args}, []) of
{ok, Pid} ->
Pid
end.
{ok, Pid} = gen_server:start_link(thrift_oop_server, {Class, Args}, []),
Pid.

View File

@ -69,13 +69,18 @@ effectful_serve(This) ->
%% listen
case gen_tcp:listen(Port, Options) of
{ok, ListenSocket} ->
?INFO(server_listening, {Port}),
This1 = oop:set(This, listenSocket, ListenSocket),
%% spawn acceptor
{_Acceptor, This2} = effectful_new_acceptor(This1),
{ok, This2}
{ok, This2};
{error, eaddrinuse} ->
error_logger:format("couldn't bind port ~p, address in use", [Port]),
{{error, eaddrinuse}, This} %% state before the accept
end.
effectful_new_acceptor(This) ->
@ -96,7 +101,7 @@ effectful_new_acceptor(This) ->
{Acceptor, This1}.
catches(This, Pid, acceptor_done) ->
catches(_This, _Pid, normal) ->
ok.
%% %% The current acceptor has died, wait a little and try again %%

View File

@ -4,6 +4,9 @@
%%% See accompanying file LICENSE or visit the Thrift site at:
%%% http://developers.facebook.com/thrift/
%%% NOTE: tSimpleServer's design isn't compatible with our concurrency model.
%%% It won't work in principle, and certainly not in practice. YMMV.
-module(tSimpleServer).
-include("oop.hrl").
@ -46,6 +49,7 @@ inspect(_This) ->
new(Handler, Processor, ServerTransport, TransportFactory, ProtocolFactory) ->
Super = (super()):new(Handler, Processor, ServerTransport, TransportFactory, ProtocolFactory),
error_logger:warning_msg("tSimpleServer has an incompatable design and doesn't work. Promise."),
#?MODULE{super=Super}.
new(Handler, Processor, ServerTransport) ->
@ -57,13 +61,14 @@ new(Handler, Processor, ServerTransport, TransportFactory) ->
%
serve(This) ->
exit(tSimpleServer_doesnt_work),
ST = oop:get(This, serverTransport),
?R0(ST, effectful_listen),
serve_loop(This).
serve_loop(This) ->
io:format("~nready.~n", []),
error_logger:info_msg("ready.", []),
ST = oop:get(This, serverTransport),
Client = ?RT0(ST, accept, infinity),
@ -74,7 +79,7 @@ serve_loop(This) ->
PF = oop:get(This, protocolFactory),
Prot = ?F1(PF, getProtocol, Trans), %% cpiro: OPAQUE!! Prot = start_new(tBinaryProtocol, [Trans])
io:format("client accept()ed~n", []),
error_logger:info_msg("client accept()ed", []),
serve_loop_loop(This, Prot), % giggle loop?
@ -88,16 +93,16 @@ serve_loop_loop(This, Prot) ->
Handler = oop:get(This, handler),
Processor = oop:get(This, processor),
Val = apply(Processor, process, [Handler, Prot, Prot]), %% TODO(cpiro): make processor a gen_server instance
io:format("request processed: rv=~p~n", [Val]),
error_logger:info_msg("request processed: rv=~p", [Val]),
loop
catch
%% TODO(cpiro) case when is_record(...) to pick out our exception
%% records vs. normal erlang throws
E when is_record(E, tTransportException) ->
io:format("tTransportException (normal-ish?)~n", []),
error_logger:info_msg("tTransportException (normal-ish?)", []),
close;
F ->
io:format("EXCEPTION: ~p~n", [F]),
error_logger:info_msg("EXCEPTION: ~p", [F]),
close
end,
case Next of

View File

@ -6,6 +6,7 @@
-module(tErlProcessor).
-include("thrift.hrl").
-include("oop.hrl").
-include("tErlProcessor.hrl").

View File

@ -0,0 +1,25 @@
%%% Copyright (c) 2007- Facebook
%%% Distributed under the Thrift Software License
%%%
%%% See accompanying file LICENSE or visit the Thrift site at:
%%% http://developers.facebook.com/thrift/
-module(thrift).
-export([start/2, shutdown/0, stop/1]).
-behaviour(application).
-include("thrift.hrl").
%%%
%%% behavior definition
%%%
start(Type, StartArgs) ->
ok.
shutdown() ->
application:stop(?MODULE).
stop(_State) ->
ok.

View File

@ -0,0 +1,282 @@
%%% Copyright (c) 2007- Facebook
%%% Distributed under the Thrift Software License
%%%
%%% See accompanying file LICENSE or visit the Thrift site at:
%%% http://developers.facebook.com/thrift/
-module(thrift_logger).
-behaviour(gen_event).
-include("thrift_logger.hrl").
%% gen_event callbacks
-export([init/1, handle_event/2, handle_call/2,
handle_info/2, terminate/2, code_change/3]).
-export([install/0, install/1]).
%% ensure the regular logger is out and ours is in
install() ->
install([]).
install(Args) ->
%% remove loggers
lists:foreach(fun(Logger) ->
case Logger of
_ -> gen_event:delete_handler(error_logger, Logger, normal)
end end,
gen_event:which_handlers(error_logger)),
%% TODO(cpiro): sasl someday?
%% gen_event:add_handler(error_logger, sasl_report_file_h, {LogFile, all}),
gen_event:add_handler(error_logger, ?MODULE, Args).
%% how to output
format(Format, Data) ->
io:format(Format, Data).
%% convenience
sformat(Format, Data) ->
thrift_utils:sformat(Format, Data).
%%====================================================================
%% gen_event callbacks
%%====================================================================
%%--------------------------------------------------------------------
%% @spec init(Args) -> {ok, State}.
%%
%% @doc
%% Whenever a new event handler is added to an event manager,
%% this function is called to initialize the event handler.
%% @end
%%--------------------------------------------------------------------
init([]) ->
{ok, #thrift_logger_state{
term_width = 110,
force_one_line = true,
omit = [oop_new], % req_processed
gen_server_messages = false,
lookup = true
}};
init([State]) ->
{ok, State}.
%%--------------------------------------------------------------------
%% @spec handle_event(Event, State) -> {ok, State} |
%% {swap_handler, Args1, State1, Mod2, Args2} |
%% remove_handler.
%%
%% @doc
%% Whenever an event manager receives an event sent using
%% gen_event:notify/2 or gen_event:sync_notify/2, this function is called for
%% each installed event handler to handle the event.
%% @end
%%--------------------------------------------------------------------
handle_event2(Symbol, Pid, Type, Message, State) -> % Message must be a string
{ok, MessageSafe, NL} = regexp:gsub(Message, "[\n]+", " "), % collapse whitespace to one space
Type1 =
case Type of
"" -> "";
_ -> sformat("~p ", [Type])
end,
Banner = sformat("~s ~p ~s", [Symbol, Pid, Type1]),
BannerLen = length(Banner),
{Output, OutputSafe} =
try %% there's no way to see if Message is a string? just try
{sformat("~s", [Message]),
sformat("~s", [MessageSafe])}
catch X -> why_doesnt_this_work
end,
Length =
case (length(OutputSafe) + BannerLen) < State#thrift_logger_state.term_width of
true -> short;
false -> long
end,
OneLine =
case NL == 0 of
true -> oneliner;
false -> multiline
end,
case { State#thrift_logger_state.force_one_line, Length, OneLine } of
%% one line and short ... print as is
{_, short, oneliner} ->
format("~s~s~n", [Banner, OutputSafe]);
%% too long ... squash to one
{true, long, _} ->
O = Banner ++ OutputSafe,
Format = sformat("~~~ps >~n", [State#thrift_logger_state.term_width-2]), % e.g. "~80s >~n"
format(Format, [O]);
%% short but multiline... collapse to one
{true, short, multiline} ->
format("~s~s~n", [Banner, OutputSafe]);
%% just print it
_ ->
format("~s~n~s~n~n", [Banner, Output])
end.
%%
handle_event1({What, _Gleader, {Pid, Format, Data}}, State) when is_list(Format) ->
Symbol = case What of
error -> "!!";
warning_msg -> "**";
info_msg -> "..";
_Else -> "??"
end,
case Format of
"** Generic server ~p terminating \n** Last message in was ~p~n** When Server state == ~p~n** Reason for termination == ~n** ~p~n" ->
%% v- Pid is a pattern match, not a bind
[Pid, LastMessage, Obj, Reason] = Data,
%% TODO: move as much logic as possible out of thrift_logger
Ignore = error /= thrift_utils:unnest_record(Reason, tTransportException),
case Ignore of
true ->
ok;
false ->
Format1 = "** gen_server terminating in message ~p~n** State = ~s~n** Reason = ~s~n",
Message = sformat(Format1, [LastMessage, oop:inspect(Obj), oop:inspect(Reason)]), %% TODO(cpiro): hope Reason is an object?
handle_event2(Symbol, Pid, "", Message, State)
end;
_ ->
Message = sformat(Format, Data),
handle_event2(Symbol, Pid, "", Message, State)
end,
{ok, State};
handle_event1({What, _Gleader, {Pid, Type, Report}}, State) ->
Symbol = case What of
error_report -> "!!";
warning_report -> "**";
info_report -> "..";
_Else -> "??"
end,
case Type of
{thrift_info, TI} ->
%% should we show it?
case not lists:member(TI, State#thrift_logger_state.omit) of
true ->
Message = handle_thrift_info(TI, Report, State),
handle_event2(Symbol, Pid, "", Message, State);
false ->
ok
end;
crash_report ->
%% [Cruft|_] = Report, %%
%% {value, {_, Reason}} = lists:keysearch(error_info, 1, Cruft), %%
%% {value, {_, {_, _, [_,_,_,_, Obj, []]}}} = lists:keysearch(initial_call, 1, Cruft), %%
%% sformat("state == ~s~nreason ==~s", [oop:inspect(Obj), oop:inspect(Reason)]), %%
ok;
progress ->
ok;
_ ->
Message = sformat("|| ~s", [oop:inspect(Report)]),
handle_event2(Symbol, Pid, Type, Message, State)
end,
{ok, State};
handle_event1(_Event, State) ->
handle_event2("??", "<?.?.?>", "", _Event, State),
{ok, State}.
handle_event(Event, State) ->
try
handle_event1(Event, State)
catch
_:E ->
format("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~n error logger error:~n ~p~n Event = ~p~n State = ~p~n ~p~n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~n",
[E, Event, State, erlang:get_stacktrace()]),
{ok, State}
end.
%% thrift info handlers
handle_thrift_info(oop_new, {Args, Class, Object}, State) ->
%% arg Class can't come first! Then it'd look like a Class object
L = io_lib:format("~p:new(~s) = ~s", [Class, thrift_utils:unbrack(Args), oop:inspect(Object)]),
lists:flatten(L);
handle_thrift_info(server_listening, {Port}, State) ->
sformat("server listening on port ~p", [Port]);
handle_thrift_info(req_processed, {Value}, State) ->
sformat("request: ~p", [Value]);
handle_thrift_info(conn_accepted, {AddrString}, State) ->
sformat("connection accepted from ~s", [AddrString]);
handle_thrift_info(conn_closed, {AddrString}, State) ->
sformat("connection closed from ~s", [AddrString]);
handle_thrift_info(Else, Report, State) ->
sformat("~p ~s", [Else, oop:inspect(Report)]).
%%--------------------------------------------------------------------
%% @spec handle_call(Request, State) -> {ok, Reply, State} |
%% {swap_handler, Reply, Args1, State1,
%% Mod2, Args2} |
%% {remove_handler, Reply}.
%%
%% @doc
%% Whenever an event manager receives a request sent using
%% gen_event:call/3,4, this function is called for the specified event
%% handler to handle the request.
%% @end
%%--------------------------------------------------------------------
handle_call(_Request, State) ->
Reply = ok,
{ok, Reply, State}.
%%--------------------------------------------------------------------
%% @spec handle_info(Info, State) -> {ok, State} |
%% {swap_handler, Args1, State1, Mod2, Args2} |
%% remove_handler.
%%
%% @doc
%% This function is called for each installed event handler when
%% an event manager receives any other message than an event or a synchronous
%% request (or a system message).
%% @end
%%--------------------------------------------------------------------
handle_info(_Info, State) ->
{ok, State}.
%%--------------------------------------------------------------------
%% @spec terminate(Reason, State) -> void().
%%
%% @doc
%% Whenever an event handler is deleted from an event manager,
%% this function is called. It should be the opposite of Module:init/1 and
%% do any necessary cleaning up.
%% @end
%%--------------------------------------------------------------------
terminate(normal, _State) ->
ok;
terminate(Reason, _State) ->
format("*****************~n~n frick, error logger terminating: ~p~n~n*****************~n~n", [Reason]),
ok.
%%--------------------------------------------------------------------
%% @spec code_change(OldVsn, State, Extra) -> {ok, NewState}.
%%
%% @doc
%% Convert process state when code is changed
%% @end
%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%====================================================================
%%% Internal functions
%%====================================================================

View File

@ -72,21 +72,16 @@ stop() ->
%% {stop, Reason}
%%--------------------------------------------------------------------
unparenth(Args) ->
Args.
init({Class, Args}) ->
process_flag(trap_exit, true),
if
true -> % lists:member(Class, Class:interface(subclasses)) ->
io:format("oop_server init: ~p := ~p:new(~p)~n", [self(), Class, unparenth(Args)]),
State = apply(Class, new, Args), % TODO(cpiro): try catch?
io:format(" =>~p~n", [oop:inspect(State)]),
{ok, State}
%% true -> %%
%% {stop, invalid_subclass} %%
try
State = apply(Class, new, Args),
?INFO(oop_new, {Args, Class, State}),
{ok, State}
catch
E -> {stop, {new_failed, E}}
end;
init(_) ->
{stop, invalid_params}.
@ -126,8 +121,8 @@ handle_cast({Method, Args}, State) ->
).
handle_either(Type, Request, From, State) ->
%% io:format("~p: ~p~n", [?SERVER, oop:inspect(State)]),
%% io:format(" handle_call(Request=~p, From=~p, State)~n", [Request, From]),
%% error_logger:info_msg("~p: ~p", [?SERVER, oop:inspect(State)]),
%% error_logger:info_msg("handle_call(Request=~p, From=~p, State)", [Request, From]),
case Request of
{get, [Field]} ->
@ -138,11 +133,14 @@ handle_either(Type, Request, From, State) ->
State1 = oop:set(State, Field, Value),
?REPLY(Value, State1);
{class, []} ->
?REPLY(?CLASS(State), State);
{Method, Args} ->
handle_method(Type, State, Method, Args);
_ ->
io:format(" ERROR: Request = ~p nomatch {Method, Args}~n", [Request]),
error_logger:format("no match for Request = ~p", [Request]),
%% {stop, server_error, State}
{reply, server_error, State}
end.
@ -160,8 +158,8 @@ handle_method(Type, State, Method, Args) ->
{true, _MalformedReturn} ->
%% TODO(cpiro): bad match -- remove when we're done converting
io:format(" ERROR: oop:call(effectful_*,..,..) malformed return value ~p~n",
[_MalformedReturn]),
error_logger:format("oop:call(effectful_*,..,..) malformed return value ~p",
[_MalformedReturn]),
%% {stop, server_error, State}
{noreply, State};
@ -177,18 +175,23 @@ handle_method(Type, State, Method, Args) ->
%% {stop, Reason, State} (terminate/2 is called)
%%--------------------------------------------------------------------
handle_info({'EXIT', Pid, Except} = All, State) ->
case catch oop:call(State, catches, [Pid, Except]) of
{'EXIT', MM} when element(1,MM) == missing_method ->
io:format("UNHANDLED ~p by ~p!~n", [All, self()]),
%% not caught
Result = try
oop:call(State, catches, [Pid, Except])
catch
exit:{missing_method, _} ->
unhandled
end,
case Result of
unhandled ->
error_logger:format("unhandled exit ~p", [All]),
{stop, All, State};
_IsCaught ->
%% caught and handled
_WasHandled ->
{noreply, State}
end;
handle_info(Info, State) ->
io:format("~p infoED!: ~p~n", [self(), Info]),
error_logger:info_msg("~p", [Info]),
{noreply, State}.
%%--------------------------------------------------------------------
@ -197,7 +200,7 @@ handle_info(Info, State) ->
%% Returns: any (ignored by gen_server)
%%--------------------------------------------------------------------
terminate(Reason, State) ->
io:format("~p terminated!: ~p~n", [self(), Reason]),
%%error_logger:format("~p terminated!: ~p", [self(), Reason]),
ok.
%%--------------------------------------------------------------------

View File

@ -1,11 +1,52 @@
%%% Copyright (c) 2007- Facebook
%%% Distributed under the Thrift Software License
%%%
%%% See accompanying file LICENSE or visit the Thrift site at:
%%% http://developers.facebook.com/thrift/
-module(thrift_utils).
-export([tabulate/2,dict_size/1]).
-include("transport/tTransportException.hrl").
tabulateh(N,M,_) when N==M -> [];
tabulateh(N,M,F) -> [F(N)|tabulateh(N+1,M,F)].
tabulate(N,F) -> tabulateh(0,N,F).
-export([tabulate/2, dict_size/1, sformat/2, unbrack/1, first_item/1, unnest_record/2]).
% makin me sad
%% tabulate
tabulate(N,F) ->
tabulate(0, N, F).
tabulate(N,M,_) when N==M ->
[];
tabulate(N,M,F) ->
[F(N) | tabulate(N+1, M, F)].
%% makin me sad
dict_size(Dict) ->
dict:fold(fun (_,_,I) -> I+1 end,0,Dict).
%% I CAN HAS EAZIER KTHX
sformat(Format, Data) when is_list(Data) ->
lists:flatten(io_lib:format(Format, Data));
sformat(Format, Item) ->
error_logger:warning_msg("sformat called with non-list Data: (~p, ~p)", [Format, Item]),
sformat(Format, [Item]).
%% render a list and pick off the square brackets
unbrack(List) ->
List1 = sformat("~w", [List]),
string:substr(List1, 2, length(List1)-2).
first_item(DeepTuple) ->
case is_tuple(DeepTuple) of
true -> first_item(element(1, DeepTuple));
false -> DeepTuple
end.
unnest_record(Term, RecordTag) ->
case is_record(Term, RecordTag) of
true ->
{ok, Term};
false when is_tuple(Term) ->
unnest_record(element(1, Term), RecordTag);
_ ->
error
end.

View File

@ -12,6 +12,8 @@
-include("transport/tServerSocket.hrl").
-include("transport/tErlAcceptor.hrl").
-include_lib("kernel/include/inet.hrl").
-behavior(oop).
-export([attr/4, super/0, inspect/1]).
@ -62,15 +64,15 @@ new(ServerPid, TF, PF) ->
%%%
accept(This, ListenSocket, GP, Handler) ->
io:format("acceptor started~n",[]),
ServerPid = oop:get(This, serverPid),
case catch gen_tcp:accept(ListenSocket) of
{ok, Socket} ->
?C0(ServerPid, effectful_new_acceptor), %% cast to create new acceptor
AddrString = render_addr(Socket),
?INFO(conn_accepted, {AddrString}),
%% start_new(tSocket, [])
Client = oop:start_new(tSocket, []),
?R1(Client, effectful_setHandle, Socket), %% TODO(cpiro): should we just let this be a param to the constructor?
@ -88,139 +90,51 @@ accept(This, ListenSocket, GP, Handler) ->
receive_loop(This, Processor, Prot, Prot),
exit(acceptor_done); %% TODO(cpiro): grace?
?INFO(conn_closed, {AddrString}),
exit(normal);
Else ->
R = lists:flatten(
io_lib:format("accept() failed: ~p", [Else])),
R = thrift_utils:sformat("accept() failed: ~p", [Else]),
exit(tTransportException:new(R))
end.
receive_loop(This, Processor, Iprot, Oprot) ->
case catch ?R2(Processor, process, Iprot, Oprot) of
{'EXIT', X} ->
io:format("Acceptor: we gotta ~p~n", [X]);
Value ->
io:format("request processed: rv=~p~n", [Value]),
receive_loop(This, Processor, Iprot, Oprot)
try
Value = ?R2(Processor, process, Iprot, Oprot),
?INFO(req_processed, {Value}),
receive_loop(This, Processor, Iprot, Oprot)
catch
%% the following clause must be last because we might reexit
%% cpiro: breaks if it's a subclass of tTransportException
%% since unnest_record knows nothing about oop
exit:Else ->
case thrift_utils:unnest_record(Else, tTransportException) of
{ok, TTE} when TTE#tTransportException.type == ?tTransportException_NOT_OPEN ->
ok; %% will exit to tErlAcceptor
_ ->
exit(Else) %% shouldn't have caught it in the first place
end
end.
%%%
%%% error handlers
%%%
%% helper functions
%% end
%% @param Socket the socket in question
%% TODO(cpiro): there probably needs to be a switch for DoLookup somewhere prominent and outside the lib,
%% probably at the "application" level
render_addr(Socket) ->
DoLookup = true,
{ok, {Peer, Port}} = inet:peername(Socket),
%%% old codez
%% effectful_listen(This) -> %%
%% Port = oop:get(This, port), %%
%% Options = [binary, {packet, 0}, {active, false}], % was [] %%
%% %%
%% case gen_tcp:listen(Port, Options) of %%
%% {ok, ListenSocket} -> %%
%% This1 = oop:set(This, handle, ListenSocket), %%
%% {ok, This1} %%
%% %%
%% % {error, _} -> %%
%% % TODO: no error handling in Ruby version? %%
%% end. %%
%% %%
%% accept(This) -> %%
%% case oop:get(This, handle) of %%
%% nil -> %%
%% nil; % cpiro: sic the Ruby code %%
%% %%
%% Handle -> %%
%% case gen_tcp:accept(Handle) of %%
%% {ok, Sock} -> %%
%% Trans = oop:start_new(tSocket, []), %%
%% ?R1(Trans, effectful_setHandle, Sock), %%
%% Trans %%
%% % {error, _} -> %%
%% % TODO: no error handling in Ruby version? %%
%% end %%
%% end. %%
%% %%
%% effectful_close(This) -> %%
%% case oop:get(This, handle) of %%
%% nil -> %%
%% {nil, This}; %%
%% Handle -> %%
%% case gen_tcp:close(Handle) of %%
%% ok -> %%
%% {ok, This} % cpiro: sic the Ruby version: don't set handle to nil %%
%% % {error, _} -> %%
%% % TODO: no error handling in Ruby version? %%
%% end %%
%% end. %%
%%% teh iservez
%% -module(iserve_socket). %%
%% %%
%% -export([start_link/3]). %%
%% %%
%% -export([init/1]). %%
%% -include("iserve.hrl"). %%
%% %%
%% %TEST %%
%% -export([handle_get/2]). %%
%% %%
%% -define(not_implemented_501, "HTTP/1.1 501 Not Implemented\r\n\r\n"). %%
%% -define(forbidden_403, "HTTP/1.1 403 Forbidden\r\n\r\n"). %%
%% -define(not_found_404, "HTTP/1.1 404 Not Found\r\n\r\n"). %%
%% %%
%% -record(c, {sock, %%
%% port, %%
%% peer_addr, %%
%% peer_port %%
%% }). %%
%% %%
%% -define(server_idle_timeout, 30*1000). %%
%% %%
%% start_link(ListenPid, ListenSocket, ListenPort) -> %%
%% proc_lib:spawn_link(?MODULE, init, [{ListenPid, ListenSocket, ListenPort}]). %%
%% %%
%% init({Listen_pid, Listen_socket, ListenPort}) -> %%
%% % error_logger:info_msg("Socket Started~n"), %%
%% case catch gen_tcp:accept(Listen_socket) of %%
%% {ok, Socket} -> %%
%% %% Send the cast message to the listener process to create a new acceptor %%
%% iserve_server:create(Listen_pid, self()), %%
%% {ok, {Addr, Port}} = inet:peername(Socket), %%
%% Conn = #c{sock = Socket, %%
%% port = ListenPort, %%
%% peer_addr = Addr, %%
%% peer_port = Port}, %%
%% request(Conn, #req{}); %% Jump to state 'request' %%
%% Else -> %%
%% error_logger:error_report([{application, iserve}, %%
%% "Accept failed error", %%
%% io_lib:format("~p",[Else])]), %%
%% exit({error, accept_failed}) %%
%% end. %%
%% %%
%% request(Conn, Req) -> %%
%% case gen_tcp:recv(Conn#c.sock, 0, ?server_idle_timeout) of %%
%% {ok, {http_request,Method,Path,Version}} -> %%
%% headers(Conn, Req#req{vsn = Version, %%
%% method = Method, %%
%% uri = Path}, []); %%
%% {error, {http_error, "\r\n"}} -> %%
%% request(Conn, Req); %%
%% {error, {http_error, "\n"}} -> %%
%% request(Conn, Req); %%
%% {tcp_closed, _Port} -> %%
%% error_logger:info_msg("Closed connection: ~p ~p~n", [Conn#c.peer_addr, Conn#c.peer_port]), %%
%% exit(normal); %%
%% _Other -> %%
%% exit(normal) %%
%% end. %%
case Peer of
_ when DoLookup ->
case catch inet:gethostbyaddr(Peer) of
{ok, Hostent} ->
thrift_utils:sformat("~s:~p", [Hostent#hostent.h_name, Port]);
_ ->
"??"
end;
{A,B,C,D} when not DoLookup ->
thrift_utils:sformat("~p.~p.~p.~p:~p", [A,B,C,D,Port])
end.

View File

@ -15,7 +15,7 @@
-behavior(oop).
-export([attr/4, super/0, inspect/1]).
-export([attr/4, super/0, inspect/1, catches/2]).
-export([new/0, new/1, new/2,
effectful_setHandle/2, effectful_open/1,
@ -91,7 +91,7 @@ write(This, Str) ->
Handle = oop:get(This, handle),
Val = gen_tcp:send(Handle, Str),
%% io:format("WRITE |~p|(~p)~n", [Str,Val]),
%% error_logger:info_msg("WRITE |~p| (~p)", [Str,Val]),
case Val of
{error, _} ->
@ -110,8 +110,6 @@ read(This, Sz) ->
{ok, Data} ->
Data;
{error, Error} ->
io:format("in tSocket:read/2: gen_tcp:recv(~p, ~p) => {error, ~p}~n",
[Handle, Sz, Error]),
exit(tTransportException:new(?tTransportException_NOT_OPEN, "in tSocket:read/2: gen_tcp:recv"))
end.
@ -123,4 +121,3 @@ effectful_close(This) ->
gen_tcp:close(Handle),
{ok, oop:set(This, handle, nil)}
end.

View File

@ -70,7 +70,7 @@ readAll_loop(This, Sz, Buff, Have) ->
%% possibly discarding less than Length bytes of data when
%% the socket gets closed from the other side.
%% io:format("READ |~p|~n", [Chunk]),
%% error_logger:info_msg("READ |~p|", [Chunk]),
Have1 = Have + (Sz-Have), % length(Chunk)
Buff1 = Buff ++ Chunk, % TODO: ++ efficiency?

View File

@ -1,6 +1,7 @@
-module(server).
-include("thrift.hrl").
-include("thrift_logger.hrl").
-include("transport/tSocket.hrl").
-include("protocol/tBinaryProtocol.hrl").
@ -9,20 +10,22 @@
-include("calculator.hrl").
-export([start/0, start/1, stop/1, ping/0, add/2, calculate/2, getStruct/1, zip/0]).
debug(Format, Data) ->
error_logger:info_msg(Format, Data).
ping() ->
io:format("ping()~n",[]),
nil.
debug("ping()",[]),
ok.
add(N1, N2) ->
io:format("add(~p,~p)~n",[N1,N2]),
debug("add(~p,~p)",[N1,N2]),
N1+N2.
calculate(Logid, Work) ->
{ Op, Num1, Num2 } = { Work#work.op, Work#work.num1, Work#work.num2 },
io:format("calculate(~p, {~p,~p,~p})~n", [Logid, Op, Num1, Num2]),
debug("calculate(~p, {~p,~p,~p})", [Logid, Op, Num1, Num2]),
case Op of
?tutorial_ADD -> Num1 + Num2;
?tutorial_SUBTRACT -> Num1 - Num2;
@ -39,11 +42,12 @@ calculate(Logid, Work) ->
end.
getStruct(Key) ->
io:format("getStruct(~p)~n", [Key]),
debug("getStruct(~p)", [Key]),
#sharedStruct{key=Key, value="RARG"}.
zip() ->
io:format("zip~n").
debug("zip", []),
ok.
%%
@ -51,6 +55,12 @@ start() ->
start(9090).
start(Port) ->
thrift_logger:install([#thrift_logger_state{
force_one_line = false, %% should we collapse all errors to one line?
term_width = 110, %% if so, crop output at this width
omit = [oop_new, req_processed] %% don't show these kinds of infos
}]),
Handler = ?MODULE,
Processor = calculator,
@ -62,9 +72,10 @@ start(Port) ->
Server = oop:start_new(ServerFlavor, [Port, Handler, Processor, ServerTransport, TF, PF]),
?R0(Server, effectful_serve),
Server.
case ?R0(Server, effectful_serve) of
ok -> Server;
Error -> Error
end.
stop(Server) ->
?C0(Server, stop),