blob: aa2666e467299084744161a80d8016388bc3271a [file] [log] [blame]
David Reissac549552008-06-10 22:56:59 +00001%%%-------------------------------------------------------------------
2%%% File : thrift_processor.erl
3%%% Author : <todd@lipcon.org>
4%%% Description :
5%%%
6%%% Created : 28 Jan 2008 by <todd@lipcon.org>
7%%%-------------------------------------------------------------------
8-module(thrift_processor).
9
David Reissf4761e32008-06-11 00:56:49 +000010-export([start/3,init/3]).
David Reissac549552008-06-10 22:56:59 +000011
12-include("thrift_constants.hrl").
13-include("thrift_protocol.hrl").
14
15-record(state, {handler, in_protocol, out_protocol, service}).
16
David Reissf4761e32008-06-11 00:56:49 +000017start(ProtocolGenerator, Service, Handler) when is_function(ProtocolGenerator, 0) ->
18 spawn(thrift_processor, init, [ProtocolGenerator, Service, Handler]).
David Reissac549552008-06-10 22:56:59 +000019
David Reissf4761e32008-06-11 00:56:49 +000020init(ProtocolGenerator, Service, Handler) ->
21 {ok, IProt, OProt} = ProtocolGenerator(),
David Reissac549552008-06-10 22:56:59 +000022 loop(#state{in_protocol = IProt,
23 out_protocol = OProt,
24 service = Service,
25 handler = Handler}).
26
27loop(State = #state{in_protocol = IProto,
28 out_protocol = OProto}) ->
David Reissae756f42008-06-10 22:57:11 +000029 case thrift_protocol:read(IProto, message_begin) of
30 #protocol_message_begin{name = Function,
31 type = ?tMessageType_CALL} ->
David Reissd93521b2008-06-10 22:58:07 +000032 ok= handle_function(State, list_to_atom(Function)),
David Reissae756f42008-06-10 22:57:11 +000033 loop(State);
34 {error, closed} ->
David Reissfc44c412008-06-11 00:57:19 +000035 % error_logger:info_msg("Client disconnected~n"),
David Reissae756f42008-06-10 22:57:11 +000036 ok
David Reissac549552008-06-10 22:56:59 +000037 end.
David Reissae756f42008-06-10 22:57:11 +000038
David Reiss1c1ca742008-06-10 22:57:21 +000039
40handle_function(State = #state{in_protocol = IProto,
41 out_protocol = OProto,
42 handler = Handler,
43 service = Service},
44 Function) ->
45 InParams = Service:function_info(Function, params_type),
46
47 {ok, Params} = thrift_protocol:read(IProto, InParams),
David Reiss76f0d112008-06-10 22:57:35 +000048
David Reiss982c72d2008-06-10 22:58:33 +000049 try
50 {Micro, Result} = better_timer(Handler, handle_function, [Function, Params]),
David Reissfc44c412008-06-11 00:57:19 +000051 % error_logger:info_msg("Processed ~p(~p) in ~.4fms~n",
52 % [Function, Params, Micro/1000.0]),
David Reiss982c72d2008-06-10 22:58:33 +000053 handle_success(State, Function, Result)
54 catch
David Reiss5541d652008-06-11 00:57:42 +000055 Type:Data ->
56 handle_function_catch(State, Function, Type, Data)
57 end.
58
59handle_function_catch(State = #state{service = Service},
60 Function, ErrType, ErrData) ->
61 IsAsync = Service:function_info(Function, reply_type) =:= async_void,
62
63 case {ErrType, ErrData} of
64 _ when IsAsync ->
65 error_logger:warning_msg(
66 "async void ~p threw error which must be ignored: ~p",
67 [Function, {ErrType, ErrData}]),
68 ok;
69
70 {throw, Exception} when is_tuple(Exception), size(Exception) > 0 ->
David Reiss982c72d2008-06-10 22:58:33 +000071 error_logger:warning_msg("~p threw exception: ~p~n", [Function, Exception]),
72 handle_exception(State, Function, Exception),
David Reiss920959a2008-06-11 00:56:35 +000073 ok; % we still want to accept more requests from this client
David Reiss5541d652008-06-11 00:57:42 +000074
75 {error, Error} ->
David Reiss920959a2008-06-11 00:56:35 +000076 ok = handle_error(State, Function, Error)
David Reiss982c72d2008-06-10 22:58:33 +000077 end.
78
79handle_success(State = #state{out_protocol = OProto,
80 service = Service},
81 Function,
82 Result) ->
David Reiss1c1ca742008-06-10 22:57:21 +000083 ReplyType = Service:function_info(Function, reply_type),
David Reisse1a79982008-06-10 22:58:14 +000084 StructName = atom_to_list(Function) ++ "_result",
David Reiss1c1ca742008-06-10 22:57:21 +000085
86 case Result of
David Reisse1a79982008-06-10 22:58:14 +000087 {reply, ReplyData} ->
88 Reply = {{struct, [{0, ReplyType}]}, {StructName, ReplyData}},
David Reiss982c72d2008-06-10 22:58:33 +000089 ok = send_reply(OProto, Function, ?tMessageType_REPLY, Reply);
David Reisse1a79982008-06-10 22:58:14 +000090
91 ok when ReplyType == {struct, []} ->
David Reisscdf8d192008-06-11 00:57:35 +000092 ok = send_reply(OProto, Function, ?tMessageType_REPLY, {ReplyType, {StructName}});
93
94 ok when ReplyType == async_void ->
95 % no reply for async void
96 ok
David Reiss1c1ca742008-06-10 22:57:21 +000097 end,
98 ok.
David Reisse1a79982008-06-10 22:58:14 +000099
David Reiss982c72d2008-06-10 22:58:33 +0000100handle_exception(State = #state{out_protocol = OProto,
101 service = Service},
102 Function,
103 Exception) ->
104 ExceptionType = element(1, Exception),
105 % Fetch a structure like {struct, [{-2, {struct, {Module, Type}}},
106 % {-3, {struct, {Module, Type}}}]}
David Reisse1a79982008-06-10 22:58:14 +0000107
David Reiss982c72d2008-06-10 22:58:33 +0000108 ReplySpec = Service:function_info(Function, exceptions),
109 {struct, XInfo} = ReplySpec,
110
111 true = is_list(XInfo),
112
113 % e.g.: [{-1, type0}, {-2, type1}, {-3, type2}]
114 XPairs = [{Fid, Type} || {Fid, {struct, {_Module, Type}}} <- XInfo],
115
116 Mapper = fun({Fid, Type}) ->
117 case Type of
118 ExceptionType ->
119 Exception;
120 _ ->
121 undefined
122 end
123 end,
124 % Assuming we had a type1 exception, we get: [undefined, Exception, undefined]
125 ExceptionList = lists:map(Mapper, XPairs),
126 ExceptionTuple = list_to_tuple([Function | ExceptionList]),
127
128 % Make sure we got at least one defined
129 case lists:all(fun(X) -> X =:= undefined end, ExceptionList) of
130 true ->
131 ok = handle_unknown_exception(State, Function, Exception);
132 false ->
133 ok = send_reply(OProto, Function, ?tMessageType_REPLY, {ReplySpec, ExceptionTuple})
134 end.
135
David Reiss920959a2008-06-11 00:56:35 +0000136%%
137% Called when an exception has been explicitly thrown by the service, but it was
138% not one of the exceptions that was defined for the function.
139%%
David Reiss982c72d2008-06-10 22:58:33 +0000140handle_unknown_exception(State, Function, Exception) ->
David Reiss920959a2008-06-11 00:56:35 +0000141 handle_error(State, Function, {exception_not_declared_as_thrown,
142 Exception}).
143
144handle_error(#state{out_protocol = OProto}, Function, Error) ->
David Reissda070672008-06-11 00:56:42 +0000145 Stack = erlang:get_stacktrace(),
146 error_logger:error_msg("~p had an error: ~p~n", [Function, {Error, Stack}]),
147
David Reiss920959a2008-06-11 00:56:35 +0000148 Message =
149 case application:get_env(thrift, exceptions_include_traces) of
150 {ok, true} ->
151 lists:flatten(io_lib:format("An error occurred: ~p~n",
David Reissda070672008-06-11 00:56:42 +0000152 [{Error, Stack}]));
David Reiss920959a2008-06-11 00:56:35 +0000153 _ ->
154 "An unknown handler error occurred."
155 end,
156 Reply = {?TApplicationException_Structure,
157 #'TApplicationException'{
158 message = Message,
159 type = ?TApplicationException_UNKNOWN}},
160 send_reply(OProto, Function, ?tMessageType_EXCEPTION, Reply).
David Reiss982c72d2008-06-10 22:58:33 +0000161
162
163send_reply(OProto, Function, ReplyMessageType, Reply) ->
David Reisse1a79982008-06-10 22:58:14 +0000164 ok = thrift_protocol:write(OProto, #protocol_message_begin{
165 name = atom_to_list(Function),
David Reiss982c72d2008-06-10 22:58:33 +0000166 type = ReplyMessageType,
David Reisse1a79982008-06-10 22:58:14 +0000167 seqid = 0}),
168 ok = thrift_protocol:write(OProto, Reply),
169 ok = thrift_protocol:write(OProto, message_end),
David Reiss90b40832008-06-10 22:58:52 +0000170 ok = thrift_protocol:flush_transport(OProto),
David Reisse1a79982008-06-10 22:58:14 +0000171 ok.
David Reiss982c72d2008-06-10 22:58:33 +0000172
173
174%%
175% This is the same as timer:tc except that timer:tc appears to catch
176% exceptions when it shouldn't!
177%%
178better_timer(Module, Function, Args) ->
179 T1 = erlang:now(),
180 Result = apply(Module, Function, Args),
181 T2 = erlang:now(),
182 {timer:now_diff(T2, T1), Result}.