THRIFT-5635 Update erlang client for Erlang 23-25
Client: erl
Patch: Sergey Yelin
This closes #2677
Summary of changes:
- Add useful compiler options
- Format sources using erlfmt
- Switch to modern callbacks in thrift_* modules
- Add static analysis (dialyzer), disabled by default
- Add/fix types for API calls
NOTE: Enabling static analysis requires additional tweaks in multiplexer module.
diff --git a/lib/erl/src/thrift_processor.erl b/lib/erl/src/thrift_processor.erl
index 5c9f26f..e17eadb 100644
--- a/lib/erl/src/thrift_processor.erl
+++ b/lib/erl/src/thrift_processor.erl
@@ -24,71 +24,101 @@
-include("thrift_constants.hrl").
-include("thrift_protocol.hrl").
--record(thrift_processor, {handler, protocol, service}).
+-record(thrift_processor, {
+ handler :: module(),
+ protocol :: term(),
+ service :: atom()
+}).
init({_Server, ProtoGen, Service, Handler}) when is_function(ProtoGen, 0) ->
{ok, Proto} = ProtoGen(),
- loop(#thrift_processor{protocol = Proto,
- service = Service,
- handler = Handler}).
+ loop(#thrift_processor{
+ protocol = Proto,
+ service = Service,
+ handler = Handler
+ }).
-loop(State0 = #thrift_processor{protocol = Proto0,
- handler = Handler,
- service = Service}) ->
-
+loop(
+ State0 = #thrift_processor{
+ protocol = Proto0,
+ handler = Handler,
+ service = Service
+ }
+) ->
{Proto1, MessageBegin} = thrift_protocol:read(Proto0, message_begin),
State1 = State0#thrift_processor{protocol = Proto1},
ErrorHandler = fun
- (HandlerModules) when is_list(HandlerModules) -> thrift_multiplexed_map_wrapper:fetch(?MULTIPLEXED_ERROR_HANDLER_KEY, HandlerModules);
- (HandlerModule) -> HandlerModule
+ (HandlerModules) when is_list(HandlerModules) ->
+ thrift_multiplexed_map_wrapper:fetch(?MULTIPLEXED_ERROR_HANDLER_KEY, HandlerModules);
+ (HandlerModule) ->
+ HandlerModule
end,
case MessageBegin of
-
- #protocol_message_begin{name = Function,
- type = Type,
- seqid = Seqid} when Type =:= ?tMessageType_CALL; Type =:= ?tMessageType_ONEWAY ->
+ #protocol_message_begin{
+ name = Function,
+ type = Type,
+ seqid = Seqid
+ } when Type =:= ?tMessageType_CALL; Type =:= ?tMessageType_ONEWAY ->
case string:tokens(Function, ?MULTIPLEXED_SERVICE_SEPARATOR) of
[ServiceName, FunctionName] ->
- ServiceModule = thrift_multiplexed_map_wrapper:fetch(ServiceName, Service),
+ ServiceModule = thrift_multiplexed_map_wrapper:fetch(ServiceName, Service),
ServiceHandler = thrift_multiplexed_map_wrapper:fetch(ServiceName, Handler),
- case handle_function(State1#thrift_processor{service=ServiceModule, handler=ServiceHandler}, list_to_atom(FunctionName), Seqid) of
- {State2, ok} -> loop(State2#thrift_processor{service=Service, handler=Handler});
+ case
+ handle_function(
+ State1#thrift_processor{
+ service = ServiceModule, handler = ServiceHandler
+ },
+ list_to_existing_atom(FunctionName),
+ Seqid
+ )
+ of
+ {State2, ok} ->
+ loop(State2#thrift_processor{service = Service, handler = Handler});
{_State2, {error, Reason}} ->
- apply(ErrorHandler(Handler), handle_error, [list_to_atom(Function), Reason]),
+ apply(ErrorHandler(Handler), handle_error, [
+ list_to_existing_atom(Function), Reason
+ ]),
thrift_protocol:close_transport(Proto1),
ok
end;
_ ->
- case handle_function(State1, list_to_atom(Function), Seqid) of
- {State2, ok} -> loop(State2);
+ case handle_function(State1, list_to_existing_atom(Function), Seqid) of
+ {State2, ok} ->
+ loop(State2);
{_State2, {error, Reason}} ->
- apply(ErrorHandler(Handler), handle_error, [list_to_atom(Function), Reason]),
+ apply(ErrorHandler(Handler), handle_error, [
+ list_to_existing_atom(Function), Reason
+ ]),
thrift_protocol:close_transport(Proto1),
ok
end
end;
{error, timeout = Reason} ->
- apply(ErrorHandler(Handler), handle_error, [undefined, Reason]),
+ apply(ErrorHandler(Handler), handle_error, [undefined, Reason]),
thrift_protocol:close_transport(Proto1),
ok;
{error, closed = Reason} ->
%% error_logger:info_msg("Client disconnected~n"),
- apply(ErrorHandler(Handler), handle_error, [undefined, Reason]),
+ apply(ErrorHandler(Handler), handle_error, [undefined, Reason]),
thrift_protocol:close_transport(Proto1),
exit(shutdown);
{error, Reason} ->
- apply(ErrorHandler(Handler), handle_error, [undefined, Reason]),
+ apply(ErrorHandler(Handler), handle_error, [undefined, Reason]),
thrift_protocol:close_transport(Proto1),
exit(shutdown)
end.
-handle_function(State0=#thrift_processor{protocol = Proto0,
- handler = Handler,
- service = Service},
- Function,
- Seqid) ->
+handle_function(
+ State0 = #thrift_processor{
+ protocol = Proto0,
+ handler = Handler,
+ service = Service
+ },
+ Function,
+ Seqid
+) ->
InParams = Service:function_info(Function, params_type),
{Proto1, {ok, Params}} = thrift_protocol:read(Proto0, InParams),
@@ -101,55 +131,63 @@
%% [Function, Params, Micro/1000.0]),
handle_success(State1, Function, Result, Seqid)
catch
- Type:Data when Type =:= throw orelse Type =:= error ->
- handle_function_catch(State1, Function, Type, Data, Seqid)
+ Type:Data:Stack when Type =:= throw orelse Type =:= error ->
+ handle_function_catch(State1, Function, Type, Data, Seqid, Stack)
end.
-handle_function_catch(State = #thrift_processor{service = Service},
- Function, ErrType, ErrData, Seqid) ->
+handle_function_catch(
+ State = #thrift_processor{service = Service},
+ Function,
+ ErrType,
+ ErrData,
+ Seqid,
+ Stack
+) ->
IsOneway = Service:function_info(Function, reply_type) =:= oneway_void,
case {ErrType, ErrData} of
_ when IsOneway ->
- Stack = erlang:get_stacktrace(),
error_logger:warning_msg(
- "oneway void ~p threw error which must be ignored: ~p",
- [Function, {ErrType, ErrData, Stack}]),
+ "oneway void ~p threw error which must be ignored: ~p",
+ [Function, {ErrType, ErrData, Stack}]
+ ),
{State, ok};
-
{throw, Exception} when is_tuple(Exception), size(Exception) > 0 ->
%error_logger:warning_msg("~p threw exception: ~p~n", [Function, Exception]),
- handle_exception(State, Function, Exception, Seqid);
- % we still want to accept more requests from this client
+ handle_exception(State, Function, Exception, Seqid, Stack);
+ % we still want to accept more requests from this client
{error, Error} ->
- handle_error(State, Function, Error, Seqid)
+ handle_error(State, Function, Error, Seqid, Stack)
end.
-handle_success(State = #thrift_processor{service = Service},
- Function,
- Result,
- Seqid) ->
- ReplyType = Service:function_info(Function, reply_type),
+handle_success(
+ State = #thrift_processor{service = Service},
+ Function,
+ Result,
+ Seqid
+) ->
+ ReplyType = Service:function_info(Function, reply_type),
StructName = atom_to_list(Function) ++ "_result",
case Result of
{reply, ReplyData} ->
Reply = {{struct, [{0, ReplyType}]}, {StructName, ReplyData}},
send_reply(State, Function, ?tMessageType_REPLY, Reply, Seqid);
-
ok when ReplyType == {struct, []} ->
send_reply(State, Function, ?tMessageType_REPLY, {ReplyType, {StructName}}, Seqid);
-
ok when ReplyType == oneway_void ->
%% no reply for oneway void
{State, ok}
end.
-handle_exception(State = #thrift_processor{service = Service},
- Function,
- Exception,
- Seqid) ->
+handle_exception(
+ State = #thrift_processor{service = Service},
+ Function,
+ Exception,
+ Seqid,
+ Stack
+) ->
ExceptionType = element(1, Exception),
%% Fetch a structure like {struct, [{-2, {struct, {Module, Type}}},
%% {-3, {struct, {Module, Type}}}]}
@@ -161,18 +199,20 @@
%% Assuming we had a type1 exception, we'd get: [undefined, Exception, undefined]
%% e.g.: [{-1, type0}, {-2, type1}, {-3, type2}]
- ExceptionList = [case Type of
- ExceptionType -> Exception;
- _ -> undefined
- end
- || {_Fid, {struct, {_Module, Type}}} <- XInfo],
+ ExceptionList = [
+ case Type of
+ ExceptionType -> Exception;
+ _ -> undefined
+ end
+ || {_Fid, {struct, {_Module, Type}}} <- XInfo
+ ],
ExceptionTuple = list_to_tuple([Function | ExceptionList]),
- % Make sure we got at least one defined
+ % Make sure we got at least one defined
case lists:all(fun(X) -> X =:= undefined end, ExceptionList) of
true ->
- handle_unknown_exception(State, Function, Exception, Seqid);
+ handle_unknown_exception(State, Function, Exception, Seqid, Stack);
false ->
send_reply(State, Function, ?tMessageType_REPLY, {ReplySpec, ExceptionTuple}, Seqid)
end.
@@ -181,34 +221,38 @@
%% Called when an exception has been explicitly thrown by the service, but it was
%% not one of the exceptions that was defined for the function.
%%
-handle_unknown_exception(State, Function, Exception, Seqid) ->
- handle_error(State, Function, {exception_not_declared_as_thrown,
- Exception}, Seqid).
+handle_unknown_exception(State, Function, Exception, Seqid, Stack) ->
+ handle_error(State, Function, {exception_not_declared_as_thrown, Exception}, Seqid, Stack).
-handle_error(State, Function, Error, Seqid) ->
- Stack = erlang:get_stacktrace(),
+handle_error(State, Function, Error, Seqid, Stack) ->
error_logger:error_msg("~p had an error: ~p~n", [Function, {Error, Stack}]),
Message =
case application:get_env(thrift, exceptions_include_traces) of
{ok, true} ->
- lists:flatten(io_lib:format("An error occurred: ~p~n",
- [{Error, Stack}]));
+ lists:flatten(
+ io_lib:format(
+ "An error occurred: ~p~n",
+ [{Error, Stack}]
+ )
+ );
_ ->
"An unknown handler error occurred."
end,
- Reply = {?TApplicationException_Structure,
- #'TApplicationException'{
- message = Message,
- type = ?TApplicationException_UNKNOWN}},
+ Reply =
+ {?TApplicationException_Structure, #'TApplicationException'{
+ message = Message,
+ type = ?TApplicationException_UNKNOWN
+ }},
send_reply(State, Function, ?tMessageType_EXCEPTION, Reply, Seqid).
send_reply(State = #thrift_processor{protocol = Proto0}, Function, ReplyMessageType, Reply, Seqid) ->
try
{Proto1, ok} = thrift_protocol:write(Proto0, #protocol_message_begin{
- name = atom_to_list(Function),
- type = ReplyMessageType,
- seqid = Seqid}),
+ name = atom_to_list(Function),
+ type = ReplyMessageType,
+ seqid = Seqid
+ }),
{Proto2, ok} = thrift_protocol:write(Proto1, Reply),
{Proto3, ok} = thrift_protocol:write(Proto2, message_end),
{Proto4, ok} = thrift_protocol:flush_transport(Proto3),