Wilbeibi Wilbeibi - 3 months ago 26
C++ Question

Thrift java client cannot handle union properly

Dead simple thrift union example.
Env: latest thrift, cpp as server, java as client

mytest.thrift
:

namespace java com.wilbeibi.thrift

union Value {
1: i16 i16_v,
2: string str_v,
}

struct Box {
1: Value value;
}

service MyTest {
Box echoUnion(1: i32 number);
}


C++ server code:


#include "MyTest.h"
#include <thrift/protocol/TBinaryProtocol.h>
#include <thrift/server/TSimpleServer.h>
#include <thrift/transport/TServerSocket.h>
#include <thrift/transport/TBufferTransports.h>

using namespace ::apache::thrift;
using namespace ::apache::thrift::protocol;
using namespace ::apache::thrift::transport;
using namespace ::apache::thrift::server;

using boost::shared_ptr;

class MyTestHandler : virtual public MyTestIf {
public:
MyTestHandler() {
// Your initialization goes here
}

void echoUnion(Box& _return, const int32_t number) {
// Your implementation goes here
printf("Into echoUnion\n");
if (number % 2 == 0) {
Value v;
v.__set_i16_v(100);
v.__isset.i16_v = true;
_return.__set_value(v);
printf("Even number set int32\n");
} else {
Value v;
v.__set_str_v("String value");
v.__isset.str_v = true;
_return.__set_value(v);
printf("Odd number set string\n");
}
printf("echoUnion\n");
}

};

int main(int argc, char **argv) {
int port = 9090;
shared_ptr<MyTestHandler> handler(new MyTestHandler());
shared_ptr<TProcessor> processor(new MyTestProcessor(handler));
shared_ptr<TServerTransport> serverTransport(new TServerSocket(port));
shared_ptr<TTransportFactory> transportFactory(new TBufferedTransportFactory());
shared_ptr<TProtocolFactory> protocolFactory(new TBinaryProtocolFactory());

TSimpleServer server(processor, serverTransport, transportFactory, protocolFactory);
printf("Server is running on %d\n", port);
server.serve();
return 0;
}


java client code
:

// some imports here
public class Client {
public void startClient() {
TTransport transport;
try {
transport = new TSocket("localhost", 9090);
TProtocol protocol = new TBinaryProtocol(transport);
MyTest.Client client = new MyTest.Client(protocol);
transport.open();
Box box = client.echoUnion(1);
System.out.println(box.toString());

Box box2 = client.echoUnion(2);
System.out.println(box2.toString());

transport.close();
} catch (TTransportException e) {
e.printStackTrace();
} catch (TException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Client client = new Client();
client.startClient();
}
}


Somehow, java client cannot print out string properly. (I also wrote a python client, but that seems work)

Full code on gist here: thrift file, c++ and java code

Answer

Actually you are observing THRIFT-1833 bug which cause compiler to produce invalid C++ code for union types.

In your case server writes both fields of union type while client always reads only first one - i16_v (remaining bytes are still resides in buffer). So second read never ends because it finds some unexpected data in buffer.

You can can use struct instead of union and maintain single-field logic manually. Or you either can contribute/wait till bug will be fixed.

The last option is applying a patch on incorrectly generated C++ source code like this:

--- mytest_types.cpp    2016-02-26 20:02:57.210652969 +0300
+++ mytest_types.cpp.old   2016-02-26 20:02:39.650652742 +0300 
@@ -80,13 +80,17 @@
apache::thrift::protocol::TOutputRecursionTracker tracker(*oprot);
xfer += oprot->writeStructBegin("Value");

-  xfer += oprot->writeFieldBegin("i16_v", ::apache::thrift::protocol::T_I16, 1);
-  xfer += oprot->writeI16(this->i16_v);
-  xfer += oprot->writeFieldEnd();
+  if (this->__isset.i16_v) {
+    xfer += oprot->writeFieldBegin("i16_v", ::apache::thrift::protocol::T_I16, 1);
+    xfer += oprot->writeI16(this->i16_v);
+    xfer += oprot->writeFieldEnd();
+  }

-  xfer += oprot->writeFieldBegin("str_v", ::apache::thrift::protocol::T_STRING, 2);
-  xfer += oprot->writeString(this->str_v);
-  xfer += oprot->writeFieldEnd();
+  if (this->__isset.str_v) {
+    xfer += oprot->writeFieldBegin("str_v", ::apache::thrift::protocol::T_STRING, 2);
+    xfer += oprot->writeString(this->str_v);
+    xfer += oprot->writeFieldEnd();
+  }