TEST(ThriftServer, CompressionClientTest) { TestThriftServerFactory<TestInterface> factory; ScopedServerThread sst(factory.create()); folly::EventBase base; std::shared_ptr<TAsyncSocket> socket( TAsyncSocket::newSocket(&base, *sst.getAddress())); TestServiceAsyncClient client( std::unique_ptr<HeaderClientChannel, folly::DelayedDestruction::Destructor>( new HeaderClientChannel(socket))); auto channel = boost::polymorphic_downcast<HeaderClientChannel*>(client.getChannel()); channel->setTransform(apache::thrift::transport::THeader::ZLIB_TRANSFORM); channel->setMinCompressBytes(1); std::string response; client.sync_sendResponse(response, 64); EXPECT_EQ(response, "test64"); auto trans = channel->getWriteTransforms(); EXPECT_EQ(trans.size(), 1); for (auto& tran : trans) { EXPECT_EQ(tran, apache::thrift::transport::THeader::ZLIB_TRANSFORM); } }
TEST(ThriftServer, Thrift1OnewayRequestTest) { TestThriftServerFactory<TestInterface> factory; auto cpp2Server = factory.create(); cpp2Server->setNWorkerThreads(1); cpp2Server->setIsOverloaded([](const THeader*) { return true; }); apache::thrift::util::ScopedServerThread st(cpp2Server); std::shared_ptr<TestServiceClient> client = getThrift1Client(*st.getAddress()); std::string response; // Send a oneway request. Server doesn't send error back client->noResponse(1); // Send a twoway request. Server sends overloading error back try { client->sendResponse(response, 0); } catch (apache::thrift::TApplicationException& ex) { EXPECT_STREQ(ex.what(), "loadshedding request"); } catch (...) { ADD_FAILURE(); } cpp2Server->setIsOverloaded([](const THeader*) { return false; }); // Send another twoway request. Client should receive a response // with correct seqId client->sendResponse(response, 0); }
TEST(ThriftServer, ShutdownDegenarateServer) { TestThriftServerFactory<TestInterface> factory; auto server = factory.create(); server->setMaxRequests(1); server->setNWorkerThreads(1); ScopedServerThread sst(server); }
TEST(ThriftServer, useExistingSocketAndConnectionIdleTimeout) { // This is ConnectionIdleTimeoutTest, but with an existing socket TestThriftServerFactory<TestInterface> factory; auto server = std::static_pointer_cast<ThriftServer>(factory.create()); folly::AsyncServerSocket::UniquePtr serverSocket( new folly::AsyncServerSocket); serverSocket->bind(0); server->useExistingSocket(std::move(serverSocket)); server->setIdleTimeout(std::chrono::milliseconds(20)); apache::thrift::util::ScopedServerThread st(server); folly::EventBase base; std::shared_ptr<TAsyncSocket> socket( TAsyncSocket::newSocket(&base, *st.getAddress())); TestServiceAsyncClient client( std::unique_ptr<HeaderClientChannel, folly::DelayedDestruction::Destructor>( new HeaderClientChannel(socket))); std::string response; client.sync_sendResponse(response, 200); EXPECT_EQ(response, "test200"); base.loop(); }
TEST(ThriftServer, HeaderTest) { TestThriftServerFactory<TestInterface> factory; auto serv = factory.create(); ScopedServerThread sst(serv); folly::EventBase base; std::shared_ptr<TAsyncSocket> socket( TAsyncSocket::newSocket(&base, *sst.getAddress())); TestServiceAsyncClient client( std::unique_ptr<HeaderClientChannel, folly::DelayedDestruction::Destructor>( new HeaderClientChannel(socket))); RpcOptions options; // Set it as a header directly so the client channel won't set a // timeout and the test won't throw TTransportException options.setWriteHeader( apache::thrift::transport::THeader::CLIENT_TIMEOUT_HEADER, folly::to<std::string>(10)); try { client.sync_processHeader(options); ADD_FAILURE() << "should timeout"; } catch (const TApplicationException& e) { EXPECT_EQ(e.getType(), TApplicationException::TApplicationExceptionType::TIMEOUT); } }
TEST(ThriftServer, useExistingSocketAndExit) { TestThriftServerFactory<TestInterface> factory; auto server = std::static_pointer_cast<ThriftServer>(factory.create()); folly::AsyncServerSocket::UniquePtr serverSocket( new folly::AsyncServerSocket); serverSocket->bind(0); server->useExistingSocket(std::move(serverSocket)); // In the past, this would cause a SEGV }
TEST(ThriftServer, IdleServerTimeout) { TestThriftServerFactory<TestInterface> factory; auto server = factory.create(); auto thriftServer = dynamic_cast<ThriftServer *>(server.get()); thriftServer->setIdleServerTimeout(std::chrono::milliseconds(50)); ScopedServerThread scopedServer(server); scopedServer.join(); }
TEST(ThriftServer, setIOThreadPool) { auto exe = std::make_shared<wangle::IOThreadPoolExecutor>(1); TestThriftServerFactory<TestInterface> factory; factory.useSimpleThreadManager(false); auto server = std::static_pointer_cast<ThriftServer>(factory.create()); // Set the exe, this used to trip various calls like // CHECK(ioThreadPool->numThreads() == 0). server->setIOThreadPool(exe); EXPECT_EQ(1, server->getNWorkerThreads()); }
TEST(ThriftServer, ModifyingIOThreadCountLive) { TestThriftServerFactory<TestInterface> factory; auto server = std::static_pointer_cast<ThriftServer>(factory.create()); auto iothreadpool = std::make_shared<wangle::IOThreadPoolExecutor>(0); server->setIOThreadPool(iothreadpool); ScopedServerThread sst(server); // If there are no worker threads, generally the server event base // will stop loop()ing. Create a timeout event to make sure // it continues to loop for the duration of the test. server->getServeEventBase()->runInEventBaseThread( [&]() { server->getServeEventBase()->tryRunAfterDelay([]() {}, 5000); }); server->getServeEventBase()->runInEventBaseThreadAndWait( [=]() { iothreadpool->setNumThreads(0); }); folly::EventBase base; std::shared_ptr<TAsyncSocket> socket( TAsyncSocket::newSocket(&base, *sst.getAddress())); TestServiceAsyncClient client( std::unique_ptr<HeaderClientChannel, folly::DelayedDestruction::Destructor>( new HeaderClientChannel(socket))); std::string response; boost::polymorphic_downcast<HeaderClientChannel*>(client.getChannel()) ->setTimeout(100); // This should fail as soon as it connects: // since AsyncServerSocket has no accept callbacks installed, // it should close the connection right away. ASSERT_ANY_THROW(client.sync_sendResponse(response, 64)); server->getServeEventBase()->runInEventBaseThreadAndWait( [=]() { iothreadpool->setNumThreads(30); }); std::shared_ptr<TAsyncSocket> socket2( TAsyncSocket::newSocket(&base, *sst.getAddress())); // Can't reuse client since the channel has gone bad TestServiceAsyncClient client2( std::unique_ptr<HeaderClientChannel, folly::DelayedDestruction::Destructor>( new HeaderClientChannel(socket2))); client2.sync_sendResponse(response, 64); }
TEST(ThriftServer, ShutdownSocketSetTest) { TestThriftServerFactory<TestInterface> factory; auto server = std::static_pointer_cast<ThriftServer>(factory.create()); ScopedServerThread sst(server); folly::EventBase base; ReadCallbackTest cb; std::shared_ptr<TAsyncSocket> socket2( TAsyncSocket::newSocket(&base, *sst.getAddress())); socket2->setReadCallback(&cb); base.tryRunAfterDelay([&]() { server->immediateShutdown(true); }, 10); base.tryRunAfterDelay([&]() { base.terminateLoopSoon(); }, 30); base.loopForever(); EXPECT_EQ(cb.eof, true); }
TEST(ThriftServer, BadSendTest) { TestThriftServerFactory<TestInterface> factory; ScopedServerThread sst(factory.create()); folly::EventBase base; std::shared_ptr<TAsyncSocket> socket( TAsyncSocket::newSocket(&base, *sst.getAddress())); TestServiceAsyncClient client(HeaderClientChannel::newChannel(socket)); client.sendResponse(std::unique_ptr<RequestCallback>(new Callback), 64); socket->shutdownWriteNow(); base.loop(); std::string response; EXPECT_THROW(client.sync_sendResponse(response, 64), TTransportException); }
TEST(ThriftServer, ConnectionIdleTimeoutTest) { TestThriftServerFactory<TestInterface> factory; auto server = factory.create(); server->setIdleTimeout(std::chrono::milliseconds(20)); apache::thrift::util::ScopedServerThread st(server); folly::EventBase base; std::shared_ptr<TAsyncSocket> socket( TAsyncSocket::newSocket(&base, *st.getAddress())); TestServiceAsyncClient client(HeaderClientChannel::newChannel(socket)); std::string response; client.sync_sendResponse(response, 200); EXPECT_EQ(response, "test200"); base.loop(); }
TEST(ThriftServer, CompressionServerTest) { TestThriftServerFactory<TestInterface> factory; factory.minCompressBytes(100); ScopedServerThread sst(factory.create()); folly::EventBase base; std::shared_ptr<TAsyncSocket> socket( TAsyncSocket::newSocket(&base, *sst.getAddress())); TestServiceAsyncClient client(HeaderClientChannel::newChannel(socket)); auto channel = boost::polymorphic_downcast<HeaderClientChannel*>(client.getChannel()); channel->setTransform(apache::thrift::transport::THeader::ZLIB_TRANSFORM); std::string request(55, 'a'); std::string response; // The response is slightly more than 100 bytes before compression // and less than 100 bytes after compression client.sync_echoRequest(response, request); EXPECT_EQ(response.size(), 100); }
TEST(ThriftServer, FailureInjection) { enum ExpectedFailure { NONE = 0, ERROR, TIMEOUT, DISCONNECT, END }; std::atomic<ExpectedFailure> expected(NONE); using apache::thrift::transport::TTransportException; class Callback : public RequestCallback { public: explicit Callback(const std::atomic<ExpectedFailure>* expected) : expected_(expected) {} private: void requestSent() override {} void replyReceived(ClientReceiveState&& state) override { std::string response; try { TestServiceAsyncClient::recv_sendResponse(response, state); EXPECT_EQ(NONE, *expected_); } catch (const apache::thrift::TApplicationException& ex) { const auto& headers = state.header()->getHeaders(); EXPECT_TRUE(headers.find("ex") != headers.end() && headers.find("ex")->second == kInjectedFailureErrorCode); EXPECT_EQ(ERROR, *expected_); } catch (...) { ADD_FAILURE() << "Unexpected exception thrown"; } // Now do it again with exception_wrappers. auto ew = TestServiceAsyncClient::recv_wrapped_sendResponse(response, state); if (ew) { EXPECT_TRUE( ew.is_compatible_with<apache::thrift::TApplicationException>()); EXPECT_EQ(ERROR, *expected_); } else { EXPECT_EQ(NONE, *expected_); } } void requestError(ClientReceiveState&& state) override { try { std::rethrow_exception(state.exception()); } catch (const TTransportException& ex) { if (ex.getType() == TTransportException::TIMED_OUT) { EXPECT_EQ(TIMEOUT, *expected_); } else { EXPECT_EQ(DISCONNECT, *expected_); } } catch (...) { ADD_FAILURE() << "Unexpected exception thrown"; } } const std::atomic<ExpectedFailure>* expected_; }; TestThriftServerFactory<TestInterface> factory; ScopedServerThread sst(factory.create()); folly::EventBase base; std::shared_ptr<TAsyncSocket> socket( TAsyncSocket::newSocket(&base, *sst.getAddress())); TestServiceAsyncClient client( std::unique_ptr<HeaderClientChannel, folly::DelayedDestruction::Destructor>( new HeaderClientChannel(socket))); auto server = std::dynamic_pointer_cast<ThriftServer>(sst.getServer().lock()); CHECK(server); SCOPE_EXIT { server->setFailureInjection(ThriftServer::FailureInjection()); }; RpcOptions rpcOptions; rpcOptions.setTimeout(std::chrono::milliseconds(100)); for (int i = 0; i < END; ++i) { auto exp = static_cast<ExpectedFailure>(i); ThriftServer::FailureInjection fi; switch (exp) { case NONE: break; case ERROR: fi.errorFraction = 1; break; case TIMEOUT: fi.dropFraction = 1; break; case DISCONNECT: fi.disconnectFraction = 1; break; case END: LOG(FATAL) << "unreached"; break; } server->setFailureInjection(std::move(fi)); expected = exp; auto callback = folly::make_unique<Callback>(&expected); client.sendResponse(rpcOptions, std::move(callback), 1); base.loop(); } }
TEST(ThriftServer, ClientTimeoutTest) { TestThriftServerFactory<TestInterface> factory; auto server = factory.create(); ScopedServerThread sst(server); folly::EventBase base; auto getClient = [&base, &sst]() { std::shared_ptr<TAsyncSocket> socket( TAsyncSocket::newSocket(&base, *sst.getAddress())); return std::make_shared<TestServiceAsyncClient>( std::unique_ptr<HeaderClientChannel, folly::DelayedDestruction::Destructor>( new HeaderClientChannel(socket))); }; int cbCtor = 0; int cbCall = 0; auto callback = [&cbCall, &cbCtor]( std::shared_ptr<TestServiceAsyncClient> client, bool& timeout) { cbCtor++; return std::unique_ptr<RequestCallback>(new FunctionReplyCallback( [&cbCall, client, &timeout](ClientReceiveState&& state) { cbCall++; if (state.exception()) { timeout = true; try { std::rethrow_exception(state.exception()); } catch (const TTransportException& e) { EXPECT_EQ(int(TTransportException::TIMED_OUT), int(e.getType())); } return; } try { std::string resp; client->recv_sendResponse(resp, state); } catch (const TApplicationException& e) { timeout = true; EXPECT_EQ(int(TApplicationException::TIMEOUT), int(e.getType())); EXPECT_TRUE(state.header()->getFlags() & HEADER_FLAG_SUPPORT_OUT_OF_ORDER); return; } timeout = false; })); }; // Set the timeout to be 5 milliseconds, but the call will take 10 ms. // The server should send a timeout after 5 milliseconds RpcOptions options; options.setTimeout(std::chrono::milliseconds(5)); auto client1 = getClient(); bool timeout1; client1->sendResponse(options, callback(client1, timeout1), 10000); base.loop(); EXPECT_TRUE(timeout1); usleep(10000); // This time we set the timeout to be 100 millseconds. The server // should not time out options.setTimeout(std::chrono::milliseconds(100)); client1->sendResponse(options, callback(client1, timeout1), 10000); base.loop(); EXPECT_FALSE(timeout1); usleep(10000); // This time we set server timeout to be 5 millseconds. However, the // task should start processing within that millisecond, so we should // not see an exception because the client timeout should be used after // processing is started server->setTaskExpireTime(std::chrono::milliseconds(5)); client1->sendResponse(options, callback(client1, timeout1), 10000); base.loop(); usleep(10000); // The server timeout stays at 5 ms, but we put the client timeout at // 5 ms. We should timeout even though the server starts processing within // 5ms. options.setTimeout(std::chrono::milliseconds(5)); client1->sendResponse(options, callback(client1, timeout1), 10000); base.loop(); EXPECT_TRUE(timeout1); usleep(50000); // And finally, with the server timeout at 50 ms, we send 2 requests at // once. Because the first request will take more than 50 ms to finish // processing (the server only has 1 worker thread), the second request // won't start processing until after 50ms, and will timeout, despite the // very high client timeout. // We don't know which one will timeout (race conditions) so we just check // the xor auto client2 = getClient(); bool timeout2; server->setTaskExpireTime(std::chrono::milliseconds(50)); options.setTimeout(std::chrono::milliseconds(110)); client1->sendResponse(options, callback(client1, timeout1), 100000); client2->sendResponse(options, callback(client2, timeout2), 100000); base.loop(); EXPECT_TRUE(timeout1 || timeout2); EXPECT_FALSE(timeout1 && timeout2); EXPECT_EQ(cbCall, cbCtor); }
TEST(ThriftServer, DefaultCompressionTest) { /* Tests the functionality of default transforms, ensuring the server properly applies them even if the client does not apply any transforms. */ class Callback : public RequestCallback { public: explicit Callback(bool compressionExpected, uint16_t expectedTransform) : compressionExpected_(compressionExpected), expectedTransform_(expectedTransform) {} private: void requestSent() override {} void replyReceived(ClientReceiveState&& state) override { auto trans = state.header()->getTransforms(); if (compressionExpected_) { EXPECT_EQ(trans.size(), 1); for (auto& tran : trans) { EXPECT_EQ(tran, expectedTransform_); } } else { EXPECT_EQ(trans.size(), 0); } } void requestError(ClientReceiveState&& state) override { std::rethrow_exception(state.exception()); } bool compressionExpected_; uint16_t expectedTransform_; }; TestThriftServerFactory<TestInterface> factory; factory.minCompressBytes(1); factory.defaultWriteTransform( apache::thrift::transport::THeader::ZLIB_TRANSFORM); auto server = std::static_pointer_cast<ThriftServer>(factory.create()); ScopedServerThread sst(server); folly::EventBase base; // First, with minCompressBytes set low, ensure we compress even though the // client did not compress std::shared_ptr<TAsyncSocket> socket( TAsyncSocket::newSocket(&base, *sst.getAddress())); TestServiceAsyncClient client(HeaderClientChannel::newChannel(socket)); client.sendResponse( folly::make_unique<Callback>( true, apache::thrift::transport::THeader::ZLIB_TRANSFORM ), 64 ); base.loop(); // Ensure that client transforms take precedence auto channel = boost::polymorphic_downcast<HeaderClientChannel*>(client.getChannel()); channel->setTransform(apache::thrift::transport::THeader::SNAPPY_TRANSFORM); client.sendResponse( folly::make_unique<Callback>( true, apache::thrift::transport::THeader::SNAPPY_TRANSFORM ), 64 ); base.loop(); // Ensure that minCompressBytes still works with default transforms. We // Do not expect compression server->setMinCompressBytes(1000); std::shared_ptr<TAsyncSocket> socket2( TAsyncSocket::newSocket(&base, *sst.getAddress())); TestServiceAsyncClient client2(HeaderClientChannel::newChannel(socket2)); client2.sendResponse(folly::make_unique<Callback>(false, 0), 64); base.loop(); }