From The Programmer’s Guide to Apache Thrift by Randy Abernethy

This article, excerpted from The Programmer’s Guide to Apache Thrift, delves into how Apache Thrift handles exceptions of all descriptions.


Save 37% on The Programmer’s Guide to Apache Thrift. Just enter code fccabernethy into the discount code box at checkout at manning.com.


User-defined exceptions

What happens when a user-defined service handler runs into trouble?

When an Apache Thrift service handler experiences an error (for example, not being able to find a customer database record, and so on) the service needs a way to report the problem. Raising a local exception, possibly killing the server process, isn’t the desired approach. Service handlers need a way to report errors back to the calling client. In the RPC context, the client may be running on a separate computer and may be coded in a different language, making this process nontrivial.

Fortunately, the Apache Thrift framework makes propagating exceptions in a service handler back to a client seamless. Apache Thrift users can define custom exception types in IDL. Services defined in the IDL file can flag any method as capable of throwing these user-defined exceptions. The IDL Compiler generates Processor code that automatically catches user-defined exceptions raised by the service handler and passes them back to the client where they’re raised as normal client-side exceptions by the client proxy.

Like TApplicationExceptions, user-defined exceptions are derived from TException. The distinction is that TApplicationExceptions are raised by the Apache Thrift framework and user-defined exceptions are raised by user code.

For example, if a program used by a seafood distributor calls an Apache Thrift fish market server to retrieve the price of Halibut, but Halibut isn’t in the database. The service handler can raise a user-defined BadFish exception. The Apache Thrift framework will then pass the BadFish exception back to the client automatically (see figure 1). It’s important to recognize that user-defined exceptions are an integral part of a service’s interface and therefore defined within the IDL.


Figure 1. User-defined exceptions specified in Apache Thrift IDL can be automatically transferred from the service handler to the calling client.


User-defined exception IDL example

To get a better feel for how user-defined exceptions work, let’s build an exception-enabled RPC application. The TradeHistory service example in the following listing provides a GetLastSale() method returning the going price for fish. If an unsupported fish type is requested, such as halibut, an exception will be generated. Here’s the IDL that declares the exception for bad fish requests and then associates it with your GetLastSale() method.

Listing 1 ~/ThriftBook/part2/exceptions/excep.thrift

  
 exception BadFish {                      
     1: string       fish,       //The problem fish
     2: i16          error_code, //The service specific error code
 }
  
 service TradeHistory {
     double GetLastSale(1: string fish)
         throws (1: BadFish bf),           
 }
  

Defining a custom exception in Apache Thrift IDL is exactly like defining a struct but with the exception keyword (). In fact, Apache Thrift implements exceptions using structs internally.

The IDL “throws” keyword associates exceptions with the methods that might throw them (). Exception types are listed within parenthesis separated by commas. Elements in the throws list are each given a unique positive Id value, like fields in an exception declaration and parameters in an argument list. In the previous example, the GetLastSale() method throws only one exception type, BadFish, with an Id of 1.

C++ user-defined exception client

A client program using the TradeHistory service GetLastSale() method should be prepared to handle the BadFish exception. The following listing shows a sample C++ client listing that calls GetLastSale() and processes the BadFish exception if thrown.

Listing 2 ~/ThriftBook/part2/exceptions/excep_client.cpp

 #include "gen-cpp/TradeHistory.h"
 #include "gen-cpp/excep_types.h"
 #include <thrift/transport/TSocket.h>
 #include <thrift/transport/TBufferTransports.h>
 #include <thrift/protocol/TBinaryProtocol.h>
 #include <boost/shared_ptr.hpp>
 #include <boost/make_shared.hpp>
 #include <iostream>
  
 using namespace apache::thrift::transport; 
 using namespace apache::thrift::protocol;
 using boost::shared_ptr;
 using boost::make_shared;
  
 int main(int argv, char * argc[]) {
     shared_ptr<TTransport> trans;
     trans = make_shared<TSocket>("localhost", 8585);
     trans = make_shared<TBufferedTransport>(trans);
  
     auto proto = make_shared<TBinaryProtocol>(trans);   
     TradeHistoryClient client(proto);
  
     try {
         trans->open();
         auto price = client.GetLastSale(argc[1]);
         std::cout << "[Client] received: " << price << std::endl;
     } catch (const BadFish & bf) {                                         
         std::cout << "[Client] GetLastSale() call failed for fish: "
                   << bf.fish << ", error: " << bf.error_code << std::endl;
     } catch (...) {                                                        
         std::cout << "[Client] GetLastSale() call failed" << std::endl;
     }
 }

The sample program provides code to trap the user defined exception () as well as any other exceptions that may be raised ().

C++ user-defined exception server

User-defined exceptions are raised on the server by using the native language exception mechanism in the service handler. For example, to raise the BadFish exception in the GetLastSale() handler of a C++ TradeHistory implementation, you’d use the C++ throw statement with a BadFish object.

Warning Nothing stops a service handler from throwing an exception type not listed in the IDL throws clause. However, the processor that dispatches RPC calls to the service handler will only trap exceptions listed in the throws list. Other exceptions will not be caught by the processor and instead of being passed back to the client they will likely kill the server thread, or possibly the entire server.

To get a complete picture of the user defined exception process you’ll build a simple RPC server example using your excep.thrift IDL. The session below compiles the excep.thrift IDL, generating C++ RPC stubs used by your C++ client and RPC server.

  
 $ thrift -gen cpp excep.thrift
 $ ls -l
 -rw-r--r-- 1 randy randy  240 Jun  5 17:28 excep.thrift
 drwxr-xr-x 2 randy randy 4096 Jun  5 18:37 gen-cpp
 $ ls -l gen-cpp
 -rw-r--r-- 1 randy randy  251 Jun  5 18:37 excep_constants.cpp
 -rw-r--r-- 1 randy randy  333 Jun  5 18:37 excep_constants.h
 -rw-r--r-- 1 randy randy 2204 Jun  5 18:37 excep_types.cpp                  
 -rw-r--r-- 1 randy randy 1534 Jun  5 18:37 excep_types.h                    
 -rw-r--r-- 1 randy randy 9769 Jun  5 18:37 TradeHistory.cpp
 -rw-r--r-- 1 randy randy 7113 Jun  5 18:37 TradeHistory.h
 -rw-r--r-- 1 randy randy 1366 Jun  5 18:37 TradeHistory_server.skeleton.cpp 
  

Compiling the IDL produces the standard *_types files which house your IDL types, including exception types (). The IDL Compiler C++ code generator also creates a server skeleton for any services defined in the IDL source (). With a few lines of code, we can modify the skeleton for the TradeHistory service so that it throws the BadFish exception when the price of a fish we don’t carry is requested. In the following listing, you throw a user-defined exception for any request other than halibut. It’s a good idea to copy the server skeleton to a new filename before you modify it, because it will be overwritten each time you rerun the IDL Compiler. Here’s an example listing for the modified C++ server skeleton.

Listing 3 ~/ThriftBook/part2/exceptions/excep_server.cpp

  
 #include "gen-cpp/TradeHistory.h"
 #include "gen-cpp/excep_types.h"                     
 #include <thrift/protocol/TBinaryProtocol.h>
 #include <thrift/server/TSimpleServer.h>
 #include <thrift/transport/TServerSocket.h>
 #include <thrift/transport/TBufferTransports.h>
 #include <boost/shared_ptr.hpp>
 #include <boost/make_shared.hpp>
  
 using namespace ::apache::thrift::protocol;
 using namespace ::apache::thrift::transport;
 using namespace ::apache::thrift::server;
  
 using boost::shared_ptr;
 using boost::make_shared;
  
 class TradeHistoryHandler : virtual public TradeHistoryIf {
 public:
     double GetLastSale(const std::string& fish) {
         if (0 != fish.compare("Halibut")) {
             BadFish bf;
             bf.fish = fish;
             bf.error_code = 94;
             throw bf;                                  
         }
         return 10.0;
     }
 };
  
 int main(int argc, char **argv) {
   int port = 8585;
  
 auto handler = make_shared<TradeHistoryHandler>();
 shared_ptr<TProcessor> proc =
   make_shared<TradeHistoryProcessor>(handler);
 shared_ptr<TServerTransport> svr_trans =
   make_shared<TServerSocket>(port);
 shared_ptr<TTransportFactory> trans_fac =
   make_shared<TBufferedTransportFactory>();
 shared_ptr<TProtocolFactory> proto_fac =
   make_shared<TBinaryProtocolFactory>();
  
   TSimpleServer server(proc, svr_trans, trans_fac, proto_fac);
   server.serve();
   return 0;
 }
  

Because user-defined exceptions are another kind of user-defined type you must include the IDL *_types.h header to provide exceptions definitions within your code. The exception can then be constructed, initialized, and thrown from the service handler. This is exactly like throwing an exception in a standalone program.

The following session compiles the example client and server then runs the server.

  
 $ g++ -o server excep_server.cpp \
       gen-cpp/TradeHistory.cpp gen-cpp/excep_types.cpp –lthrift
 $ g++ -o client excep_client.cpp \
       gen-cpp/TradeHistory.cpp gen-cpp/excep_types.cpp –lthrift
 $ ls -l
 -rwxr-xr-x 1 randy randy 142841 Jun  5 18:54 client
 -rw-r--r-- 1 randy randy    832 Jun  5 18:25 excep_client.cpp
 -rw-r--r-- 1 randy randy   1388 Jun  5 18:52 excep_server.cpp
 -rw-r--r-- 1 randy randy    240 Jun  5 17:28 excep.thrift
 drwxr-xr-x 2 randy randy   4096 Jun  5 18:48 gen-cpp
 -rwxr-xr-x 1 randy randy 202651 Jun  5 18:53 server
 $ ./server
  

With the server running, you can start the client program in a separate shell to test normal and exceptional RPC responses.

 $ ./client Halibut
 [Client] received: 10
 $ ./client Salmon
 [Client] GetLastSale() call failed for fish: Salmon, error: 94
  

The completed example demonstrates a common scenario, that of a service running in one process detecting an error that needs to be passed back to a client. The Apache Thrift exception mechanism provides an elegant and seamless solution, wherein both the service code and the client code use their native (and potentially different) error processing mechanisms, with Apache Thrift generating the glue necessary to connect the two.

Java user-defined exception client

To illustrate cross-language exceptions you can recreate the C++ exception client in Java.

Listing 4 ~/ThriftBook/part2/exceptions/ExcepClient.java

  
 import org.apache.thrift.protocol.TBinaryProtocol;
 import org.apache.thrift.transport.TSocket;
 import org.apache.thrift.TException;
  
 public class ExcepClient {
     public static void main(String[] args) throws TException {
         TSocket socket = new TSocket("localhost", 8585);
         socket.open();
         TBinaryProtocol protocol = new TBinaryProtocol(socket);
         TradeHistory.Client client = new TradeHistory.Client(protocol);
         try {
             double price = client.GetLastSale(args[0]);              
             System.out.println("[Client] received: " + price);
         } catch (BadFish bf) {                                       
             System.out.println("[Client] GetLastSale()failed for fish: " +
                                bf.fish + ", error " + bf.error_code);
         }
     }
 }
  

The following listing shows a sample session building and running the Java client against the C++ server (which must be running in another shell).

  
 $ thrift -gen java excep.thrift
 $ javac -cp /usr/local/lib/libthrift-1.0.0.jar:\
             /usr/share/java/slf4j-api.jar:\
             /usr/share/java/slf4j-nop.jar \
             ExcepClient.java \
             gen-java/TradeHistory.java \
             gen-java/BadFish.java
 $ java -cp /usr/local/lib/libthrift-1.0.0.jar:\
            /usr/share/java/slf4j-api.jar:\
            /usr/share/java/slf4j-nop.jar:\
            ./gen-java:\
            . \
            ExcepClient Halibut
 [Client] received: 10.0
 $ java -cp /usr/local/lib/libthrift-1.0.0.jar:\
            /usr/share/java/slf4j-api.jar:\
            /usr/share/java/slf4j-nop.jar:\
            ./gen-java:\
            . \
            ExcepClient Salmon
 [Client] GetLastSale() failed for fish: Salmon, error 94
  

This session runs much like the C++ client session. It’s worth appreciating the fact that in this example a C++ exception object was thrown in a C++ service, trapped by the Apache Thrift processor, serialized into a binary stream, transmitted to the Java client proxy, de-serialized into a Java exception object, and thrown in the Java client process. This is a lot of functionality in exchange for a few lines of IDL.

Python user-defined exception client

To round out the examples, the following listing demonstrates the exception client coded in Python.

Listing 5 ~/ThriftBook/part2/exceptions/excep_client.py

  
 import sys
 sys.path.append("gen-py")
  
 from thrift.transport import TSocket
 from thrift.transport import TTransport
 from thrift.protocol import TBinaryProtocol
 from excep import TradeHistory
 from excep.ttypes import BadFish
  
 trans = TSocket.TSocket("localhost", 8585)
 trans = TTransport.TBufferedTransport(trans)
 trans.open()
  
 proto = TBinaryProtocol.TBinaryProtocol(trans)
 client = TradeHistory.Client(proto)
 try:
     print("[Client] received: %f" % client.GetLastSale(sys.argv[1]))
 except BadFish as bf:
     print("[Client] GetLastSale() call failed for fish: %s, error %d" %
            (bf.fish, bf.error_code))
  

Here’s a session running the Python client with a normal and an exceptional call against the C++ server in the previous listing.

  
 $ thrift –gen py excep.thrift
 $ python excep_client.py Halibut
 [Client] received: 10.000000
 $ python excep_client.py Salmon
 [Client] GetLastSale() call failed for fish: Salmon, error 94
  

That’s all for this article.


If you want to learn more about the book, check it out on liveBook here.