Basic FBThrift example
Facebook re-opensourced their fork of Apache Thrift some 4 years ago. Yet there is relatively little documentation and independent comparison to see if there is any performance bonus to gain by moving from Apache Thrift to FBThrift. The two are no drop-in replacement, thus, replacing one with another requires effort. This post first looks at setting up and running FBThrift. Complete code example used in this post can be found here.
Build FBThrift
FBThrift source can be obtained from their github page. Installing procedure is typical and relatively straight forward. One catch was that the README did not mention rsocket-cpp in dependencies but that is needed as well. (If you encounter ‘internal compiler error: cpp1ypl’ when building rsocket-cpp, try to run make without -j
).
After successfully building FBThrift, the thrift compiler is built to /path/to/fbthrift/build/bin/thrift1
, which can be used to generate C++ service source. Seem like most of online guides on using FBThrift compiler are outdated, which refers to the python module thrift_compiler
. Rather, this github issue is helpful in making it work. To generate C++ source files from idle file example.thrift
, run:
/path/to/fbthrift/build/bin/thrift1 --gen mstch_cpp2 --templates /path/to/fbthrift/thrift/compiler/generate/templates example.thrift
Content of example.thrift
# example.thrift
namespace cpp tamvm
service ExampleService {
i32 get_number(1:i32 number);
}
Implement ExampleService
Implementing the service code is not much different from Apache Thrift. Output C++ files are generated in ./gen-cpp2
by default. Pay attention to the generated file ExampleService.h
, which contains the interface ExampleServiceSvIf
which we need to override with actual service logic.
class ExampleHandler: public ExampleServiceSvIf {
public:
int32_t get_number(int32_t n) override {
printf ("server: receive %d", n);
return n;
}
};
Create client and server instances
For the purpose of this demo, we are going to have a FBThrift client and a server running in one process.
int main(int argc, char *argv[]) {
LOG(INFO) << "Starting test ...";
FLAGS_logtostderr = 1;
folly::init(&argc, &argv);
// starting server on a separate thread
std::thread server_thread([] {
auto server = newServer(thrift_port);
LOG(INFO) << "server: starts";
server->serve();
});
server_thread.detach();
// wait for a short while
// enough for socket opening
std::this_thread::sleep_for(std::chrono::milliseconds(50));
// create event runloop, to run on this thread
folly::EventBase eb;
folly::SocketAddress addr("127.0.0.1", thrift_port);
// creating client
auto client = newHeaderClient(&eb, addr);
std::vector<folly::Future<folly::Unit>> futs;
for (int32_t i = 10; i < 14; i++) {
LOG(INFO) << "client: send number " << i;
auto f = client->future_get_number(i);
futs.push_back(std::move(f).thenValue(onReply).thenError<std::exception>(onError));
}
collectAll(futs.begin(), futs.end()).thenValue([&eb](std::vector<folly::Try<folly::Unit>>&& v){
LOG(INFO) << "client: received all responses";
eb.terminateLoopSoon();
});
// libevent/epoll loop which keeps main thread from existing.
eb.loopForever();
return 0;
}
The above code uses folly::Future
based api for clarify. The RequestCallback
api will do just as well. folly::EventBase
is the main run loop on which async callbacks are scheduled. The call to client->future_get_number
does not actually send outbound request right away. Rather, it schedules the task on EventBase run loop to be excuted in batch 1. As a result, request is only actually sent when caller method gives up program control (whether it returns or waits as coroutine) 2. If the actual time when request is sent is important, the RequestCallback
api is useful.
Notice the last line eb.loopForever
is to keep the program from quitting before collecting all responses. You can replace it with other non-blocking wait call. (std::this_thread::sleep
is not one of them. sleep
blocks the main thread and EventBase run loop can’t proceed).
Build and Run
Build and run is basic:
mkdir build && cd build
cmake ..
make
./fbthrift_ex
If you encounter this error exception specification in declaration does not match previous declaration
, change the compiler to g++ instead of clang++.
Final output:
WARNING: Logging before InitGoogleLogging() is written to STDERR
I0123 17:05:29.276188 26497 main.cc:103] Starting test ...
I0123 17:05:29.276881 26498 main.cc:110] server: starts
I0123 17:05:29.327250 26497 main.cc:121] client: send number 10
I0123 17:05:29.327659 26497 main.cc:121] client: send number 11
I0123 17:05:29.327802 26497 main.cc:121] client: send number 12
I0123 17:05:29.327927 26497 main.cc:121] client: send number 13
I0123 17:05:29.328977 26512 ExampleHandler.h:11] server: receive 11
I0123 17:05:29.328977 26511 ExampleHandler.h:11] server: receive 10
I0123 17:05:29.329150 26510 ExampleHandler.h:11] server: receive 12
I0123 17:05:29.329188 26509 ExampleHandler.h:11] server: receive 13
I0123 17:05:29.329497 26497 main.cc:95] client: get response 10
I0123 17:05:29.329609 26497 main.cc:95] client: get response 11
I0123 17:05:29.329762 26497 main.cc:95] client: get response 12
I0123 17:05:29.329864 26497 main.cc:95] client: get response 13
I0123 17:05:29.329892 26497 main.cc:126] client: received all responses
Again, complete source code of example is here: https://github.com/vuamitom/Code-Exercises/tree/master/blog/fbthrift