From: David Reiss Date: Mon, 30 Aug 2010 22:05:29 +0000 (+0000) Subject: erlang: Client refactor, part 1 X-Git-Tag: 0.5.0~126 X-Git-Url: https://source.supwisdom.com/gerrit/gitweb?a=commitdiff_plain;h=3f660a4266b675a0c9d1f7024dda827bd38c24b9;p=common%2Fthrift.git erlang: Client refactor, part 1 - Client is no longer a separate process. - Simplified constructors moved into another module. - All functions and exceptions return the new client, to allow for future statefulness in the client. NOTE: With the new library and old gen-code, attempting to call a nonexistent function will result in an exit. TODO: fix docs and tests (tether test is not meaningful) git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@990979 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/lib/erl/README b/lib/erl/README index ddb6946f..667c549c 100644 --- a/lib/erl/README +++ b/lib/erl/README @@ -25,32 +25,19 @@ Example Example session using thrift_client: -118> f(), {ok, C} = thrift_client:start_link("localhost", 9090, thriftTest_thrif -t). -{ok,<0.271.0>} -119> thrift_client:call(C, testVoid, []). +1> {ok, C0} = thrift_client_util:new("localhost", 9090, thriftTest_thrift, []), ok. +ok +2> {C1, R1} = thrift_client:call(C0, testVoid, []), R1. {ok,ok} -120> thrift_client:call(C, testVoid, [asdf]). +3> {C2, R2} = thrift_client:call(C1, testVoid, [asdf]), R2. {error,{bad_args,testVoid,[asdf]}} -121> thrift_client:call(C, testI32, [123]). +4> {C3, R3} = thrift_client:call(C2, testI32, [123]), R3. {ok,123} -122> thrift_client:call(C, testOneway, [1]). +5> {C4, R4} = thrift_client:call(C3, testOneway, [1]), R4. {ok,ok} -123> catch thrift_client:call(C, testXception, ["foo"]). +6> {C5, R5} = thrift_client:call(C4, testXception, ["foo"]), R5. {error,{no_function,testXception}} -124> catch thrift_client:call(C, testException, ["foo"]). +7> {C6, R6} = thrift_client:call(C5, testException, ["foo"]), R6. {ok,ok} -125> catch thrift_client:call(C, testException, ["Xception"]). -{xception,1001,"This is an Xception"} -126> thrift_client:call(C, testException, ["Xception"]). - -=ERROR REPORT==== 24-Feb-2008::23:00:23 === -Error in process <0.269.0> with exit value: {{nocatch,{xception,1001,"This is an - Xception"}},[{thrift_client,call,3},{erl_eval,do_apply,5},{shell,exprs,6},{shel -l,eval_loop,3}]} - -** exited: {{nocatch,{xception,1001,"This is an Xception"}}, - [{thrift_client,call,3}, - {erl_eval,do_apply,5}, - {shell,exprs,6}, - {shell,eval_loop,3}]} ** +8> {C7, R7} = (catch thrift_client:call(C6, testException, ["Xception"])), R7. +{exception,{xception,1001,<<"Xception">>}} diff --git a/lib/erl/src/thrift_client.erl b/lib/erl/src/thrift_client.erl index 07c9f488..e5a9d71c 100644 --- a/lib/erl/src/thrift_client.erl +++ b/lib/erl/src/thrift_client.erl @@ -19,370 +19,123 @@ -module(thrift_client). --behaviour(gen_server). - %% API --export([start_link/2, start_link/3, start_link/4, - start/3, start/4, - call/3, cast/3, send_call/3, close/1]). - -%% gen_server callbacks --export([init/1, handle_call/3, handle_cast/2, handle_info/2, - terminate/2, code_change/3]). - +-export([new/2, call/3, send_call/3, close/1]). -include("thrift_constants.hrl"). -include("thrift_protocol.hrl"). --record(state, {service, protocol, seqid}). - -%%==================================================================== -%% API -%%==================================================================== -%%-------------------------------------------------------------------- -%% Function: start_link() -> {ok,Pid} | ignore | {error,Error} -%% Description: Starts the server as a linked process. -%%-------------------------------------------------------------------- -start_link(Host, Port, Service) when is_integer(Port), is_atom(Service) -> - start_link(Host, Port, Service, []). - -start_link(Host, Port, Service, Options) -> - start(Host, Port, Service, [{monitor, link} | Options]). - -start_link(ProtocolFactory, Service) -> - start(ProtocolFactory, Service, [{monitor, link}]). - -%% -%% Splits client options into protocol options and transport options -%% -%% split_options([Options...]) -> {ProtocolOptions, TransportOptions} -%% -split_options(Options) -> - split_options(Options, [], [], []). - -split_options([], ClientIn, ProtoIn, TransIn) -> - {ClientIn, ProtoIn, TransIn}; - -split_options([Opt = {OptKey, _} | Rest], ClientIn, ProtoIn, TransIn) - when OptKey =:= monitor -> - split_options(Rest, [Opt | ClientIn], ProtoIn, TransIn); - -split_options([Opt = {OptKey, _} | Rest], ClientIn, ProtoIn, TransIn) - when OptKey =:= strict_read; - OptKey =:= strict_write -> - split_options(Rest, ClientIn, [Opt | ProtoIn], TransIn); +-record(tclient, {service, protocol, seqid}). -split_options([Opt = {OptKey, _} | Rest], ClientIn, ProtoIn, TransIn) - when OptKey =:= framed; - OptKey =:= connect_timeout; - OptKey =:= sockopts -> - split_options(Rest, ClientIn, ProtoIn, [Opt | TransIn]). +new(Protocol, Service) + when is_atom(Service) -> + {ok, #tclient{protocol = Protocol, + service = Service, + seqid = 0}}. -%%-------------------------------------------------------------------- -%% Function: start() -> {ok,Pid} | ignore | {error,Error} -%% Description: Starts the server as an unlinked process. -%%-------------------------------------------------------------------- - -%% Backwards-compatible starter for the common-case of socket transports -start(Host, Port, Service, Options) - when is_integer(Port), is_atom(Service), is_list(Options) -> - {ClientOpts, ProtoOpts, TransOpts} = split_options(Options), - - {ok, TransportFactory} = - thrift_socket_transport:new_transport_factory(Host, Port, TransOpts), - - {ok, ProtocolFactory} = thrift_binary_protocol:new_protocol_factory( - TransportFactory, ProtoOpts), - - start(ProtocolFactory, Service, ClientOpts). - - -%% ProtocolFactory :: fun() -> thrift_protocol() -start(ProtocolFactory, Service, ClientOpts) - when is_function(ProtocolFactory), is_atom(Service) -> - {Starter, Opts} = - case lists:keysearch(monitor, 1, ClientOpts) of - {value, {monitor, link}} -> - {start_link, []}; - {value, {monitor, tether}} -> - {start, [{tether, self()}]}; - _ -> - {start, []} - end, - - Connect = - case lists:keysearch(connect, 1, ClientOpts) of - {value, {connect, Choice}} -> - Choice; - _ -> - %% By default, connect at creation-time. - true - end, - - - Started = gen_server:Starter(?MODULE, [Service, Opts], []), - - if - Connect -> - case Started of - {ok, Pid} -> - case gen_server:call(Pid, {connect, ProtocolFactory}) of - ok -> - {ok, Pid}; - Error -> - Error - end; - Else -> - Else - end; - true -> - Started +-spec call(#tclient{}, atom(), list()) -> {#tclient{}, {ok, term()} | {error, term()}}. +call(Client = #tclient{}, Function, Args) + when is_atom(Function), is_list(Args) -> + case send_function_call(Client, Function, Args) of + {Client1, ok} -> + receive_function_result(Client1, Function); + Else -> + Else end. --spec call(term(), atom(), list()) -> {ok, term()} | {error, term()}. -call(Client, Function, Args) - when is_pid(Client), is_atom(Function), is_list(Args) -> - case gen_server:call(Client, {call, Function, Args}) of - R = {ok, _} -> R; - R = {error, _} -> R; - {exception, Exception} -> throw(Exception) - end. - --spec cast(term(), atom(), list()) -> ok. -cast(Client, Function, Args) - when is_pid(Client), is_atom(Function), is_list(Args) -> - gen_server:cast(Client, {call, Function, Args}). %% Sends a function call but does not read the result. This is useful %% if you're trying to log non-oneway function calls to write-only %% transports like thrift_disk_log_transport. --spec send_call(term(), atom(), list()) -> ok. -send_call(Client, Function, Args) - when is_pid(Client), is_atom(Function), is_list(Args) -> - gen_server:call(Client, {send_call, Function, Args}). - --spec close(term()) -> ok. -close(Client) when is_pid(Client) -> - gen_server:cast(Client, close). - -%%==================================================================== -%% gen_server callbacks -%%==================================================================== +-spec send_call(#tclient{}, atom(), list()) -> {#tclient{}, ok}. +send_call(Client = #tclient{}, Function, Args) + when is_atom(Function), is_list(Args) -> + send_function_call(Client, Function, Args). -%%-------------------------------------------------------------------- -%% Function: init(Args) -> {ok, State} | -%% {ok, State, Timeout} | -%% ignore | -%% {stop, Reason} -%% Description: Initiates the server -%%-------------------------------------------------------------------- -init([Service, Opts]) -> - case lists:keysearch(tether, 1, Opts) of - {value, {tether, Pid}} -> - erlang:monitor(process, Pid); - _Else -> - ok - end, - {ok, #state{service = Service}}. - -%%-------------------------------------------------------------------- -%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} | -%% {reply, Reply, State, Timeout} | -%% {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, Reply, State} | -%% {stop, Reason, State} -%% Description: Handling call messages -%%-------------------------------------------------------------------- -handle_call({connect, ProtocolFactory}, _From, - State = #state{service = Service}) -> - case ProtocolFactory() of - {ok, Protocol} -> - {reply, ok, State#state{protocol = Protocol, - seqid = 0}}; - Error -> - {stop, normal, Error, State} - end; - -handle_call({call, Function, Args}, _From, State = #state{service = Service}) -> - Result = catch_function_exceptions( - fun() -> - ok = send_function_call(State, Function, Args), - receive_function_result(State, Function) - end, - Service), - {reply, Result, State}; - - -handle_call({send_call, Function, Args}, _From, State = #state{service = Service}) -> - Result = catch_function_exceptions( - fun() -> - send_function_call(State, Function, Args) - end, - Service), - {reply, Result, State}. - - -%% Helper function that catches exceptions thrown by sending or receiving -%% a function and returns the correct response for call or send_only above. -catch_function_exceptions(Fun, Service) -> - try - Fun() - catch - throw:{return, Return} -> - Return; - error:function_clause -> - ST = erlang:get_stacktrace(), - case hd(ST) of - {Service, function_info, [Function, _]} -> - {error, {no_function, Function}}; - _ -> throw({error, {function_clause, ST}}) - end - end. - - -%%-------------------------------------------------------------------- -%% Function: handle_cast(Msg, State) -> {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, State} -%% Description: Handling cast messages -%%-------------------------------------------------------------------- -handle_cast({call, Function, Args}, State = #state{service = Service, - protocol = Protocol, - seqid = SeqId}) -> - _Result = - try - ok = send_function_call(State, Function, Args), - receive_function_result(State, Function) - catch - Class:Reason -> - error_logger:error_msg("error ignored in handle_cast({cast,...},...): ~p:~p~n", [Class, Reason]) - end, - - {noreply, State}; +-spec close(#tclient{}) -> ok. +close(#tclient{protocol=Protocol}) -> + thrift_protocol:close_transport(Protocol). -handle_cast(close, State=#state{protocol = Protocol}) -> -%% error_logger:info_msg("thrift_client ~p received close", [self()]), - {stop,normal,State}; -handle_cast(_Msg, State) -> - {noreply, State}. - -%%-------------------------------------------------------------------- -%% Function: handle_info(Info, State) -> {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, State} -%% Description: Handling all non call/cast messages -%%-------------------------------------------------------------------- -handle_info({'DOWN', MonitorRef, process, Pid, _Info}, State) - when is_reference(MonitorRef), is_pid(Pid) -> - %% We don't actually verify the correctness of the DOWN message. - {stop, parent_died, State}; - -handle_info(_Info, State) -> - {noreply, State}. - -%%-------------------------------------------------------------------- -%% Function: terminate(Reason, State) -> void() -%% Description: This function is called by a gen_server when it is about to -%% terminate. It should be the opposite of Module:init/1 and do any necessary -%% cleaning up. When it returns, the gen_server terminates with Reason. -%% The return value is ignored. -%%-------------------------------------------------------------------- -terminate(Reason, State = #state{protocol=undefined}) -> - ok; -terminate(Reason, State = #state{protocol=Protocol}) -> - thrift_protocol:close_transport(Protocol), - ok. - -%%-------------------------------------------------------------------- -%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState} -%% Description: Convert process state when code is changed -%%-------------------------------------------------------------------- -code_change(_OldVsn, State, _Extra) -> - {ok, State}. %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -send_function_call(#state{protocol = Proto, - service = Service, - seqid = SeqId}, +-spec send_function_call(#tclient{}, atom(), list()) -> {#tclient{}, ok | {error, term()}}. +send_function_call(Client = #tclient{protocol = Proto, + service = Service, + seqid = SeqId}, Function, Args) -> Params = Service:function_info(Function, params_type), - {struct, PList} = Params, - if - length(PList) =/= length(Args) -> - throw({return, {error, {bad_args, Function, Args}}}); - true -> ok - end, - - Begin = #protocol_message_begin{name = atom_to_list(Function), - type = ?tMessageType_CALL, - seqid = SeqId}, - ok = thrift_protocol:write(Proto, Begin), - ok = thrift_protocol:write(Proto, {Params, list_to_tuple([Function | Args])}), - ok = thrift_protocol:write(Proto, message_end), - thrift_protocol:flush_transport(Proto), - ok. + case Params of + no_function -> + {Client, {error, {no_function, Function}}}; + {struct, PList} when length(PList) =/= length(Args) -> + {Client, {error, {bad_args, Function, Args}}}; + {struct, _PList} -> + Begin = #protocol_message_begin{name = atom_to_list(Function), + type = ?tMessageType_CALL, + seqid = SeqId}, + ok = thrift_protocol:write(Proto, Begin), + ok = thrift_protocol:write(Proto, {Params, list_to_tuple([Function | Args])}), + ok = thrift_protocol:write(Proto, message_end), + ok = thrift_protocol:flush_transport(Proto), + {Client, ok} + end. -receive_function_result(State = #state{protocol = Proto, - service = Service}, - Function) -> +-spec receive_function_result(#tclient{}, atom()) -> {#tclient{}, {ok, term()} | {error, term()}}. +receive_function_result(Client = #tclient{service = Service}, Function) -> ResultType = Service:function_info(Function, reply_type), - read_result(State, Function, ResultType). + read_result(Client, Function, ResultType). -read_result(_State, - _Function, - oneway_void) -> - {ok, ok}; +read_result(Client, _Function, oneway_void) -> + {Client, {ok, ok}}; -read_result(State = #state{protocol = Proto, - seqid = SeqId}, +read_result(Client = #tclient{protocol = Proto, + seqid = SeqId}, Function, ReplyType) -> case thrift_protocol:read(Proto, message_begin) of #protocol_message_begin{seqid = RetSeqId} when RetSeqId =/= SeqId -> - {error, {bad_seq_id, SeqId}}; + {Client, {error, {bad_seq_id, SeqId}}}; #protocol_message_begin{type = ?tMessageType_EXCEPTION} -> - handle_application_exception(State); + handle_application_exception(Client); #protocol_message_begin{type = ?tMessageType_REPLY} -> - handle_reply(State, Function, ReplyType) + handle_reply(Client, Function, ReplyType) end. -handle_reply(State = #state{protocol = Proto, - service = Service}, + +handle_reply(Client = #tclient{protocol = Proto, + service = Service}, Function, ReplyType) -> {struct, ExceptionFields} = Service:function_info(Function, exceptions), ReplyStructDef = {struct, [{0, ReplyType}] ++ ExceptionFields}, {ok, Reply} = thrift_protocol:read(Proto, ReplyStructDef), + ok = thrift_protocol:read(Proto, message_end), ReplyList = tuple_to_list(Reply), true = length(ReplyList) == length(ExceptionFields) + 1, ExceptionVals = tl(ReplyList), Thrown = [X || X <- ExceptionVals, X =/= undefined], - Result = - case Thrown of - [] when ReplyType == {struct, []} -> - {ok, ok}; - [] -> - {ok, hd(ReplyList)}; - [Exception] -> - {exception, Exception} - end, - ok = thrift_protocol:read(Proto, message_end), - Result. + case Thrown of + [] when ReplyType == {struct, []} -> + {Client, {ok, ok}}; + [] -> + {Client, {ok, hd(ReplyList)}}; + [Exception] -> + throw({Client, {exception, Exception}}) + end. -handle_application_exception(State = #state{protocol = Proto}) -> - {ok, Exception} = thrift_protocol:read(Proto, - ?TApplicationException_Structure), +handle_application_exception(Client = #tclient{protocol = Proto}) -> + {ok, Exception} = + thrift_protocol:read(Proto, ?TApplicationException_Structure), ok = thrift_protocol:read(Proto, message_end), XRecord = list_to_tuple( ['TApplicationException' | tuple_to_list(Exception)]), error_logger:error_msg("X: ~p~n", [XRecord]), true = is_record(XRecord, 'TApplicationException'), - {exception, XRecord}. + throw({Client, {exception, XRecord}}). diff --git a/lib/erl/src/thrift_client_util.erl b/lib/erl/src/thrift_client_util.erl new file mode 100644 index 00000000..c52bb8b6 --- /dev/null +++ b/lib/erl/src/thrift_client_util.erl @@ -0,0 +1,61 @@ +%% +%% Licensed to the Apache Software Foundation (ASF) under one +%% or more contributor license agreements. See the NOTICE file +%% distributed with this work for additional information +%% regarding copyright ownership. The ASF licenses this file +%% to you under the Apache License, Version 2.0 (the +%% "License"); you may not use this file except in compliance +%% with the License. You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, +%% software distributed under the License is distributed on an +%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%% KIND, either express or implied. See the License for the +%% specific language governing permissions and limitations +%% under the License. +%% + +-module(thrift_client_util). + +-export([new/4]). + +%% +%% Splits client options into client, protocol, and transport options +%% +%% split_options([Options...]) -> {ProtocolOptions, TransportOptions} +%% +split_options(Options) -> + split_options(Options, [], []). + +split_options([], ProtoIn, TransIn) -> + {ProtoIn, TransIn}; + +split_options([Opt = {OptKey, _} | Rest], ProtoIn, TransIn) + when OptKey =:= strict_read; + OptKey =:= strict_write -> + split_options(Rest, [Opt | ProtoIn], TransIn); + +split_options([Opt = {OptKey, _} | Rest], ProtoIn, TransIn) + when OptKey =:= framed; + OptKey =:= connect_timeout; + OptKey =:= sockopts -> + split_options(Rest, ProtoIn, [Opt | TransIn]). + + +%% Client constructor for the common-case of socket transports +%% with the binary protocol +new(Host, Port, Service, Options) + when is_integer(Port), is_atom(Service), is_list(Options) -> + {ProtoOpts, TransOpts} = split_options(Options), + + {ok, TransportFactory} = + thrift_socket_transport:new_transport_factory(Host, Port, TransOpts), + + {ok, ProtocolFactory} = thrift_binary_protocol:new_protocol_factory( + TransportFactory, ProtoOpts), + + {ok, Protocol} = ProtocolFactory(), + + thrift_client:new(Protocol, Service). diff --git a/test/erl/Makefile b/test/erl/Makefile index 3fc8fe52..993af4d2 100644 --- a/test/erl/Makefile +++ b/test/erl/Makefile @@ -29,7 +29,7 @@ SRCDIR=src ALL_INCLUDEDIR=$(GEN_INCLUDEDIR) $(INCLUDEDIR) ../../lib/erl/include INCLUDEFLAGS=$(patsubst %,-I%, ${ALL_INCLUDEDIR}) -MODULES = stress_server test_server test_disklog test_membuffer test_tether +MODULES = stress_server test_server test_disklog test_membuffer INCLUDES = TARGETS = $(patsubst %,${TARGETDIR}/%.beam,${MODULES}) diff --git a/test/erl/src/test_tether.erl b/test/erl/src/test_tether.erl deleted file mode 100644 index dc11a9a9..00000000 --- a/test/erl/src/test_tether.erl +++ /dev/null @@ -1,186 +0,0 @@ -%% -%% Licensed to the Apache Software Foundation (ASF) under one -%% or more contributor license agreements. See the NOTICE file -%% distributed with this work for additional information -%% regarding copyright ownership. The ASF licenses this file -%% to you under the Apache License, Version 2.0 (the -%% "License"); you may not use this file except in compliance -%% with the License. You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, -%% software distributed under the License is distributed on an -%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -%% KIND, either express or implied. See the License for the -%% specific language governing permissions and limitations -%% under the License. -%% -%% Tests the behavior of clients in the face of transport errors. -%% Makes sure start, start_linked, and start_tethered work as expected. - --module(test_tether). - --compile(export_all). - - -t() -> - io:format("Beginning transport error test.~n"), - Pid1 = erlang:spawn(?MODULE, t_sub, [2]), - wait_for(Pid1), - io:format("Beginning protocol error test.~n"), - Pid2 = erlang:spawn(?MODULE, t_sub, [22]), - wait_for(Pid2), - ok. - -t_sub(Port) -> - io:format("Starting.~n", []), - register(tester, self()), - - Pid1 = erlang:spawn(?MODULE, test_start, [Port]), - receive after 200 -> ok end, % Wait for completion. - case is_up(Pid1) of - true -> - io:format("PASS. Unlinked owner still alive.~n"); - false -> - io:format("FAIL. Unlinked owner is dead.~n") - end, - - Pid2 = erlang:spawn(?MODULE, test_linked, [Port]), - receive after 200 -> ok end, % Wait for completion. - case is_up(Pid2) of - true -> - io:format("FAIL. Linked owner still alive.~n"); - false -> - io:format("PASS. Linked owner is dead.~n") - end, - - Pid3 = erlang:spawn(?MODULE, test_tethered, [Port]), - receive after 200 -> ok end, % Wait for completion. - case is_up(Pid3) of - true -> - io:format("PASS. Tethered owner still alive.~n"); - false -> - io:format("FAIL. Tethered owner is dead.~n") - end, - - check_extras(3). - -is_up(Pid) -> - MonitorRef = erlang:monitor(process, Pid), - receive - {'DOWN', MonitorRef, process, Pid, _Info} -> - false - after - 50 -> - erlang:demonitor(MonitorRef), - true - end. - -wait_for(Pid) -> - MonitorRef = erlang:monitor(process, Pid), - receive - {'DOWN', MonitorRef, process, Pid, _Info} -> - ok - end. - -check_extras(0) -> ok; -check_extras(N) -> - receive - {client, Type, Pid} -> - case {Type, is_up(Pid)} of - {unlinked, true} -> - io:format("PASS. Unlinked client still alive.~n"); - {unlinked, false} -> - io:format("FAIL. Unlinked client dead.~n"); - {linked, true} -> - io:format("FAIL. Linked client still alive.~n"); - {linked, false} -> - io:format("PASS. Linked client dead.~n"); - {tethered, true} -> - io:format("FAIL. Tethered client still alive.~n"); - {tethered, false} -> - io:format("PASS. Tethered client dead.~n") - end, - check_extras(N-1) - after - 500 -> - io:format("FAIL. Expected ~p more clients.~n", [N]) - end. - -make_thrift_client(Opts) -> - thrift_client:start(fun()->ok end, thriftTest_thrift, Opts). - -make_protocol_factory(Port) -> - {ok, TransportFactory} = - thrift_socket_transport:new_transport_factory( - "127.0.0.1", Port, []), - {ok, ProtocolFactory} = - thrift_binary_protocol:new_protocol_factory( - TransportFactory, []), - ProtocolFactory. - - -test_start(Port) -> - {ok, Client1} = make_thrift_client([{connect, false}]), - tester ! {client, unlinked, Client1}, - {ok, Client2} = make_thrift_client([{connect, false}]), - io:format("PASS. Unlinked clients created.~n"), - try - gen_server:call(Client2, {connect, make_protocol_factory(Port)}), - thrift_client:call(Client2, testVoid, []), - io:format("FAIL. Unlinked client connected and called.~n", []) - catch - Kind:Info -> - io:format("PASS. Caught unlinked error. ~p:~p~n", [Kind, Info]) - end, - receive after 100 -> - io:format("PASS. Still alive after unlinked death.~n"), - %% Hang around a little longer so our parent can verify. - receive after 200 -> ok end - end, - %% Exit abnormally to not kill our unlinked extra client. - exit(die). - -test_linked(Port) -> - {ok, Client1} = make_thrift_client([{connect, false}, {monitor, link}]), - tester ! {client, linked, Client1}, - {ok, Client2} = make_thrift_client([{connect, false}, {monitor, link}]), - io:format("PASS. Linked clients created.~n"), - try - gen_server:call(Client2, {connect, make_protocol_factory(Port)}), - thrift_client:call(Client2, testVoid, []), - io:format("FAIL. Linked client connected and called.~n", []) - catch - Kind:Info -> - io:format("FAIL. Caught linked error. ~p:~p~n", [Kind, Info]) - end, - receive after 100 -> - io:format("FAIL. Still alive after linked death.~n"), - % Hang around a little longer so our parent can verify. - receive after 200 -> ok end - end, - %% Exit abnormally to kill our linked extra client. - %% But we should never get here. - exit(die). - -test_tethered(Port) -> - {ok, Client1} = make_thrift_client([{connect, false}, {monitor, tether}]), - tester ! {client, tethered, Client1}, - {ok, Client2} = make_thrift_client([{connect, false}, {monitor, tether}]), - io:format("PASS. Tethered clients created.~n"), - try - gen_server:call(Client2, {connect, make_protocol_factory(Port)}), - thrift_client:call(Client2, testVoid, []), - io:format("FAIL. Tethered client connected and called.~n", []) - catch - Kind:Info -> - io:format("PASS. Caught tethered error. ~p:~p~n", [Kind, Info]) - end, - receive after 100 -> - io:format("PASS. Still alive after tethered death.~n"), - % Hang around a little longer so our parent can verify. - receive after 200 -> ok end - end, - %% Exit abnormally to kill our tethered extra client. - exit(die). diff --git a/tutorial/erl/client.erl b/tutorial/erl/client.erl index 97803349..adaebe42 100644 --- a/tutorial/erl/client.erl +++ b/tutorial/erl/client.erl @@ -29,46 +29,50 @@ p(X) -> t() -> Port = 9999, - - {ok, Client} = thrift_client:start_link("127.0.0.1", - Port, - calculator_thrift), - thrift_client:call(Client, ping, []), + {ok, Client0} = thrift_client_util:new("127.0.0.1", + Port, + calculator_thrift, + []), + + {Client1, {ok, ok}} = thrift_client:call(Client0, ping, []), io:format("ping~n", []), - {ok, Sum} = thrift_client:call(Client, add, [1, 1]), + {Client2, {ok, Sum}} = thrift_client:call(Client1, add, [1, 1]), io:format("1+1=~p~n", [Sum]), - {ok, Sum1} = thrift_client:call(Client, add, [1, 4]), + {Client3, {ok, Sum1}} = thrift_client:call(Client2, add, [1, 4]), io:format("1+4=~p~n", [Sum1]), Work = #work{op=?tutorial_SUBTRACT, num1=15, num2=10}, - {ok, Diff} = thrift_client:call(Client, calculate, [1, Work]), + {Client4, {ok, Diff}} = thrift_client:call(Client3, calculate, [1, Work]), io:format("15-10=~p~n", [Diff]), - {ok, Log} = thrift_client:call(Client, getStruct, [1]), + {Client5, {ok, Log}} = thrift_client:call(Client4, getStruct, [1]), io:format("Log: ~p~n", [Log]), - try - Work1 = #work{op=?tutorial_DIVIDE, - num1=1, - num2=0}, - {ok, _Quot} = thrift_client:call(Client, calculate, [2, Work1]), + Client6 = + try + Work1 = #work{op=?tutorial_DIVIDE, + num1=1, + num2=0}, + {ClientS1, {ok, _Quot}} = thrift_client:call(Client5, calculate, [2, Work1]), - io:format("LAME: exception handling is broken~n", []) - catch - Z -> - io:format("Got exception where expecting - the " ++ - "following is NOT a problem!!!~n"), - p(Z) - end, + io:format("LAME: exception handling is broken~n", []), + ClientS1 + catch + throw:{ClientS2, Z} -> + io:format("Got exception where expecting - the " ++ + "following is NOT a problem!!!~n"), + p(Z), + ClientS2 + end, - {ok, ok} = thrift_client:call(Client, zip, []), + {Client7, {ok, ok}} = thrift_client:call(Client6, zip, []), io:format("zip~n", []), - ok = thrift_client:close(Client), + {_Client8, ok} = thrift_client:close(Client7), ok.