From 19fcec84d8d7d21e796c7624e521b60d28ee21ed Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 20:45:59 +0200 Subject: Adding upstream version 16.2.11+ds. Signed-off-by: Daniel Baumann --- src/jaegertracing/thrift/lib/cpp/3rdparty.props | 25 + src/jaegertracing/thrift/lib/cpp/CMakeLists.txt | 191 +++ src/jaegertracing/thrift/lib/cpp/Makefile.am | 256 ++++ src/jaegertracing/thrift/lib/cpp/README.md | 278 ++++ .../thrift/lib/cpp/coding_standards.md | 4 + src/jaegertracing/thrift/lib/cpp/libthrift.vcxproj | 366 +++++ .../thrift/lib/cpp/libthrift.vcxproj.filters | 283 ++++ .../thrift/lib/cpp/libthriftnb.vcxproj | 298 ++++ .../thrift/lib/cpp/libthriftnb.vcxproj.filters | 75 + .../lib/cpp/src/thrift/TApplicationException.cpp | 81 ++ .../lib/cpp/src/thrift/TApplicationException.h | 115 ++ .../thrift/lib/cpp/src/thrift/TBase.h | 38 + .../thrift/lib/cpp/src/thrift/TDispatchProcessor.h | 141 ++ .../thrift/lib/cpp/src/thrift/TLogging.h | 195 +++ .../thrift/lib/cpp/src/thrift/TOutput.cpp | 128 ++ .../thrift/lib/cpp/src/thrift/TOutput.h | 60 + .../thrift/lib/cpp/src/thrift/TProcessor.h | 229 +++ .../thrift/lib/cpp/src/thrift/TToString.h | 114 ++ .../thrift/lib/cpp/src/thrift/Thrift.h | 133 ++ .../thrift/lib/cpp/src/thrift/VirtualProfiling.cpp | 425 ++++++ .../cpp/src/thrift/async/TAsyncBufferProcessor.h | 46 + .../lib/cpp/src/thrift/async/TAsyncChannel.cpp | 36 + .../lib/cpp/src/thrift/async/TAsyncChannel.h | 74 + .../cpp/src/thrift/async/TAsyncDispatchProcessor.h | 151 ++ .../lib/cpp/src/thrift/async/TAsyncProcessor.h | 84 ++ .../src/thrift/async/TAsyncProtocolProcessor.cpp | 53 + .../cpp/src/thrift/async/TAsyncProtocolProcessor.h | 55 + .../src/thrift/async/TConcurrentClientSyncInfo.cpp | 243 ++++ .../src/thrift/async/TConcurrentClientSyncInfo.h | 126 ++ .../cpp/src/thrift/async/TEvhttpClientChannel.cpp | 156 ++ .../cpp/src/thrift/async/TEvhttpClientChannel.h | 88 ++ .../lib/cpp/src/thrift/async/TEvhttpServer.cpp | 160 +++ .../lib/cpp/src/thrift/async/TEvhttpServer.h | 74 + .../lib/cpp/src/thrift/concurrency/Exception.h | 64 + .../cpp/src/thrift/concurrency/FunctionRunner.h | 118 ++ .../lib/cpp/src/thrift/concurrency/Monitor.cpp | 185 +++ .../lib/cpp/src/thrift/concurrency/Monitor.h | 126 ++ .../lib/cpp/src/thrift/concurrency/Mutex.cpp | 63 + .../thrift/lib/cpp/src/thrift/concurrency/Mutex.h | 89 ++ .../lib/cpp/src/thrift/concurrency/Thread.cpp | 37 + .../thrift/lib/cpp/src/thrift/concurrency/Thread.h | 174 +++ .../cpp/src/thrift/concurrency/ThreadFactory.cpp | 40 + .../lib/cpp/src/thrift/concurrency/ThreadFactory.h | 76 + .../cpp/src/thrift/concurrency/ThreadManager.cpp | 583 ++++++++ .../lib/cpp/src/thrift/concurrency/ThreadManager.h | 213 +++ .../cpp/src/thrift/concurrency/TimerManager.cpp | 321 +++++ .../lib/cpp/src/thrift/concurrency/TimerManager.h | 137 ++ .../lib/cpp/src/thrift/processor/PeekProcessor.cpp | 131 ++ .../lib/cpp/src/thrift/processor/PeekProcessor.h | 83 ++ .../lib/cpp/src/thrift/processor/StatsProcessor.h | 242 ++++ .../src/thrift/processor/TMultiplexedProcessor.h | 224 +++ .../lib/cpp/src/thrift/protocol/TBase64Utils.cpp | 315 ++++ .../lib/cpp/src/thrift/protocol/TBase64Utils.h | 45 + .../lib/cpp/src/thrift/protocol/TBinaryProtocol.h | 250 ++++ .../cpp/src/thrift/protocol/TBinaryProtocol.tcc | 454 ++++++ .../lib/cpp/src/thrift/protocol/TCompactProtocol.h | 266 ++++ .../cpp/src/thrift/protocol/TCompactProtocol.tcc | 826 +++++++++++ .../lib/cpp/src/thrift/protocol/TDebugProtocol.cpp | 393 +++++ .../lib/cpp/src/thrift/protocol/TDebugProtocol.h | 204 +++ .../cpp/src/thrift/protocol/THeaderProtocol.cpp | 253 ++++ .../lib/cpp/src/thrift/protocol/THeaderProtocol.h | 210 +++ .../lib/cpp/src/thrift/protocol/TJSONProtocol.cpp | 1098 ++++++++++++++ .../lib/cpp/src/thrift/protocol/TJSONProtocol.h | 325 +++++ .../src/thrift/protocol/TMultiplexedProtocol.cpp | 40 + .../cpp/src/thrift/protocol/TMultiplexedProtocol.h | 95 ++ .../lib/cpp/src/thrift/protocol/TProtocol.cpp | 33 + .../thrift/lib/cpp/src/thrift/protocol/TProtocol.h | 762 ++++++++++ .../cpp/src/thrift/protocol/TProtocolDecorator.h | 151 ++ .../cpp/src/thrift/protocol/TProtocolException.h | 107 ++ .../lib/cpp/src/thrift/protocol/TProtocolTap.h | 177 +++ .../lib/cpp/src/thrift/protocol/TProtocolTypes.h | 36 + .../lib/cpp/src/thrift/protocol/TVirtualProtocol.h | 513 +++++++ .../thrift/lib/cpp/src/thrift/qt/CMakeLists.txt | 28 + .../lib/cpp/src/thrift/qt/TQIODeviceTransport.cpp | 169 +++ .../lib/cpp/src/thrift/qt/TQIODeviceTransport.h | 68 + .../thrift/lib/cpp/src/thrift/qt/TQTcpServer.cpp | 151 ++ .../thrift/lib/cpp/src/thrift/qt/TQTcpServer.h | 81 ++ .../lib/cpp/src/thrift/server/TConnectedClient.cpp | 122 ++ .../lib/cpp/src/thrift/server/TConnectedClient.h | 110 ++ .../cpp/src/thrift/server/TNonblockingServer.cpp | 1518 ++++++++++++++++++++ .../lib/cpp/src/thrift/server/TNonblockingServer.h | 860 +++++++++++ .../thrift/lib/cpp/src/thrift/server/TServer.cpp | 52 + .../thrift/lib/cpp/src/thrift/server/TServer.h | 273 ++++ .../lib/cpp/src/thrift/server/TServerFramework.cpp | 244 ++++ .../lib/cpp/src/thrift/server/TServerFramework.h | 184 +++ .../lib/cpp/src/thrift/server/TSimpleServer.cpp | 106 ++ .../lib/cpp/src/thrift/server/TSimpleServer.h | 77 + .../cpp/src/thrift/server/TThreadPoolServer.cpp | 131 ++ .../lib/cpp/src/thrift/server/TThreadPoolServer.h | 101 ++ .../lib/cpp/src/thrift/server/TThreadedServer.cpp | 151 ++ .../lib/cpp/src/thrift/server/TThreadedServer.h | 143 ++ .../thrift/lib/cpp/src/thrift/thrift-config.h | 24 + .../thrift/lib/cpp/src/thrift/thrift_export.h | 20 + .../lib/cpp/src/thrift/transport/PlatformSocket.h | 134 ++ .../cpp/src/thrift/transport/TBufferTransports.cpp | 415 ++++++ .../cpp/src/thrift/transport/TBufferTransports.h | 747 ++++++++++ .../lib/cpp/src/thrift/transport/TFDTransport.cpp | 93 ++ .../lib/cpp/src/thrift/transport/TFDTransport.h | 77 + .../cpp/src/thrift/transport/TFileTransport.cpp | 1068 ++++++++++++++ .../lib/cpp/src/thrift/transport/TFileTransport.h | 439 ++++++ .../cpp/src/thrift/transport/THeaderTransport.cpp | 611 ++++++++ .../cpp/src/thrift/transport/THeaderTransport.h | 275 ++++ .../lib/cpp/src/thrift/transport/THttpClient.cpp | 122 ++ .../lib/cpp/src/thrift/transport/THttpClient.h | 50 + .../lib/cpp/src/thrift/transport/THttpServer.cpp | 168 +++ .../lib/cpp/src/thrift/transport/THttpServer.h | 64 + .../cpp/src/thrift/transport/THttpTransport.cpp | 270 ++++ .../lib/cpp/src/thrift/transport/THttpTransport.h | 104 ++ .../transport/TNonblockingSSLServerSocket.cpp | 58 + .../thrift/transport/TNonblockingSSLServerSocket.h | 76 + .../thrift/transport/TNonblockingServerSocket.cpp | 549 +++++++ .../thrift/transport/TNonblockingServerSocket.h | 136 ++ .../thrift/transport/TNonblockingServerTransport.h | 100 ++ .../thrift/lib/cpp/src/thrift/transport/TPipe.cpp | 398 +++++ .../thrift/lib/cpp/src/thrift/transport/TPipe.h | 113 ++ .../lib/cpp/src/thrift/transport/TPipeServer.cpp | 481 +++++++ .../lib/cpp/src/thrift/transport/TPipeServer.h | 103 ++ .../cpp/src/thrift/transport/TSSLServerSocket.cpp | 61 + .../cpp/src/thrift/transport/TSSLServerSocket.h | 76 + .../lib/cpp/src/thrift/transport/TSSLSocket.cpp | 1120 +++++++++++++++ .../lib/cpp/src/thrift/transport/TSSLSocket.h | 436 ++++++ .../lib/cpp/src/thrift/transport/TServerSocket.cpp | 698 +++++++++ .../lib/cpp/src/thrift/transport/TServerSocket.h | 184 +++ .../cpp/src/thrift/transport/TServerTransport.h | 113 ++ .../cpp/src/thrift/transport/TShortReadTransport.h | 82 ++ .../src/thrift/transport/TSimpleFileTransport.cpp | 67 + .../src/thrift/transport/TSimpleFileTransport.h | 42 + .../lib/cpp/src/thrift/transport/TSocket.cpp | 959 +++++++++++++ .../thrift/lib/cpp/src/thrift/transport/TSocket.h | 353 +++++ .../lib/cpp/src/thrift/transport/TSocketPool.cpp | 266 ++++ .../lib/cpp/src/thrift/transport/TSocketPool.h | 195 +++ .../lib/cpp/src/thrift/transport/TTransport.h | 271 ++++ .../src/thrift/transport/TTransportException.cpp | 59 + .../cpp/src/thrift/transport/TTransportException.h | 106 ++ .../cpp/src/thrift/transport/TTransportUtils.cpp | 189 +++ .../lib/cpp/src/thrift/transport/TTransportUtils.h | 314 ++++ .../cpp/src/thrift/transport/TVirtualTransport.h | 140 ++ .../cpp/src/thrift/transport/TZlibTransport.cpp | 402 ++++++ .../lib/cpp/src/thrift/transport/TZlibTransport.h | 242 ++++ .../lib/cpp/src/thrift/windows/GetTimeOfDay.cpp | 95 ++ .../lib/cpp/src/thrift/windows/GetTimeOfDay.h | 44 + .../thrift/lib/cpp/src/thrift/windows/Operators.h | 40 + .../thrift/windows/OverlappedSubmissionThread.cpp | 151 ++ .../thrift/windows/OverlappedSubmissionThread.h | 133 ++ .../lib/cpp/src/thrift/windows/SocketPair.cpp | 100 ++ .../thrift/lib/cpp/src/thrift/windows/SocketPair.h | 37 + .../thrift/lib/cpp/src/thrift/windows/Sync.h | 104 ++ .../cpp/src/thrift/windows/TWinsockSingleton.cpp | 59 + .../lib/cpp/src/thrift/windows/TWinsockSingleton.h | 73 + .../thrift/lib/cpp/src/thrift/windows/WinFcntl.cpp | 101 ++ .../thrift/lib/cpp/src/thrift/windows/WinFcntl.h | 56 + .../thrift/lib/cpp/src/thrift/windows/config.h | 72 + .../thrift/lib/cpp/test/AllProtocolTests.cpp | 47 + .../thrift/lib/cpp/test/AllProtocolTests.tcc | 225 +++ .../thrift/lib/cpp/test/AnnotationTest.cpp | 68 + .../thrift/lib/cpp/test/Base64Test.cpp | 74 + .../thrift/lib/cpp/test/Benchmark.cpp | 244 ++++ .../thrift/lib/cpp/test/CMakeLists.txt | 390 +++++ .../thrift/lib/cpp/test/DebugProtoTest.cpp | 310 ++++ .../thrift/lib/cpp/test/DebugProtoTest_extras.cpp | 35 + src/jaegertracing/thrift/lib/cpp/test/EnumTest.cpp | 108 ++ .../thrift/lib/cpp/test/GenericHelpers.h | 107 ++ .../thrift/lib/cpp/test/JSONProtoTest.cpp | 343 +++++ src/jaegertracing/thrift/lib/cpp/test/Makefile.am | 425 ++++++ .../thrift/lib/cpp/test/OneWayHTTPTest.cpp | 242 ++++ .../thrift/lib/cpp/test/OneWayTest.thrift | 45 + .../thrift/lib/cpp/test/OpenSSLManualInitTest.cpp | 93 ++ .../thrift/lib/cpp/test/OptionalRequiredTest.cpp | 386 +++++ .../thrift/lib/cpp/test/RecursiveTest.cpp | 92 ++ .../lib/cpp/test/RenderedDoubleConstantsTest.cpp | 122 ++ .../thrift/lib/cpp/test/SecurityTest.cpp | 278 ++++ .../thrift/lib/cpp/test/SpecializationTest.cpp | 103 ++ .../thrift/lib/cpp/test/TBufferBaseTest.cpp | 639 ++++++++ .../thrift/lib/cpp/test/TFDTransportTest.cpp | 50 + .../thrift/lib/cpp/test/TFileTransportTest.cpp | 404 ++++++ .../thrift/lib/cpp/test/TMemoryBufferTest.cpp | 160 +++ .../lib/cpp/test/TNonblockingSSLServerTest.cpp | 286 ++++ .../thrift/lib/cpp/test/TNonblockingServerTest.cpp | 215 +++ .../thrift/lib/cpp/test/TPipeInterruptTest.cpp | 90 ++ .../thrift/lib/cpp/test/TPipedTransportTest.cpp | 53 + .../lib/cpp/test/TSSLSocketInterruptTest.cpp | 282 ++++ .../thrift/lib/cpp/test/TServerIntegrationTest.cpp | 536 +++++++ .../thrift/lib/cpp/test/TServerSocketTest.cpp | 69 + .../thrift/lib/cpp/test/TServerTransportTest.cpp | 58 + .../thrift/lib/cpp/test/TSocketInterruptTest.cpp | 146 ++ .../thrift/lib/cpp/test/TTransportCheckThrow.h | 44 + .../thrift/lib/cpp/test/ThriftTest_extras.cpp | 33 + .../thrift/lib/cpp/test/ToStringTest.cpp | 137 ++ .../thrift/lib/cpp/test/TransportTest.cpp | 1089 ++++++++++++++ .../thrift/lib/cpp/test/TypedefTest.cpp | 28 + .../thrift/lib/cpp/test/UnitTestMain.cpp | 21 + src/jaegertracing/thrift/lib/cpp/test/ZlibTest.cpp | 475 ++++++ .../thrift/lib/cpp/test/concurrency/Tests.cpp | 224 +++ .../lib/cpp/test/concurrency/ThreadFactoryTests.h | 308 ++++ .../lib/cpp/test/concurrency/ThreadManagerTests.h | 639 ++++++++ .../lib/cpp/test/concurrency/TimerManagerTests.h | 273 ++++ .../thrift/lib/cpp/test/link/LinkTest.cpp | 22 + .../thrift/lib/cpp/test/link/TemplatedService1.cpp | 26 + .../thrift/lib/cpp/test/link/TemplatedService2.cpp | 26 + .../thrift/lib/cpp/test/processor/EventLog.cpp | 135 ++ .../thrift/lib/cpp/test/processor/EventLog.h | 95 ++ .../thrift/lib/cpp/test/processor/Handlers.h | 338 +++++ .../lib/cpp/test/processor/ProcessorTest.cpp | 929 ++++++++++++ .../thrift/lib/cpp/test/processor/ServerThread.cpp | 152 ++ .../thrift/lib/cpp/test/processor/ServerThread.h | 135 ++ .../thrift/lib/cpp/test/processor/proc.thrift | 22 + .../thrift/lib/cpp/test/qt/CMakeLists.txt | 32 + .../thrift/lib/cpp/test/qt/TQTcpServerTest.cpp | 113 ++ src/jaegertracing/thrift/lib/cpp/thrift-nb.pc.in | 30 + src/jaegertracing/thrift/lib/cpp/thrift-qt5.pc.in | 30 + src/jaegertracing/thrift/lib/cpp/thrift-z.pc.in | 30 + src/jaegertracing/thrift/lib/cpp/thrift.pc.in | 29 + src/jaegertracing/thrift/lib/cpp/thrift.sln | 61 + 213 files changed, 45145 insertions(+) create mode 100644 src/jaegertracing/thrift/lib/cpp/3rdparty.props create mode 100755 src/jaegertracing/thrift/lib/cpp/CMakeLists.txt create mode 100755 src/jaegertracing/thrift/lib/cpp/Makefile.am create mode 100755 src/jaegertracing/thrift/lib/cpp/README.md create mode 100644 src/jaegertracing/thrift/lib/cpp/coding_standards.md create mode 100644 src/jaegertracing/thrift/lib/cpp/libthrift.vcxproj create mode 100644 src/jaegertracing/thrift/lib/cpp/libthrift.vcxproj.filters create mode 100755 src/jaegertracing/thrift/lib/cpp/libthriftnb.vcxproj create mode 100644 src/jaegertracing/thrift/lib/cpp/libthriftnb.vcxproj.filters create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/TApplicationException.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/TApplicationException.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/TBase.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/TDispatchProcessor.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/TLogging.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/TOutput.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/TOutput.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/TProcessor.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/TToString.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/Thrift.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/VirtualProfiling.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/async/TAsyncBufferProcessor.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/async/TAsyncChannel.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/async/TAsyncChannel.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/async/TAsyncDispatchProcessor.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/async/TAsyncProcessor.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/async/TAsyncProtocolProcessor.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/async/TAsyncProtocolProcessor.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/async/TConcurrentClientSyncInfo.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/async/TConcurrentClientSyncInfo.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/async/TEvhttpClientChannel.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/async/TEvhttpClientChannel.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/async/TEvhttpServer.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/async/TEvhttpServer.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/concurrency/Exception.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/concurrency/FunctionRunner.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/concurrency/Monitor.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/concurrency/Monitor.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/concurrency/Mutex.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/concurrency/Mutex.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/concurrency/Thread.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/concurrency/Thread.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/concurrency/ThreadFactory.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/concurrency/ThreadFactory.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/concurrency/ThreadManager.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/concurrency/ThreadManager.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/concurrency/TimerManager.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/concurrency/TimerManager.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/processor/PeekProcessor.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/processor/PeekProcessor.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/processor/StatsProcessor.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/processor/TMultiplexedProcessor.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TBase64Utils.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TBase64Utils.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TBinaryProtocol.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TBinaryProtocol.tcc create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TCompactProtocol.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TCompactProtocol.tcc create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TDebugProtocol.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TDebugProtocol.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/THeaderProtocol.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/THeaderProtocol.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TJSONProtocol.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TJSONProtocol.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TMultiplexedProtocol.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TMultiplexedProtocol.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TProtocol.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TProtocol.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TProtocolDecorator.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TProtocolException.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TProtocolTap.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TProtocolTypes.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TVirtualProtocol.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/qt/CMakeLists.txt create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/qt/TQIODeviceTransport.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/qt/TQIODeviceTransport.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/qt/TQTcpServer.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/qt/TQTcpServer.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/server/TConnectedClient.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/server/TConnectedClient.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/server/TNonblockingServer.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/server/TNonblockingServer.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/server/TServer.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/server/TServer.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/server/TServerFramework.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/server/TServerFramework.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/server/TSimpleServer.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/server/TSimpleServer.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/server/TThreadPoolServer.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/server/TThreadPoolServer.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/server/TThreadedServer.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/server/TThreadedServer.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/thrift-config.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/thrift_export.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/transport/PlatformSocket.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TBufferTransports.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TBufferTransports.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TFDTransport.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TFDTransport.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TFileTransport.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TFileTransport.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/transport/THeaderTransport.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/transport/THeaderTransport.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/transport/THttpClient.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/transport/THttpClient.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/transport/THttpServer.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/transport/THttpServer.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/transport/THttpTransport.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/transport/THttpTransport.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TNonblockingSSLServerSocket.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TNonblockingSSLServerSocket.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TNonblockingServerSocket.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TNonblockingServerSocket.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TNonblockingServerTransport.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TPipe.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TPipe.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TPipeServer.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TPipeServer.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TSSLServerSocket.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TSSLServerSocket.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TSSLSocket.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TSSLSocket.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TServerSocket.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TServerSocket.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TServerTransport.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TShortReadTransport.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TSimpleFileTransport.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TSimpleFileTransport.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TSocket.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TSocket.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TSocketPool.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TSocketPool.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TTransport.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TTransportException.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TTransportException.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TTransportUtils.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TTransportUtils.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TVirtualTransport.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TZlibTransport.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TZlibTransport.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/windows/GetTimeOfDay.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/windows/GetTimeOfDay.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/windows/Operators.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/windows/OverlappedSubmissionThread.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/windows/OverlappedSubmissionThread.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/windows/SocketPair.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/windows/SocketPair.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/windows/Sync.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/windows/TWinsockSingleton.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/windows/TWinsockSingleton.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/windows/WinFcntl.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/windows/WinFcntl.h create mode 100644 src/jaegertracing/thrift/lib/cpp/src/thrift/windows/config.h create mode 100644 src/jaegertracing/thrift/lib/cpp/test/AllProtocolTests.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/test/AllProtocolTests.tcc create mode 100644 src/jaegertracing/thrift/lib/cpp/test/AnnotationTest.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/test/Base64Test.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/test/Benchmark.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/test/CMakeLists.txt create mode 100644 src/jaegertracing/thrift/lib/cpp/test/DebugProtoTest.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/test/DebugProtoTest_extras.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/test/EnumTest.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/test/GenericHelpers.h create mode 100644 src/jaegertracing/thrift/lib/cpp/test/JSONProtoTest.cpp create mode 100755 src/jaegertracing/thrift/lib/cpp/test/Makefile.am create mode 100644 src/jaegertracing/thrift/lib/cpp/test/OneWayHTTPTest.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/test/OneWayTest.thrift create mode 100644 src/jaegertracing/thrift/lib/cpp/test/OpenSSLManualInitTest.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/test/OptionalRequiredTest.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/test/RecursiveTest.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/test/RenderedDoubleConstantsTest.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/test/SecurityTest.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/test/SpecializationTest.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/test/TBufferBaseTest.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/test/TFDTransportTest.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/test/TFileTransportTest.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/test/TMemoryBufferTest.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/test/TNonblockingSSLServerTest.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/test/TNonblockingServerTest.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/test/TPipeInterruptTest.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/test/TPipedTransportTest.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/test/TSSLSocketInterruptTest.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/test/TServerIntegrationTest.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/test/TServerSocketTest.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/test/TServerTransportTest.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/test/TSocketInterruptTest.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/test/TTransportCheckThrow.h create mode 100644 src/jaegertracing/thrift/lib/cpp/test/ThriftTest_extras.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/test/ToStringTest.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/test/TransportTest.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/test/TypedefTest.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/test/UnitTestMain.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/test/ZlibTest.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/test/concurrency/Tests.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/test/concurrency/ThreadFactoryTests.h create mode 100644 src/jaegertracing/thrift/lib/cpp/test/concurrency/ThreadManagerTests.h create mode 100644 src/jaegertracing/thrift/lib/cpp/test/concurrency/TimerManagerTests.h create mode 100644 src/jaegertracing/thrift/lib/cpp/test/link/LinkTest.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/test/link/TemplatedService1.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/test/link/TemplatedService2.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/test/processor/EventLog.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/test/processor/EventLog.h create mode 100644 src/jaegertracing/thrift/lib/cpp/test/processor/Handlers.h create mode 100644 src/jaegertracing/thrift/lib/cpp/test/processor/ProcessorTest.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/test/processor/ServerThread.cpp create mode 100644 src/jaegertracing/thrift/lib/cpp/test/processor/ServerThread.h create mode 100644 src/jaegertracing/thrift/lib/cpp/test/processor/proc.thrift create mode 100644 src/jaegertracing/thrift/lib/cpp/test/qt/CMakeLists.txt create mode 100644 src/jaegertracing/thrift/lib/cpp/test/qt/TQTcpServerTest.cpp create mode 100755 src/jaegertracing/thrift/lib/cpp/thrift-nb.pc.in create mode 100755 src/jaegertracing/thrift/lib/cpp/thrift-qt5.pc.in create mode 100755 src/jaegertracing/thrift/lib/cpp/thrift-z.pc.in create mode 100755 src/jaegertracing/thrift/lib/cpp/thrift.pc.in create mode 100644 src/jaegertracing/thrift/lib/cpp/thrift.sln (limited to 'src/jaegertracing/thrift/lib/cpp') diff --git a/src/jaegertracing/thrift/lib/cpp/3rdparty.props b/src/jaegertracing/thrift/lib/cpp/3rdparty.props new file mode 100644 index 000000000..528d40a0b --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/3rdparty.props @@ -0,0 +1,25 @@ + + + + + $(THIRD_PARTY)\boost\boost_1_47_0 + $(THIRD_PARTY)\openssl\OpenSSL-Win32 + $(THIRD_PARTY)\libevent-2.0.21-stable + + + + + + $(BOOST_ROOT) + true + + + $(OPENSSL_ROOT_DIR) + true + + + $(LIBEVENT_ROOT) + true + + + \ No newline at end of file diff --git a/src/jaegertracing/thrift/lib/cpp/CMakeLists.txt b/src/jaegertracing/thrift/lib/cpp/CMakeLists.txt new file mode 100755 index 000000000..e92da606b --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/CMakeLists.txt @@ -0,0 +1,191 @@ +# +# 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. +# + +# Remove the following once lib/cpp no longer depends on boost headers: +include(BoostMacros) +REQUIRE_BOOST_HEADERS() + +include_directories(src) + +if(NOT BUILD_SHARED_LIBS) + add_definitions("-DTHRIFT_STATIC_DEFINE") +endif() + +# SYSLIBS contains libraries that need to be linked to all lib targets +set(SYSLIBS "") + +# Create the thrift C++ library +set( thriftcpp_SOURCES + src/thrift/TApplicationException.cpp + src/thrift/TOutput.cpp + src/thrift/async/TAsyncChannel.cpp + src/thrift/async/TAsyncProtocolProcessor.cpp + src/thrift/async/TConcurrentClientSyncInfo.h + src/thrift/async/TConcurrentClientSyncInfo.cpp + src/thrift/concurrency/ThreadManager.cpp + src/thrift/concurrency/TimerManager.cpp + src/thrift/processor/PeekProcessor.cpp + src/thrift/protocol/TBase64Utils.cpp + src/thrift/protocol/TDebugProtocol.cpp + src/thrift/protocol/TJSONProtocol.cpp + src/thrift/protocol/TMultiplexedProtocol.cpp + src/thrift/protocol/TProtocol.cpp + src/thrift/transport/TTransportException.cpp + src/thrift/transport/TFDTransport.cpp + src/thrift/transport/TSimpleFileTransport.cpp + src/thrift/transport/THttpTransport.cpp + src/thrift/transport/THttpClient.cpp + src/thrift/transport/THttpServer.cpp + src/thrift/transport/TSocket.cpp + src/thrift/transport/TSocketPool.cpp + src/thrift/transport/TServerSocket.cpp + src/thrift/transport/TTransportUtils.cpp + src/thrift/transport/TBufferTransports.cpp + src/thrift/server/TConnectedClient.cpp + src/thrift/server/TServerFramework.cpp + src/thrift/server/TSimpleServer.cpp + src/thrift/server/TThreadPoolServer.cpp + src/thrift/server/TThreadedServer.cpp +) + +# These files don't work on Windows CE as there is no pipe support +# TODO: These files won't work with UNICODE support on windows. If fixed this can be re-added. +if (NOT WINCE) + list(APPEND thriftcpp_SOURCES + src/thrift/transport/TPipe.cpp + src/thrift/transport/TPipeServer.cpp + src/thrift/transport/TFileTransport.cpp + ) +endif() + + +if (WIN32) + list(APPEND thriftcpp_SOURCES + src/thrift/windows/TWinsockSingleton.cpp + src/thrift/windows/SocketPair.cpp + src/thrift/windows/GetTimeOfDay.cpp + src/thrift/windows/WinFcntl.cpp + ) + if(NOT WINCE) + # This file uses pipes so it currently won't work on Windows CE + list(APPEND thriftcpp_SOURCES + src/thrift/windows/OverlappedSubmissionThread.cpp + ) + endif() +else() + # These files evaluate to nothing on Windows, so omit them from the + # Windows build + list(APPEND thriftcpp_SOURCES + src/thrift/VirtualProfiling.cpp + src/thrift/server/TServer.cpp + ) +endif() + +# If OpenSSL is not found or disabled just ignore the OpenSSL stuff +if(OPENSSL_FOUND AND WITH_OPENSSL) + list( APPEND thriftcpp_SOURCES + src/thrift/transport/TSSLSocket.cpp + src/thrift/transport/TSSLServerSocket.cpp + ) + include_directories(SYSTEM "${OPENSSL_INCLUDE_DIR}") + list(APPEND SYSLIBS "${OPENSSL_LIBRARIES}") +endif() + +if(UNIX) + if(ANDROID) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread") + else() + list(APPEND SYSLIBS pthread) + endif() +endif() +set( thriftcpp_threads_SOURCES + src/thrift/concurrency/ThreadFactory.cpp + src/thrift/concurrency/Thread.cpp + src/thrift/concurrency/Monitor.cpp + src/thrift/concurrency/Mutex.cpp +) + +# Thrift non blocking server +set( thriftcppnb_SOURCES + src/thrift/server/TNonblockingServer.cpp + src/thrift/transport/TNonblockingServerSocket.cpp + src/thrift/transport/TNonblockingSSLServerSocket.cpp + src/thrift/async/TEvhttpServer.cpp + src/thrift/async/TEvhttpClientChannel.cpp +) + +# Thrift zlib transport +set( thriftcppz_SOURCES + src/thrift/transport/TZlibTransport.cpp + src/thrift/protocol/THeaderProtocol.cpp + src/thrift/transport/THeaderTransport.cpp + src/thrift/protocol/THeaderProtocol.cpp + src/thrift/transport/THeaderTransport.cpp +) + +# Contains the thrift specific ADD_LIBRARY_THRIFT and TARGET_LINK_LIBRARIES_THRIFT +include(ThriftMacros) + +ADD_LIBRARY_THRIFT(thrift ${thriftcpp_SOURCES} ${thriftcpp_threads_SOURCES}) +if(WIN32) + TARGET_LINK_LIBRARIES_THRIFT(thrift ${SYSLIBS} ws2_32) +else() + TARGET_LINK_LIBRARIES_THRIFT(thrift ${SYSLIBS}) +endif() +ADD_PKGCONFIG_THRIFT(thrift) + +if(WITH_LIBEVENT) + find_package(Libevent REQUIRED) # Libevent comes with CMake support form upstream + include_directories(SYSTEM ${LIBEVENT_INCLUDE_DIRS}) + + ADD_LIBRARY_THRIFT(thriftnb ${thriftcppnb_SOURCES}) + LINK_AGAINST_THRIFT_LIBRARY(thriftnb thrift) + TARGET_LINK_LIBRARIES_THRIFT(thriftnb ${SYSLIBS} ${LIBEVENT_LIBRARIES}) + ADD_PKGCONFIG_THRIFT(thrift-nb) +endif() + +if(WITH_ZLIB) + find_package(ZLIB REQUIRED) + include_directories(SYSTEM ${ZLIB_INCLUDE_DIRS}) + + ADD_LIBRARY_THRIFT(thriftz ${thriftcppz_SOURCES}) + TARGET_LINK_LIBRARIES_THRIFT(thriftz ${SYSLIBS} ${ZLIB_LIBRARIES}) + TARGET_LINK_LIBRARIES_THRIFT_AGAINST_THRIFT_LIBRARY(thriftz thrift) + ADD_PKGCONFIG_THRIFT(thrift-z) +endif() + +if(WITH_QT5) + add_subdirectory(src/thrift/qt) + ADD_PKGCONFIG_THRIFT(thrift-qt5) +endif() + +if(MSVC) + add_definitions("-DUNICODE -D_UNICODE") +endif() + +# Install the headers +install(DIRECTORY "src/thrift" DESTINATION "${INCLUDE_INSTALL_DIR}" + FILES_MATCHING PATTERN "*.h" PATTERN "*.tcc") +# Copy config.h file +install(DIRECTORY "${CMAKE_BINARY_DIR}/thrift" DESTINATION "${INCLUDE_INSTALL_DIR}" + FILES_MATCHING PATTERN "*.h") + +if(BUILD_TESTING) + add_subdirectory(test) +endif() diff --git a/src/jaegertracing/thrift/lib/cpp/Makefile.am b/src/jaegertracing/thrift/lib/cpp/Makefile.am new file mode 100755 index 000000000..9b5fb4c12 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/Makefile.am @@ -0,0 +1,256 @@ +# +# 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. +# + +AUTOMAKE_OPTIONS = subdir-objects nostdinc + +moc__%.cpp: %.h + $(QT5_MOC) $(QT5_CFLAGS) $< -o $@ + +SUBDIRS = . + +if WITH_TESTS +SUBDIRS += test +endif + +pkgconfigdir = $(libdir)/pkgconfig + +lib_LTLIBRARIES = libthrift.la +pkgconfig_DATA = thrift.pc +libthrift_la_LDFLAGS = -release $(VERSION) +libthrift_la_LIBADD = $(BOOST_LDFLAGS) $(OPENSSL_LDFLAGS) $(OPENSSL_LIBS) + +## We only build the extra libraries if we have the dependencies, +## but we install all of the headers unconditionally. +if AMX_HAVE_LIBEVENT +lib_LTLIBRARIES += libthriftnb.la +pkgconfig_DATA += thrift-nb.pc +endif +if AMX_HAVE_ZLIB +lib_LTLIBRARIES += libthriftz.la +pkgconfig_DATA += thrift-z.pc +endif +if AMX_HAVE_QT5 +lib_LTLIBRARIES += libthriftqt5.la +pkgconfig_DATA += thrift-qt5.pc +endif + +AM_CXXFLAGS = -Wall -Wextra -pedantic +AM_CPPFLAGS = $(BOOST_CPPFLAGS) $(OPENSSL_INCLUDES) -I$(srcdir)/src -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS + +# Define the source files for the module + +libthrift_la_SOURCES = src/thrift/TApplicationException.cpp \ + src/thrift/TOutput.cpp \ + src/thrift/VirtualProfiling.cpp \ + src/thrift/async/TAsyncChannel.cpp \ + src/thrift/async/TAsyncProtocolProcessor.cpp \ + src/thrift/async/TConcurrentClientSyncInfo.cpp \ + src/thrift/concurrency/ThreadManager.cpp \ + src/thrift/concurrency/TimerManager.cpp \ + src/thrift/processor/PeekProcessor.cpp \ + src/thrift/protocol/TDebugProtocol.cpp \ + src/thrift/protocol/TJSONProtocol.cpp \ + src/thrift/protocol/TBase64Utils.cpp \ + src/thrift/protocol/TMultiplexedProtocol.cpp \ + src/thrift/protocol/TProtocol.cpp \ + src/thrift/transport/TTransportException.cpp \ + src/thrift/transport/TFDTransport.cpp \ + src/thrift/transport/TFileTransport.cpp \ + src/thrift/transport/TSimpleFileTransport.cpp \ + src/thrift/transport/THttpTransport.cpp \ + src/thrift/transport/THttpClient.cpp \ + src/thrift/transport/THttpServer.cpp \ + src/thrift/transport/TSocket.cpp \ + src/thrift/transport/TPipe.cpp \ + src/thrift/transport/TPipeServer.cpp \ + src/thrift/transport/TSSLSocket.cpp \ + src/thrift/transport/TSocketPool.cpp \ + src/thrift/transport/TServerSocket.cpp \ + src/thrift/transport/TSSLServerSocket.cpp \ + src/thrift/transport/TNonblockingServerSocket.cpp \ + src/thrift/transport/TNonblockingSSLServerSocket.cpp \ + src/thrift/transport/TTransportUtils.cpp \ + src/thrift/transport/TBufferTransports.cpp \ + src/thrift/server/TConnectedClient.cpp \ + src/thrift/server/TServer.cpp \ + src/thrift/server/TServerFramework.cpp \ + src/thrift/server/TSimpleServer.cpp \ + src/thrift/server/TThreadPoolServer.cpp \ + src/thrift/server/TThreadedServer.cpp + +libthrift_la_SOURCES += src/thrift/concurrency/Mutex.cpp \ + src/thrift/concurrency/ThreadFactory.cpp \ + src/thrift/concurrency/Thread.cpp \ + src/thrift/concurrency/Monitor.cpp + +libthriftnb_la_SOURCES = src/thrift/server/TNonblockingServer.cpp \ + src/thrift/async/TEvhttpServer.cpp \ + src/thrift/async/TEvhttpClientChannel.cpp + +libthriftz_la_SOURCES = src/thrift/transport/TZlibTransport.cpp \ + src/thrift/transport/THeaderTransport.cpp \ + src/thrift/protocol/THeaderProtocol.cpp + + +libthriftqt5_la_MOC = src/thrift/qt/moc__TQTcpServer.cpp +nodist_libthriftqt5_la_SOURCES = $(libthriftqt5_la_MOC) +libthriftqt5_la_SOURCES = src/thrift/qt/TQIODeviceTransport.cpp \ + src/thrift/qt/TQTcpServer.cpp +CLEANFILES = $(libthriftqt5_la_MOC) + +# Flags for the various libraries +libthriftnb_la_CPPFLAGS = $(AM_CPPFLAGS) $(LIBEVENT_CPPFLAGS) +libthriftz_la_CPPFLAGS = $(AM_CPPFLAGS) $(ZLIB_CPPFLAGS) +libthriftqt5_la_CPPFLAGS = $(AM_CPPFLAGS) $(QT5_CFLAGS) +if QT5_REDUCE_RELOCATIONS +libthriftqt5_la_CPPFLAGS += -fPIC +endif +libthriftnb_la_CXXFLAGS = $(AM_CXXFLAGS) +libthriftz_la_CXXFLAGS = $(AM_CXXFLAGS) +libthriftqt5_la_CXXFLAGS = $(AM_CXXFLAGS) +libthriftnb_la_LDFLAGS = -release $(VERSION) $(BOOST_LDFLAGS) +libthriftz_la_LDFLAGS = -release $(VERSION) $(BOOST_LDFLAGS) $(ZLIB_LIBS) +libthriftqt5_la_LDFLAGS = -release $(VERSION) $(BOOST_LDFLAGS) $(QT5_LIBS) + +include_thriftdir = $(includedir)/thrift +include_thrift_HEADERS = \ + $(top_builddir)/config.h \ + src/thrift/thrift-config.h \ + src/thrift/thrift_export.h \ + src/thrift/TDispatchProcessor.h \ + src/thrift/Thrift.h \ + src/thrift/TOutput.h \ + src/thrift/TProcessor.h \ + src/thrift/TApplicationException.h \ + src/thrift/TLogging.h \ + src/thrift/TToString.h \ + src/thrift/TBase.h + +include_concurrencydir = $(include_thriftdir)/concurrency +include_concurrency_HEADERS = \ + src/thrift/concurrency/Exception.h \ + src/thrift/concurrency/Mutex.h \ + src/thrift/concurrency/Monitor.h \ + src/thrift/concurrency/ThreadFactory.h \ + src/thrift/concurrency/Thread.h \ + src/thrift/concurrency/ThreadManager.h \ + src/thrift/concurrency/TimerManager.h \ + src/thrift/concurrency/FunctionRunner.h + +include_protocoldir = $(include_thriftdir)/protocol +include_protocol_HEADERS = \ + src/thrift/protocol/TBinaryProtocol.h \ + src/thrift/protocol/TBinaryProtocol.tcc \ + src/thrift/protocol/TCompactProtocol.h \ + src/thrift/protocol/TCompactProtocol.tcc \ + src/thrift/protocol/TDebugProtocol.h \ + src/thrift/protocol/THeaderProtocol.h \ + src/thrift/protocol/TBase64Utils.h \ + src/thrift/protocol/TJSONProtocol.h \ + src/thrift/protocol/TMultiplexedProtocol.h \ + src/thrift/protocol/TProtocolDecorator.h \ + src/thrift/protocol/TProtocolTap.h \ + src/thrift/protocol/TProtocolTypes.h \ + src/thrift/protocol/TProtocolException.h \ + src/thrift/protocol/TVirtualProtocol.h \ + src/thrift/protocol/TProtocol.h + +include_transportdir = $(include_thriftdir)/transport +include_transport_HEADERS = \ + src/thrift/transport/PlatformSocket.h \ + src/thrift/transport/TFDTransport.h \ + src/thrift/transport/TFileTransport.h \ + src/thrift/transport/THeaderTransport.h \ + src/thrift/transport/TSimpleFileTransport.h \ + src/thrift/transport/TServerSocket.h \ + src/thrift/transport/TSSLServerSocket.h \ + src/thrift/transport/TServerTransport.h \ + src/thrift/transport/TNonblockingServerTransport.h \ + src/thrift/transport/TNonblockingServerSocket.h \ + src/thrift/transport/TNonblockingSSLServerSocket.h \ + src/thrift/transport/THttpTransport.h \ + src/thrift/transport/THttpClient.h \ + src/thrift/transport/THttpServer.h \ + src/thrift/transport/TSocket.h \ + src/thrift/transport/TPipe.h \ + src/thrift/transport/TPipeServer.h \ + src/thrift/transport/TSSLSocket.h \ + src/thrift/transport/TSocketPool.h \ + src/thrift/transport/TVirtualTransport.h \ + src/thrift/transport/TTransport.h \ + src/thrift/transport/TTransportException.h \ + src/thrift/transport/TTransportUtils.h \ + src/thrift/transport/TBufferTransports.h \ + src/thrift/transport/TShortReadTransport.h \ + src/thrift/transport/TZlibTransport.h + +include_serverdir = $(include_thriftdir)/server +include_server_HEADERS = \ + src/thrift/server/TConnectedClient.h \ + src/thrift/server/TServer.h \ + src/thrift/server/TServerFramework.h \ + src/thrift/server/TSimpleServer.h \ + src/thrift/server/TThreadPoolServer.h \ + src/thrift/server/TThreadedServer.h \ + src/thrift/server/TNonblockingServer.h + +include_processordir = $(include_thriftdir)/processor +include_processor_HEADERS = \ + src/thrift/processor/PeekProcessor.h \ + src/thrift/processor/StatsProcessor.h \ + src/thrift/processor/TMultiplexedProcessor.h + +include_asyncdir = $(include_thriftdir)/async +include_async_HEADERS = \ + src/thrift/async/TAsyncChannel.h \ + src/thrift/async/TAsyncDispatchProcessor.h \ + src/thrift/async/TAsyncProcessor.h \ + src/thrift/async/TAsyncBufferProcessor.h \ + src/thrift/async/TAsyncProtocolProcessor.h \ + src/thrift/async/TConcurrentClientSyncInfo.h \ + src/thrift/async/TEvhttpClientChannel.h \ + src/thrift/async/TEvhttpServer.h + +include_qtdir = $(include_thriftdir)/qt +include_qt_HEADERS = \ + src/thrift/qt/TQIODeviceTransport.h \ + src/thrift/qt/TQTcpServer.h + +WINDOWS_DIST = \ + src/thrift/windows \ + thrift.sln \ + libthrift.vcxproj \ + libthrift.vcxproj.filters \ + libthriftnb.vcxproj \ + libthriftnb.vcxproj.filters \ + 3rdparty.props + +EXTRA_DIST = \ + CMakeLists.txt \ + coding_standards.md \ + README.md \ + thrift-nb.pc.in \ + thrift.pc.in \ + thrift-z.pc.in \ + thrift-qt5.pc.in \ + src/thrift/qt/CMakeLists.txt \ + $(WINDOWS_DIST) + +style-local: + $(CPPSTYLE_CMD) diff --git a/src/jaegertracing/thrift/lib/cpp/README.md b/src/jaegertracing/thrift/lib/cpp/README.md new file mode 100755 index 000000000..8a897d1ef --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/README.md @@ -0,0 +1,278 @@ +Thrift C++ Software Library + +# License + +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. + +# Using Thrift with C++ + +The Thrift C++ libraries are built using the GNU tools. Follow the instructions +in the top-level README.md + +In case you do not want to open another README.md file, do this thrift src: + + ./bootstrap.sh + ./configure (--with-boost=/usr/local) + make + sudo make install + +Thrift is divided into two libraries. + +* libthrift - The core Thrift library contains all the core Thrift code. This requires + openssl, pthreads, and librt. + +* libthriftnb - This library contains the Thrift nonblocking server, which uses libevent. + To link this library you will also need to link libevent. + +## Linking Against Thrift + +After you build and install Thrift the libraries are installed to +/usr/local/lib by default. Make sure this is in your LDPATH. + +On Linux, the best way to do this is to ensure that /usr/local/lib is in +your /etc/ld.so.conf and then run /sbin/ldconfig. + +Depending upon whether you are linking dynamically or statically and how +your build environment it set up, you may need to include additional +libraries when linking against thrift, such as librt and/or libpthread. If +you are using libthriftnb you will also need libevent. + +## Dependencies + +C++11 is required at a minimum. C++03/C++98 are not supported after version 0.12.0. + +Boost is required to run the C++ unit tests. It is not necessary to link against +the runtime library. + +libevent (for libthriftnb only) - most linux distributions have dev packages for this: +http://monkey.org/~provos/libevent/ + +# Using Thrift with C++ on Windows + +Both the autoconf and cmake build systems are able to automatically detect many +system configurations without the need to specify library locations, however if +you run into problems or want to redirect thrift to build and link against your +own provided third party libraries: + +BOOST_ROOT : For boost, e.g. D:\boost_1_55_0 +OPENSSL_ROOT_DIR : For OpenSSL, e.g. D:\OpenSSL-Win32 + +only required by libthriftnb: + +LIBEVENT_ROOT_DIR : For Libevent e.g. D:\libevent-2.0.21-stable + +See /3rdparty.user for more details. + +The same linking guidelines described above for libthriftnb apply to windows as well. + +## Linking Against Thrift + +You need to link your project that uses thrift against all the thrift +dependencies; in the case of libthrift, openssl, pthreads, and librt and for +libthriftnb, libevent. + +In the project properties you must also set HAVE_CONFIG_H as force include +the config header: "windows/config.h" + +## Dependencies + +libevent (for libthriftnb only) +http://monkey.org/~provos/libevent/ + +## Windows version compatibility + +The Thrift library targets Windows 7 or latter versions. The supports for windows XP and Vista are avaiable until 0.12.0. + +## Named Pipes + +Named Pipe transport has been added in the TPipe and TPipeServer classes. This +is currently Windows-only. Named pipe transport for *NIX has not been +implemented. Domain sockets are a better choice for local IPC under non-Windows +OS's. *NIX named pipes only support 1:1 client-server connection. + +# Thrift/SSL + +## Scope + +This SSL only supports blocking mode socket I/O. It can only be used with +TSimpleServer, TThreadedServer, and TThreadPoolServer. + +## Implementation + +There are two main classes TSSLSocketFactory and TSSLSocket. Instances of +TSSLSocket are always created from TSSLSocketFactory. + +## How to use SSL APIs + +See the TestClient.cpp and TestServer.cpp files for examples. + +### AccessManager (certificate validation) + +An example of certificate validation can be found in TestServer.cpp. + +AccessManager defines a callback interface. It has three callback methods: + +(a) Decision verify(const sockaddr_storage& sa); + +(b) Decision verify(const string& host, const char* name, int size); + +(c) Decision verify(const sockaddr_storage& sa, const char* data, int size); + +After SSL handshake completes, additional checks are conducted. Application +is given the chance to decide whether or not to continue the conversation +with the remote. Application is queried through the above three "verify" +method. They are called at different points of the verification process. + +Decisions can be one of ALLOW, DENY, and SKIP. ALLOW and DENY means the +conversation should be continued or disconnected, respectively. ALLOW and +DENY decision stops the verification process. SKIP means there's no decision +based on the given input, continue the verification process. + +First, (a) is called with the remote IP. It is called once at the beginning. +"sa" is the IP address of the remote peer. + +Then, the certificate of remote peer is loaded. SubjectAltName extensions +are extracted and sent to application for verification. When a DNS +subjectAltName field is extracted, (b) is called. When an IP subjectAltName +field is extracted, (c) is called. + +The "host" in (b) is the value from TSocket::getHost() if this is a client +side socket, or TSocket::getPeerHost() if this is a server side socket. The +reason is client side socket initiates the connection. TSocket::getHost() +is the remote host name. On server side, the remote host name is unknown +unless it's retrieved through TSocket::getPeerHost(). Either way, "host" +should be the remote host name. Keep in mind, if TSocket::getPeerHost() +failed, it would return the remote host name in numeric format. + +If all subjectAltName extensions were "skipped", the common name field would +be checked. It is sent to application through (c), where "sa" is the remote +IP address. "data" is the IP address extracted from subjectAltName IP +extension, and "size" is the length of the extension data. + +If any of the above "verify" methods returned a decision ALLOW or DENY, the +verification process would be stopped. + +If any of the above "verify" methods returned SKIP, that decision would be +ignored and the verification process would move on till the last item is +examined. At that point, if there's still no decision, the connection is +terminated. + +Thread safety, an access manager should not store state information if it's +to be used by many SSL sockets. + +## SIGPIPE signal + +Applications running OpenSSL over network connections may crash if SIGPIPE +is not ignored. This happens when they receive a connection reset by remote +peer exception, which somehow triggers a SIGPIPE signal. If not handled, +this signal would kill the application. + +## How to run test client/server in SSL mode + +The server and client expects the followings from the directory /test/ + +- keys/server.crt +- keys/server.key +- keys/CA.pem + +The file names are hard coded in the source code. You need to create these +certificates before you can run the test code in SSL mode. Make sure at least +one of the followings is included in "keys/server.crt", + +- subjectAltName, DNS localhost +- subjectAltName, IP 127.0.0.1 +- common name, localhost + +Run within /test/ folder, + + ./cpp/TestServer --ssl & + ./cpp/TestClient --ssl + +If "-h " is used to run client, the above "localhost" in the above +keys/server.crt has to be replaced with that host name. + +## TSSLSocketFactory::randomize() + +The default implementation of OpenSSLSocketFactory::randomize() simply calls +OpenSSL's RAND_poll() when OpenSSL library is first initialized. + +The PRNG seed is key to the application security. This method should be +overridden if it's not strong enough for you. + +# Deprecations + +## 0.12.0 + +Support for C++03/C++98 was deprecated. +Support for Boost at runtime was deprecated. + +# Breaking Changes + +## 1.0.0 + +THRIFT-4720: +The classes Monitor and TimerManager now use std::chrono::milliseconds for timeout, the methods and functions involving THRIFT_TIMESPEC and timeval have been removed, the related tests have been modified. + +Support for Windows XP/Vista has been dropped. + +Support for C++03/C++98 has been dropped. Use version 0.12.0 to support that +language level. As a consequence, boost is no longer required as a runtime +library depenedency, but is is still required to build the runtime library +and to run the unit tests. We will work towards removing boost as a +build dependency for folks who just want to build the runtime and not +run the tests. This means the header thrift/stdcxx.h has been removed and +anything that relied on it has been changed to directly use C++11 concepts. + +THRIFT-4730: +The classes BoostThreadFactory, PosixThreadFactory, StdThreadFactory, and +PlatformThreadFactory have been removed, and we will use a ThreadFactory +based on C++11 (essentially StdThreadFactory was renamed ThreadFactory). + +THRIFT-4732: +The CMake build options WITH_SHARED_LIBS and WITH_STATIC_LIBS are deprecated. +The project no longer performs a side-by-side static and shared build; you +tell CMake through BUILD_SHARED_LIBS whether to make shared or static +libraries now. This is CMake standard behavior. + +THRIFT-4735: +Qt4 support was removed. + +THRIFT-4762: +Added `const` specifier to `TTransport::getOrigin()`. This changes its function signature. +It's recommended to add the `override` specifier in implementations derived from `TTransport`. + +## 0.11.0 + +Older versions of thrift depended on the classes which +were used in thrift headers to define interfaces. Thrift now detects C++11 +at build time and will prefer to use classes from C++11 instead. +You can force the library to build with boost memory classes by defining the +preprocessor macro `FORCE_BOOST_SMART_PTR`. (THRIFT-2221) + +In the pthread mutex implementation, the contention profiling code was enabled +by default in all builds. This changed to be disabled by default. (THRIFT-4151) + +In older releases, if a TSSLSocketFactory's lifetime was not at least as long +as the TSSLSockets it created, we silently reverted openssl to unsafe multithread behavior +and so the results were undefined. Changes were made in 0.11.0 that cause either an +assertion or a core instead of undefined behavior. The lifetime of a TSSLSocketFactory +*must* be longer than any TSSLSocket that it creates, otherwise openssl will be cleaned +up too early. If the static boolean is set to disable openssl initialization and +cleanup and leave it up to the consuming application, this requirement is not needed. +(THRIFT-4164) + diff --git a/src/jaegertracing/thrift/lib/cpp/coding_standards.md b/src/jaegertracing/thrift/lib/cpp/coding_standards.md new file mode 100644 index 000000000..8018c774e --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/coding_standards.md @@ -0,0 +1,4 @@ +Please follow [General Coding Standards](/doc/coding_standards.md) + + * see .clang-format in root dir for settings of accepted format + * clang-format (3.5 or newer) can be used to automaticaly reformat code ('make style' command) diff --git a/src/jaegertracing/thrift/lib/cpp/libthrift.vcxproj b/src/jaegertracing/thrift/lib/cpp/libthrift.vcxproj new file mode 100644 index 000000000..d1097ecd3 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/libthrift.vcxproj @@ -0,0 +1,366 @@ + + + + + Debug-mt + Win32 + + + Debug-mt + x64 + + + Debug + Win32 + + + Debug + x64 + + + Release-mt + Win32 + + + Release-mt + x64 + + + Release + Win32 + + + Release + x64 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {DD26F57E-60F2-4F37-A616-D219A9BF338F} + Win32Proj + thrift + libthrift + + + + StaticLibrary + true + MultiByte + + + StaticLibrary + true + MultiByte + + + StaticLibrary + true + MultiByte + + + StaticLibrary + true + MultiByte + + + StaticLibrary + false + true + MultiByte + + + StaticLibrary + false + true + MultiByte + + + StaticLibrary + false + true + MultiByte + + + StaticLibrary + false + true + MultiByte + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + $(ProjectDir)\src\;$(ProjectDir)\src\thrift\windows\;$(BOOST_ROOT)\include;$(BOOST_ROOT)\;$(OPENSSL_ROOT_DIR)\include\;$(IncludePath) + + + $(ProjectDir)\src\;$(ProjectDir)\src\thrift\windows\;$(BOOST_ROOT)\include;$(BOOST_ROOT)\;$(OPENSSL_ROOT_DIR)\include\;$(IncludePath) + + + $(ProjectDir)\src\;$(ProjectDir)\src\thrift\windows\;$(BOOST_ROOT)\include;$(BOOST_ROOT)\;$(OPENSSL_ROOT_DIR)\include\;$(IncludePath) + + + $(ProjectDir)\src\;$(ProjectDir)\src\thrift\windows\;$(BOOST_ROOT)\include;$(BOOST_ROOT)\;$(OPENSSL_ROOT_DIR)\include\;$(IncludePath) + + + $(ProjectDir)\src\;$(ProjectDir)\src\thrift\windows\;$(BOOST_ROOT)\include;$(BOOST_ROOT)\;$(OPENSSL_ROOT_DIR)\include\;$(IncludePath) + + + $(ProjectDir)\src\;$(ProjectDir)\src\thrift\windows\;$(BOOST_ROOT)\include;$(BOOST_ROOT)\;$(OPENSSL_ROOT_DIR)\include\;$(IncludePath) + + + $(ProjectDir)\src\;$(ProjectDir)\src\thrift\windows\;$(BOOST_ROOT)\include;$(BOOST_ROOT)\;$(OPENSSL_ROOT_DIR)\include\;$(IncludePath) + + + $(ProjectDir)\src\;$(ProjectDir)\src\thrift\windows\;$(BOOST_ROOT)\include;$(BOOST_ROOT)\;$(OPENSSL_ROOT_DIR)\include\;$(IncludePath) + + + + NotUsing + Level3 + Disabled + HAVE_CONFIG_H=1;WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + $(IntDir)libthrift.pdb + MultiThreadedDebugDLL + + + Windows + true + + + + + NotUsing + Level3 + Disabled + HAVE_CONFIG_H=1;WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + $(IntDir)libthrift.pdb + MultiThreadedDebug + + + Windows + true + + + + + NotUsing + Level3 + Disabled + HAVE_CONFIG_H=1;WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + + + Windows + true + + + + + NotUsing + Level3 + Disabled + HAVE_CONFIG_H=1;WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + MultiThreadedDebug + + + Windows + true + + + + + Level3 + NotUsing + MaxSpeed + true + true + HAVE_CONFIG_H=1;WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + $(IntDir)libthrift.pdb + + + Windows + true + true + true + + + + + Level3 + NotUsing + MaxSpeed + true + true + HAVE_CONFIG_H=1;WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + $(IntDir)libthrift.pdb + MultiThreaded + + + Windows + true + true + true + + + + + Level3 + NotUsing + MaxSpeed + true + true + HAVE_CONFIG_H=1;WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + + + Windows + true + true + true + + + + + Level3 + NotUsing + MaxSpeed + true + true + HAVE_CONFIG_H=1;WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + MultiThreaded + + + Windows + true + true + true + + + + + + diff --git a/src/jaegertracing/thrift/lib/cpp/libthrift.vcxproj.filters b/src/jaegertracing/thrift/lib/cpp/libthrift.vcxproj.filters new file mode 100644 index 000000000..5f64f78cb --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/libthrift.vcxproj.filters @@ -0,0 +1,283 @@ + + + + + transport + + + + + windows + + + transport + + + windows + + + concurrency + + + concurrency + + + concurrency + + + protocol + + + protocol + + + protocol + + + protocol + + + transport + + + transport + + + transport + + + transport + + + transport + + + transport + + + transport + + + transport + + + server + + + server + + + server + + + async + + + async + + + processor + + + transport + + + transport + + + windows + + + windows + + + concurrency + + + concurrency + + + concurrency + + + concurrency + + + windows + + + transport + + + transport + + + + + transport + + + transport + + + protocol + + + + + + windows + + + windows + + + concurrency + + + transport + + + transport + + + transport + + + windows + + + transport + + + windows + + + protocol + + + protocol + + + server + + + server + + + server + + + server + + + async + + + async + + + processor + + + processor + + + transport + + + transport + + + transport + + + transport + + + transport + + + transport + + + transport + + + protocol + + + protocol + + + protocol + + + transport + + + windows + + + windows + + + windows + + + windows + + + concurrency + + + concurrency + + + concurrency + + + windows + + + transport + + + transport + + + + + {07ced19b-b72a-4105-9ffb-6d2bcf64497e} + + + {e9f61404-1148-4103-bd6f-e5869d37fa79} + + + {2814002a-3c68-427e-b0eb-33acd2f406ae} + + + {addd4707-dbaa-4d0c-bef6-fff8be7b495a} + + + {f55a8e9b-6959-487f-a396-c31b4d6c61d6} + + + {d526885b-1b3e-4ee3-8027-e694fe98ad63} + + + {8f428da8-5a83-44fb-9578-de935fb415e1} + + + {eea10406-3380-4f2d-9365-c26fa2875dae} + + + + + protocol + + + windows\tr1 + + + diff --git a/src/jaegertracing/thrift/lib/cpp/libthriftnb.vcxproj b/src/jaegertracing/thrift/lib/cpp/libthriftnb.vcxproj new file mode 100755 index 000000000..9a6ffe60d --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/libthriftnb.vcxproj @@ -0,0 +1,298 @@ + + + + + Debug-mt + Win32 + + + Debug-mt + x64 + + + Debug + Win32 + + + Debug + x64 + + + Release-mt + Win32 + + + Release-mt + x64 + + + Release + Win32 + + + Release + x64 + + + + + + + + + + + + + + + + + + + + + + + + {D8696CCE-7D46-4659-B432-91754A41DEB0} + Win32Proj + libthriftnb + + + + StaticLibrary + true + MultiByte + + + StaticLibrary + true + MultiByte + + + StaticLibrary + true + MultiByte + + + StaticLibrary + true + MultiByte + + + StaticLibrary + false + true + MultiByte + + + StaticLibrary + false + true + MultiByte + + + StaticLibrary + false + true + MultiByte + + + StaticLibrary + false + true + MultiByte + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + $(ProjectDir)\src\;$(ProjectDir)\src\thrift\windows\;$(BOOST_ROOT)\include;$(BOOST_ROOT)\;$(LIBEVENT_ROOT)\WIN32-Code\;$(LIBEVENT_ROOT)\include;$(LIBEVENT_ROOT)\;$(IncludePath) + + + $(ProjectDir)\src\;$(ProjectDir)\src\thrift\windows\;$(BOOST_ROOT)\include;$(BOOST_ROOT)\;$(LIBEVENT_ROOT)\WIN32-Code\;$(LIBEVENT_ROOT)\include;$(LIBEVENT_ROOT)\;$(IncludePath) + + + $(ProjectDir)\src\;$(ProjectDir)\src\thrift\windows\;$(BOOST_ROOT)\include;$(BOOST_ROOT)\;$(LIBEVENT_ROOT)\WIN32-Code\;$(LIBEVENT_ROOT)\include;$(LIBEVENT_ROOT)\;$(IncludePath) + + + $(ProjectDir)\src\;$(ProjectDir)\src\thrift\windows\;$(BOOST_ROOT)\include;$(BOOST_ROOT)\;$(LIBEVENT_ROOT)\WIN32-Code\;$(LIBEVENT_ROOT)\include;$(LIBEVENT_ROOT)\;$(IncludePath) + + + $(ProjectDir)\src\;$(ProjectDir)\src\thrift\windows\;$(BOOST_ROOT)\include;$(BOOST_ROOT)\;$(LIBEVENT_ROOT)\WIN32-Code\;$(LIBEVENT_ROOT)\include;$(LIBEVENT_ROOT)\;$(IncludePath) + + + $(ProjectDir)\src\;$(ProjectDir)\src\thrift\windows\;$(BOOST_ROOT)\include;$(BOOST_ROOT)\;$(LIBEVENT_ROOT)\WIN32-Code\;$(LIBEVENT_ROOT)\include;$(LIBEVENT_ROOT)\;$(IncludePath) + + + $(ProjectDir)\src\;$(ProjectDir)\src\thrift\windows\;$(BOOST_ROOT)\include;$(BOOST_ROOT)\;$(LIBEVENT_ROOT)\WIN32-Code\;$(LIBEVENT_ROOT)\include;$(LIBEVENT_ROOT)\;$(IncludePath) + + + $(ProjectDir)\src\;$(ProjectDir)\src\thrift\windows\;$(BOOST_ROOT)\include;$(BOOST_ROOT)\;$(LIBEVENT_ROOT)\WIN32-Code\;$(LIBEVENT_ROOT)\include;$(LIBEVENT_ROOT)\;$(IncludePath) + + + + + + Level3 + Disabled + HAVE_CONFIG_H=1;WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + $(IntDir)libthriftnb.pdb + + + Windows + true + + + + + + + Level3 + Disabled + HAVE_CONFIG_H=1;WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + $(IntDir)libthriftnb.pdb + MultiThreadedDebug + + + Windows + true + + + + + + + Level3 + Disabled + HAVE_CONFIG_H=1;WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + + + Windows + true + + + + + + + Level3 + Disabled + HAVE_CONFIG_H=1;WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + MultiThreadedDebug + + + Windows + true + + + + + Level3 + + + MaxSpeed + true + true + HAVE_CONFIG_H=1;WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + $(IntDir)libthriftnb.pdb + + + Windows + true + true + true + + + + + Level3 + + + MaxSpeed + true + true + HAVE_CONFIG_H=1;WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + $(IntDir)libthriftnb.pdb + MultiThreaded + + + Windows + true + true + true + + + + + Level3 + + + MaxSpeed + true + true + HAVE_CONFIG_H=1;WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + + + Windows + true + true + true + + + + + Level3 + + + MaxSpeed + true + true + HAVE_CONFIG_H=1;WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + MultiThreaded + + + Windows + true + true + true + + + + + + \ No newline at end of file diff --git a/src/jaegertracing/thrift/lib/cpp/libthriftnb.vcxproj.filters b/src/jaegertracing/thrift/lib/cpp/libthriftnb.vcxproj.filters new file mode 100644 index 000000000..85703dd12 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/libthriftnb.vcxproj.filters @@ -0,0 +1,75 @@ + + + + + {bf449d92-4be8-4f6f-a010-c536f57c6f13} + + + {0294d0a6-ce46-4be8-a659-826d6e98ae41} + + + {60fc9e5e-0866-4aba-8662-439bb4a461d3} + + + {23fe2fde-a7c9-43ec-a409-7f53df5eee64} + + + + + server + + + async + + + async + + + async + + + windows + + + transport + + + transport + + + + + server + + + async + + + async + + + async + + + windows + + + windows + + + windows + + + windows + + + transport + + + transport + + + transport + + + diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/TApplicationException.cpp b/src/jaegertracing/thrift/lib/cpp/src/thrift/TApplicationException.cpp new file mode 100644 index 000000000..2f14653ab --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/TApplicationException.cpp @@ -0,0 +1,81 @@ +/* + * 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. + */ + +#include +#include + +namespace apache { +namespace thrift { + +uint32_t TApplicationException::read(apache::thrift::protocol::TProtocol* iprot) { + uint32_t xfer = 0; + std::string fname; + apache::thrift::protocol::TType ftype; + int16_t fid; + + xfer += iprot->readStructBegin(fname); + + while (true) { + xfer += iprot->readFieldBegin(fname, ftype, fid); + if (ftype == apache::thrift::protocol::T_STOP) { + break; + } + switch (fid) { + case 1: + if (ftype == apache::thrift::protocol::T_STRING) { + xfer += iprot->readString(message_); + } else { + xfer += iprot->skip(ftype); + } + break; + case 2: + if (ftype == apache::thrift::protocol::T_I32) { + int32_t type; + xfer += iprot->readI32(type); + type_ = (TApplicationExceptionType)type; + } else { + xfer += iprot->skip(ftype); + } + break; + default: + xfer += iprot->skip(ftype); + break; + } + xfer += iprot->readFieldEnd(); + } + + xfer += iprot->readStructEnd(); + return xfer; +} + +uint32_t TApplicationException::write(apache::thrift::protocol::TProtocol* oprot) const { + uint32_t xfer = 0; + xfer += oprot->writeStructBegin("TApplicationException"); + xfer += oprot->writeFieldBegin("message", apache::thrift::protocol::T_STRING, 1); + xfer += oprot->writeString(message_); + xfer += oprot->writeFieldEnd(); + xfer += oprot->writeFieldBegin("type", apache::thrift::protocol::T_I32, 2); + xfer += oprot->writeI32(type_); + xfer += oprot->writeFieldEnd(); + xfer += oprot->writeFieldStop(); + xfer += oprot->writeStructEnd(); + return xfer; +} +} +} // apache::thrift diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/TApplicationException.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/TApplicationException.h new file mode 100644 index 000000000..cd1b3e7c0 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/TApplicationException.h @@ -0,0 +1,115 @@ +/* + * 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. + */ + +#ifndef _THRIFT_TAPPLICATIONEXCEPTION_H_ +#define _THRIFT_TAPPLICATIONEXCEPTION_H_ 1 + +#include + +namespace apache { +namespace thrift { + +namespace protocol { +class TProtocol; +} + +class TApplicationException : public TException { +public: + /** + * Error codes for the various types of exceptions. + */ + enum TApplicationExceptionType { + UNKNOWN = 0, + UNKNOWN_METHOD = 1, + INVALID_MESSAGE_TYPE = 2, + WRONG_METHOD_NAME = 3, + BAD_SEQUENCE_ID = 4, + MISSING_RESULT = 5, + INTERNAL_ERROR = 6, + PROTOCOL_ERROR = 7, + INVALID_TRANSFORM = 8, + INVALID_PROTOCOL = 9, + UNSUPPORTED_CLIENT_TYPE = 10 + }; + + TApplicationException() : TException(), type_(UNKNOWN) {} + + TApplicationException(TApplicationExceptionType type) : TException(), type_(type) {} + + TApplicationException(const std::string& message) : TException(message), type_(UNKNOWN) {} + + TApplicationException(TApplicationExceptionType type, const std::string& message) + : TException(message), type_(type) {} + + ~TApplicationException() noexcept override = default; + + /** + * Returns an error code that provides information about the type of error + * that has occurred. + * + * @return Error code + */ + TApplicationExceptionType getType() const { return type_; } + + const char* what() const noexcept override { + if (message_.empty()) { + switch (type_) { + case UNKNOWN: + return "TApplicationException: Unknown application exception"; + case UNKNOWN_METHOD: + return "TApplicationException: Unknown method"; + case INVALID_MESSAGE_TYPE: + return "TApplicationException: Invalid message type"; + case WRONG_METHOD_NAME: + return "TApplicationException: Wrong method name"; + case BAD_SEQUENCE_ID: + return "TApplicationException: Bad sequence identifier"; + case MISSING_RESULT: + return "TApplicationException: Missing result"; + case INTERNAL_ERROR: + return "TApplicationException: Internal error"; + case PROTOCOL_ERROR: + return "TApplicationException: Protocol error"; + case INVALID_TRANSFORM: + return "TApplicationException: Invalid transform"; + case INVALID_PROTOCOL: + return "TApplicationException: Invalid protocol"; + case UNSUPPORTED_CLIENT_TYPE: + return "TApplicationException: Unsupported client type"; + default: + return "TApplicationException: (Invalid exception type)"; + }; + } else { + return message_.c_str(); + } + } + + uint32_t read(protocol::TProtocol* iprot); + uint32_t write(protocol::TProtocol* oprot) const; + +protected: + /** + * Error code + */ + TApplicationExceptionType type_; +}; +} +} // apache::thrift + +#endif // #ifndef _THRIFT_TAPPLICATIONEXCEPTION_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/TBase.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/TBase.h new file mode 100644 index 000000000..e2e78e725 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/TBase.h @@ -0,0 +1,38 @@ +/* + * 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. + */ + +#ifndef _THRIFT_TBASE_H_ +#define _THRIFT_TBASE_H_ 1 + +#include +#include + +namespace apache { +namespace thrift { + +class TBase { +public: + virtual ~TBase() = default; + virtual uint32_t read(protocol::TProtocol* iprot) = 0; + virtual uint32_t write(protocol::TProtocol* oprot) const = 0; +}; +} +} // apache::thrift + +#endif // #ifndef _THRIFT_TBASE_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/TDispatchProcessor.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/TDispatchProcessor.h new file mode 100644 index 000000000..ae522b2d8 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/TDispatchProcessor.h @@ -0,0 +1,141 @@ +/* + * 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. + */ +#ifndef _THRIFT_TDISPATCHPROCESSOR_H_ +#define _THRIFT_TDISPATCHPROCESSOR_H_ 1 + +#include + +namespace apache { +namespace thrift { + +/** + * TDispatchProcessor is a helper class to parse the message header then call + * another function to dispatch based on the function name. + * + * Subclasses must implement dispatchCall() to dispatch on the function name. + */ +template +class TDispatchProcessorT : public TProcessor { +public: + bool process(std::shared_ptr in, + std::shared_ptr out, + void* connectionContext) override { + protocol::TProtocol* inRaw = in.get(); + protocol::TProtocol* outRaw = out.get(); + + // Try to dynamic cast to the template protocol type + auto* specificIn = dynamic_cast(inRaw); + auto* specificOut = dynamic_cast(outRaw); + if (specificIn && specificOut) { + return processFast(specificIn, specificOut, connectionContext); + } + + // Log the fact that we have to use the slow path + T_GENERIC_PROTOCOL(this, inRaw, specificIn); + T_GENERIC_PROTOCOL(this, outRaw, specificOut); + + std::string fname; + protocol::TMessageType mtype; + int32_t seqid; + inRaw->readMessageBegin(fname, mtype, seqid); + + // If this doesn't look like a valid call, log an error and return false so + // that the server will close the connection. + // + // (The old generated processor code used to try to skip a T_STRUCT and + // continue. However, that seems unsafe.) + if (mtype != protocol::T_CALL && mtype != protocol::T_ONEWAY) { + GlobalOutput.printf("received invalid message type %d from client", mtype); + return false; + } + + return this->dispatchCall(inRaw, outRaw, fname, seqid, connectionContext); + } + +protected: + bool processFast(Protocol_* in, Protocol_* out, void* connectionContext) { + std::string fname; + protocol::TMessageType mtype; + int32_t seqid; + in->readMessageBegin(fname, mtype, seqid); + + if (mtype != protocol::T_CALL && mtype != protocol::T_ONEWAY) { + GlobalOutput.printf("received invalid message type %d from client", mtype); + return false; + } + + return this->dispatchCallTemplated(in, out, fname, seqid, connectionContext); + } + + /** + * dispatchCall() methods must be implemented by subclasses + */ + virtual bool dispatchCall(apache::thrift::protocol::TProtocol* in, + apache::thrift::protocol::TProtocol* out, + const std::string& fname, + int32_t seqid, + void* callContext) = 0; + + virtual bool dispatchCallTemplated(Protocol_* in, + Protocol_* out, + const std::string& fname, + int32_t seqid, + void* callContext) = 0; +}; + +/** + * Non-templatized version of TDispatchProcessor, that doesn't bother trying to + * perform a dynamic_cast. + */ +class TDispatchProcessor : public TProcessor { +public: + bool process(std::shared_ptr in, + std::shared_ptr out, + void* connectionContext) override { + std::string fname; + protocol::TMessageType mtype; + int32_t seqid; + in->readMessageBegin(fname, mtype, seqid); + + if (mtype != protocol::T_CALL && mtype != protocol::T_ONEWAY) { + GlobalOutput.printf("received invalid message type %d from client", mtype); + return false; + } + + return dispatchCall(in.get(), out.get(), fname, seqid, connectionContext); + } + +protected: + virtual bool dispatchCall(apache::thrift::protocol::TProtocol* in, + apache::thrift::protocol::TProtocol* out, + const std::string& fname, + int32_t seqid, + void* callContext) = 0; +}; + +// Specialize TDispatchProcessorT for TProtocol and TDummyProtocol just to use +// the generic TDispatchProcessor. +template <> +class TDispatchProcessorT : public TDispatchProcessor {}; +template <> +class TDispatchProcessorT : public TDispatchProcessor {}; +} +} // apache::thrift + +#endif // _THRIFT_TDISPATCHPROCESSOR_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/TLogging.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/TLogging.h new file mode 100644 index 000000000..07ff030f7 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/TLogging.h @@ -0,0 +1,195 @@ +/* + * 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. + */ + +#ifndef _THRIFT_TLOGGING_H_ +#define _THRIFT_TLOGGING_H_ 1 + +#include + +/** + * Contains utility macros for debugging and logging. + * + */ + +#include + +#ifdef HAVE_STDINT_H +#include +#endif + +/** + * T_GLOBAL_DEBUGGING_LEVEL = 0: all debugging turned off, debug macros undefined + * T_GLOBAL_DEBUGGING_LEVEL = 1: all debugging turned on + */ +#define T_GLOBAL_DEBUGGING_LEVEL 0 + +/** + * T_GLOBAL_LOGGING_LEVEL = 0: all logging turned off, logging macros undefined + * T_GLOBAL_LOGGING_LEVEL = 1: all logging turned on + */ +#define T_GLOBAL_LOGGING_LEVEL 1 + +/** + * Standard wrapper around fprintf what will prefix the file name and line + * number to the line. Uses T_GLOBAL_DEBUGGING_LEVEL to control whether it is + * turned on or off. + * + * @param format_string + */ +#if T_GLOBAL_DEBUGGING_LEVEL > 0 +#define T_DEBUG(format_string, ...) \ + if (T_GLOBAL_DEBUGGING_LEVEL > 0) { \ + fprintf(stderr, "[%s,%d] " format_string " \n", __FILE__, __LINE__, ##__VA_ARGS__); \ + } +#else +#define T_DEBUG(format_string, ...) +#endif + +/** + * analogous to T_DEBUG but also prints the time + * + * @param string format_string input: printf style format string + */ +#if T_GLOBAL_DEBUGGING_LEVEL > 0 +#define T_DEBUG_T(format_string, ...) \ + { \ + if (T_GLOBAL_DEBUGGING_LEVEL > 0) { \ + time_t now; \ + char dbgtime[26]; \ + time(&now); \ + THRIFT_CTIME_R(&now, dbgtime); \ + dbgtime[24] = '\0'; \ + fprintf(stderr, \ + "[%s,%d] [%s] " format_string " \n", \ + __FILE__, \ + __LINE__, \ + dbgtime, \ + ##__VA_ARGS__); \ + } \ + } +#else +#define T_DEBUG_T(format_string, ...) +#endif + +/** + * analogous to T_DEBUG but uses input level to determine whether or not the string + * should be logged. + * + * @param int level: specified debug level + * @param string format_string input: format string + */ +#define T_DEBUG_L(level, format_string, ...) \ + if ((level) > 0) { \ + fprintf(stderr, "[%s,%d] " format_string " \n", __FILE__, __LINE__, ##__VA_ARGS__); \ + } + +/** + * Explicit error logging. Prints time, file name and line number + * + * @param string format_string input: printf style format string + */ +#define T_ERROR(format_string, ...) \ + { \ + time_t now; \ + char dbgtime[26]; \ + time(&now); \ + THRIFT_CTIME_R(&now, dbgtime); \ + dbgtime[24] = '\0'; \ + fprintf(stderr, \ + "[%s,%d] [%s] ERROR: " format_string " \n", \ + __FILE__, \ + __LINE__, \ + dbgtime, \ + ##__VA_ARGS__); \ + } + +/** + * Analogous to T_ERROR, additionally aborting the process. + * WARNING: macro calls abort(), ending program execution + * + * @param string format_string input: printf style format string + */ +#define T_ERROR_ABORT(format_string, ...) \ + { \ + time_t now; \ + char dbgtime[26]; \ + time(&now); \ + THRIFT_CTIME_R(&now, dbgtime); \ + dbgtime[24] = '\0'; \ + fprintf(stderr, \ + "[%s,%d] [%s] ERROR: Going to abort " format_string " \n", \ + __FILE__, \ + __LINE__, \ + dbgtime, \ + ##__VA_ARGS__); \ + exit(1); \ + } + +/** + * Log input message + * + * @param string format_string input: printf style format string + */ +#if T_GLOBAL_LOGGING_LEVEL > 0 +#define T_LOG_OPER(format_string, ...) \ + { \ + if (T_GLOBAL_LOGGING_LEVEL > 0) { \ + time_t now; \ + char dbgtime[26]; \ + time(&now); \ + THRIFT_CTIME_R(&now, dbgtime); \ + dbgtime[24] = '\0'; \ + fprintf(stderr, "[%s] " format_string " \n", dbgtime, ##__VA_ARGS__); \ + } \ + } +#else +#define T_LOG_OPER(format_string, ...) +#endif + +/** + * T_GLOBAL_DEBUG_VIRTUAL = 0 or unset: normal operation, + * virtual call debug messages disabled + * T_GLOBAL_DEBUG_VIRTUAL = 1: log a debug messages whenever an + * avoidable virtual call is made + * T_GLOBAL_DEBUG_VIRTUAL = 2: record detailed info that can be + * printed by calling + * apache::thrift::profile_print_info() + */ +#if T_GLOBAL_DEBUG_VIRTUAL > 1 +#define T_VIRTUAL_CALL() ::apache::thrift::profile_virtual_call(typeid(*this)) +#define T_GENERIC_PROTOCOL(template_class, generic_prot, specific_prot) \ + do { \ + if (!(specific_prot)) { \ + ::apache::thrift::profile_generic_protocol(typeid(*template_class), typeid(*generic_prot)); \ + } \ + } while (0) +#elif T_GLOBAL_DEBUG_VIRTUAL == 1 +#define T_VIRTUAL_CALL() fprintf(stderr, "[%s,%d] virtual call\n", __FILE__, __LINE__) +#define T_GENERIC_PROTOCOL(template_class, generic_prot, specific_prot) \ + do { \ + if (!(specific_prot)) { \ + fprintf(stderr, "[%s,%d] failed to cast to specific protocol type\n", __FILE__, __LINE__); \ + } \ + } while (0) +#else +#define T_VIRTUAL_CALL() +#define T_GENERIC_PROTOCOL(template_class, generic_prot, specific_prot) +#endif + +#endif // #ifndef _THRIFT_TLOGGING_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/TOutput.cpp b/src/jaegertracing/thrift/lib/cpp/src/thrift/TOutput.cpp new file mode 100644 index 000000000..8d163a941 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/TOutput.cpp @@ -0,0 +1,128 @@ +/* + * 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. + */ + +#include +#include +#include +#include +#include +#include + +namespace apache { +namespace thrift { + +THRIFT_EXPORT TOutput GlobalOutput; + +TOutput::TOutput() : f_(&errorTimeWrapper) {} + +void TOutput::printf(const char* message, ...) { +#ifndef THRIFT_SQUELCH_CONSOLE_OUTPUT + // Try to reduce heap usage, even if printf is called rarely. + static const int STACK_BUF_SIZE = 256; + char stack_buf[STACK_BUF_SIZE]; + va_list ap; + +#ifdef _MSC_VER + va_start(ap, message); + int need = _vscprintf(message, ap); + va_end(ap); + + if (need < STACK_BUF_SIZE) { + va_start(ap, message); + vsnprintf_s(stack_buf, STACK_BUF_SIZE, _TRUNCATE, message, ap); + va_end(ap); + f_(stack_buf); + return; + } +#else + va_start(ap, message); + int need = vsnprintf(stack_buf, STACK_BUF_SIZE, message, ap); + va_end(ap); + + if (need < STACK_BUF_SIZE) { + f_(stack_buf); + return; + } +#endif + + char* heap_buf = (char*)malloc((need + 1) * sizeof(char)); + if (heap_buf == nullptr) { +#ifdef _MSC_VER + va_start(ap, message); + vsnprintf_s(stack_buf, STACK_BUF_SIZE, _TRUNCATE, message, ap); + va_end(ap); +#endif + // Malloc failed. We might as well print the stack buffer. + f_(stack_buf); + return; + } + + va_start(ap, message); + int rval = vsnprintf(heap_buf, need + 1, message, ap); + va_end(ap); + // TODO(shigin): inform user + if (rval != -1) { + f_(heap_buf); + } + free(heap_buf); +#endif +} + +void TOutput::errorTimeWrapper(const char* msg) { +#ifndef THRIFT_SQUELCH_CONSOLE_OUTPUT + time_t now; + char dbgtime[26]; + time(&now); + THRIFT_CTIME_R(&now, dbgtime); + dbgtime[24] = 0; + fprintf(stderr, "Thrift: %s %s\n", dbgtime, msg); +#endif +} + +void TOutput::perror(const char* message, int errno_copy) { + std::string out = message + std::string(": ") + strerror_s(errno_copy); + f_(out.c_str()); +} + +std::string TOutput::strerror_s(int errno_copy) { +#ifndef HAVE_STRERROR_R + return "errno = " + to_string(errno_copy); +#else // HAVE_STRERROR_R + + char b_errbuf[1024] = {'\0'}; +#ifdef STRERROR_R_CHAR_P + char* b_error = strerror_r(errno_copy, b_errbuf, sizeof(b_errbuf)); +#else + char* b_error = b_errbuf; + int rv = strerror_r(errno_copy, b_errbuf, sizeof(b_errbuf)); + if (rv == -1) { + // strerror_r failed. omgwtfbbq. + return "XSI-compliant strerror_r() failed with errno = " + + to_string(errno_copy); + } +#endif + // Can anyone prove that explicit cast is probably not necessary + // to ensure that the string object is constructed before + // b_error becomes invalid? + return std::string(b_error); + +#endif // HAVE_STRERROR_R +} +} +} // apache::thrift diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/TOutput.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/TOutput.h new file mode 100644 index 000000000..26c9a563a --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/TOutput.h @@ -0,0 +1,60 @@ +/* + * 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. + */ + +#ifndef _THRIFT_OUTPUT_H_ +#define _THRIFT_OUTPUT_H_ 1 + +#include + +namespace apache { +namespace thrift { + +class TOutput { +public: + TOutput(); + + inline void setOutputFunction(void (*function)(const char*)) { f_ = function; } + + inline void operator()(const char* message) { f_(message); } + + // It is important to have a const char* overload here instead of + // just the string version, otherwise errno could be corrupted + // if there is some problem allocating memory when constructing + // the string. + void perror(const char* message, int errno_copy); + inline void perror(const std::string& message, int errno_copy) { + perror(message.c_str(), errno_copy); + } + + void printf(const char* message, ...); + + static void errorTimeWrapper(const char* msg); + + /** Just like strerror_r but returns a C++ string object. */ + static std::string strerror_s(int errno_copy); + +private: + void (*f_)(const char*); +}; + +THRIFT_EXPORT extern TOutput GlobalOutput; +} +} // namespace apache::thrift + +#endif //_THRIFT_OUTPUT_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/TProcessor.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/TProcessor.h new file mode 100644 index 000000000..65bf3d4a7 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/TProcessor.h @@ -0,0 +1,229 @@ +/* + * 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. + */ + +#ifndef _THRIFT_TPROCESSOR_H_ +#define _THRIFT_TPROCESSOR_H_ 1 + +#include +#include + +namespace apache { +namespace thrift { + +/** + * Virtual interface class that can handle events from the processor. To + * use this you should subclass it and implement the methods that you care + * about. Your subclass can also store local data that you may care about, + * such as additional "arguments" to these methods (stored in the object + * instance's state). + */ +class TProcessorEventHandler { +public: + virtual ~TProcessorEventHandler() = default; + + /** + * Called before calling other callback methods. + * Expected to return some sort of context object. + * The return value is passed to all other callbacks + * for that function invocation. + */ + virtual void* getContext(const char* fn_name, void* serverContext) { + (void)fn_name; + (void)serverContext; + return nullptr; + } + + /** + * Expected to free resources associated with a context. + */ + virtual void freeContext(void* ctx, const char* fn_name) { + (void)ctx; + (void)fn_name; + } + + /** + * Called before reading arguments. + */ + virtual void preRead(void* ctx, const char* fn_name) { + (void)ctx; + (void)fn_name; + } + + /** + * Called between reading arguments and calling the handler. + */ + virtual void postRead(void* ctx, const char* fn_name, uint32_t bytes) { + (void)ctx; + (void)fn_name; + (void)bytes; + } + + /** + * Called between calling the handler and writing the response. + */ + virtual void preWrite(void* ctx, const char* fn_name) { + (void)ctx; + (void)fn_name; + } + + /** + * Called after writing the response. + */ + virtual void postWrite(void* ctx, const char* fn_name, uint32_t bytes) { + (void)ctx; + (void)fn_name; + (void)bytes; + } + + /** + * Called when an async function call completes successfully. + */ + virtual void asyncComplete(void* ctx, const char* fn_name) { + (void)ctx; + (void)fn_name; + } + + /** + * Called if the handler throws an undeclared exception. + */ + virtual void handlerError(void* ctx, const char* fn_name) { + (void)ctx; + (void)fn_name; + } + +protected: + TProcessorEventHandler() = default; +}; + +/** + * A helper class used by the generated code to free each context. + */ +class TProcessorContextFreer { +public: + TProcessorContextFreer(TProcessorEventHandler* handler, void* context, const char* method) + : handler_(handler), context_(context), method_(method) {} + ~TProcessorContextFreer() { + if (handler_ != nullptr) + handler_->freeContext(context_, method_); + } + void unregister() { handler_ = nullptr; } + +private: + apache::thrift::TProcessorEventHandler* handler_; + void* context_; + const char* method_; +}; + +/** + * A processor is a generic object that acts upon two streams of data, one + * an input and the other an output. The definition of this object is loose, + * though the typical case is for some sort of server that either generates + * responses to an input stream or forwards data from one pipe onto another. + * + */ +class TProcessor { +public: + virtual ~TProcessor() = default; + + virtual bool process(std::shared_ptr in, + std::shared_ptr out, + void* connectionContext) = 0; + + bool process(std::shared_ptr io, void* connectionContext) { + return process(io, io, connectionContext); + } + + std::shared_ptr getEventHandler() const { return eventHandler_; } + + void setEventHandler(std::shared_ptr eventHandler) { + eventHandler_ = eventHandler; + } + +protected: + TProcessor() = default; + + std::shared_ptr eventHandler_; +}; + +/** + * This is a helper class to allow std::shared_ptr to be used with handler + * pointers returned by the generated handler factories. + * + * The handler factory classes generated by the thrift compiler return raw + * pointers, and factory->releaseHandler() must be called when the handler is + * no longer needed. + * + * A ReleaseHandler object can be instantiated and passed as the second + * parameter to a shared_ptr, so that factory->releaseHandler() will be called + * when the object is no longer needed, instead of deleting the pointer. + */ +template +class ReleaseHandler { +public: + ReleaseHandler(const std::shared_ptr& handlerFactory) + : handlerFactory_(handlerFactory) {} + + void operator()(typename HandlerFactory_::Handler* handler) { + if (handler) { + handlerFactory_->releaseHandler(handler); + } + } + +private: + std::shared_ptr handlerFactory_; +}; + +struct TConnectionInfo { + // The input and output protocols + std::shared_ptr input; + std::shared_ptr output; + // The underlying transport used for the connection + // This is the transport that was returned by TServerTransport::accept(), + // and it may be different than the transport pointed to by the input and + // output protocols. + std::shared_ptr transport; +}; + +class TProcessorFactory { +public: + virtual ~TProcessorFactory() = default; + + /** + * Get the TProcessor to use for a particular connection. + * + * This method is always invoked in the same thread that the connection was + * accepted on. This generally means that this call does not need to be + * thread safe, as it will always be invoked from a single thread. + */ + virtual std::shared_ptr getProcessor(const TConnectionInfo& connInfo) = 0; +}; + +class TSingletonProcessorFactory : public TProcessorFactory { +public: + TSingletonProcessorFactory(std::shared_ptr processor) : processor_(processor) {} + + std::shared_ptr getProcessor(const TConnectionInfo&) override { return processor_; } + +private: + std::shared_ptr processor_; +}; +} +} // apache::thrift + +#endif // #ifndef _THRIFT_TPROCESSOR_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/TToString.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/TToString.h new file mode 100644 index 000000000..25780f9d2 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/TToString.h @@ -0,0 +1,114 @@ +/* + * 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. + */ + +#ifndef _THRIFT_TOSTRING_H_ +#define _THRIFT_TOSTRING_H_ 1 + +#include +#include +#include +#include +#include +#include +#include + +namespace apache { +namespace thrift { + +template +std::string to_string(const T& t) { + std::ostringstream o; + o << t; + return o.str(); +} + +// TODO: replace the computations below with std::numeric_limits::max_digits10 once C++11 +// is enabled. +inline std::string to_string(const float& t) { + std::ostringstream o; + o.precision(static_cast(std::ceil(static_cast(std::numeric_limits::digits * std::log10(2.0f) + 1)))); + o << t; + return o.str(); +} + +inline std::string to_string(const double& t) { + std::ostringstream o; + o.precision(static_cast(std::ceil(static_cast(std::numeric_limits::digits * std::log10(2.0f) + 1)))); + o << t; + return o.str(); +} + +inline std::string to_string(const long double& t) { + std::ostringstream o; + o.precision(static_cast(std::ceil(static_cast(std::numeric_limits::digits * std::log10(2.0f) + 1)))); + o << t; + return o.str(); +} + +template +std::string to_string(const std::map& m); + +template +std::string to_string(const std::set& s); + +template +std::string to_string(const std::vector& t); + +template +std::string to_string(const typename std::pair& v) { + std::ostringstream o; + o << to_string(v.first) << ": " << to_string(v.second); + return o.str(); +} + +template +std::string to_string(const T& beg, const T& end) { + std::ostringstream o; + for (T it = beg; it != end; ++it) { + if (it != beg) + o << ", "; + o << to_string(*it); + } + return o.str(); +} + +template +std::string to_string(const std::vector& t) { + std::ostringstream o; + o << "[" << to_string(t.begin(), t.end()) << "]"; + return o.str(); +} + +template +std::string to_string(const std::map& m) { + std::ostringstream o; + o << "{" << to_string(m.begin(), m.end()) << "}"; + return o.str(); +} + +template +std::string to_string(const std::set& s) { + std::ostringstream o; + o << "{" << to_string(s.begin(), s.end()) << "}"; + return o.str(); +} +} +} // apache::thrift + +#endif // _THRIFT_TOSTRING_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/Thrift.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/Thrift.h new file mode 100644 index 000000000..6cb24e660 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/Thrift.h @@ -0,0 +1,133 @@ +/* + * 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. + */ + +#ifndef _THRIFT_THRIFT_H_ +#define _THRIFT_THRIFT_H_ 1 + +#include + +#include + +#include +#include + +#include +#ifdef HAVE_NETINET_IN_H +#include +#endif +#ifdef HAVE_INTTYPES_H +#include +#endif +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define THRIFT_UNUSED_VARIABLE(x) ((void)(x)) + +namespace apache { +namespace thrift { + +class TEnumIterator + : public std::iterator > { +public: + TEnumIterator(int n, int* enums, const char** names) + : ii_(0), n_(n), enums_(enums), names_(names) {} + + int operator++() { return ++ii_; } + + bool operator!=(const TEnumIterator& end) { + THRIFT_UNUSED_VARIABLE(end); + assert(end.n_ == -1); + return (ii_ != n_); + } + + std::pair operator*() const { return std::make_pair(enums_[ii_], names_[ii_]); } + +private: + int ii_; + const int n_; + int* enums_; + const char** names_; +}; + +class TException : public std::exception { +public: + TException() : message_() {} + + TException(const std::string& message) : message_(message) {} + + ~TException() noexcept override = default; + + const char* what() const noexcept override { + if (message_.empty()) { + return "Default TException."; + } else { + return message_.c_str(); + } + } + +protected: + std::string message_; +}; + +class TDelayedException { +public: + template + static TDelayedException* delayException(const E& e); + virtual void throw_it() = 0; + virtual ~TDelayedException() = default; +}; + +template +class TExceptionWrapper : public TDelayedException { +public: + TExceptionWrapper(const E& e) : e_(e) {} + void throw_it() override { + E temp(e_); + delete this; + throw temp; + } + +private: + E e_; +}; + +template +TDelayedException* TDelayedException::delayException(const E& e) { + return new TExceptionWrapper(e); +} + +#if T_GLOBAL_DEBUG_VIRTUAL > 1 +void profile_virtual_call(const std::type_info& info); +void profile_generic_protocol(const std::type_info& template_type, const std::type_info& prot_type); +void profile_print_info(FILE* f); +void profile_print_info(); +void profile_write_pprof(FILE* gen_calls_f, FILE* virtual_calls_f); +#endif +} +} // apache::thrift + +#endif // #ifndef _THRIFT_THRIFT_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/VirtualProfiling.cpp b/src/jaegertracing/thrift/lib/cpp/src/thrift/VirtualProfiling.cpp new file mode 100644 index 000000000..6ce346b82 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/VirtualProfiling.cpp @@ -0,0 +1,425 @@ +/* + * 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. + */ + +#include + +// Do nothing if virtual call profiling is not enabled +#if T_GLOBAL_DEBUG_VIRTUAL > 1 + +// TODO: This code only works with g++ (since we rely on the fact +// that all std::type_info instances referring to a particular type +// always return the exact same pointer value from name().) +#ifndef __GNUG__ +#error "Thrift virtual function profiling currently only works with gcc" +#endif // !__GNUG__ + +// TODO: We also require glibc for the backtrace() and backtrace_symbols() +// functions. +#ifndef __GLIBC__ +#error "Thrift virtual function profiling currently requires glibc" +#endif // !__GLIBC__ + +#include + +#include +#include +#include + +namespace apache { +namespace thrift { + +using ::apache::thrift::concurrency::Mutex; +using ::apache::thrift::concurrency::Guard; + +static const unsigned int MAX_STACK_DEPTH = 15; + +/** + * A stack trace + */ +class Backtrace { +public: + Backtrace(int skip = 0); + Backtrace(Backtrace const& bt); + + void operator=(Backtrace const& bt) { + numCallers_ = bt.numCallers_; + if (numCallers_ >= 0) { + memcpy(callers_, bt.callers_, numCallers_ * sizeof(void*)); + } + } + + bool operator==(Backtrace const& bt) const { return (cmp(bt) == 0); } + + size_t hash() const { + intptr_t ret = 0; + for (int n = 0; n < numCallers_; ++n) { + ret ^= reinterpret_cast(callers_[n]); + } + return static_cast(ret); + } + + int cmp(Backtrace const& bt) const { + int depth_diff = (numCallers_ - bt.numCallers_); + if (depth_diff != 0) { + return depth_diff; + } + + for (int n = 0; n < numCallers_; ++n) { + int diff = reinterpret_cast(callers_[n]) + - reinterpret_cast(bt.callers_[n]); + if (diff != 0) { + return diff; + } + } + + return 0; + } + + void print(FILE* f, int indent = 0, int start = 0) const { + char** strings = backtrace_symbols(callers_, numCallers_); + if (strings) { + start += skip_; + if (start < 0) { + start = 0; + } + for (int n = start; n < numCallers_; ++n) { + fprintf(f, "%*s#%-2d %s\n", indent, "", n, strings[n]); + } + free(strings); + } else { + fprintf(f, "%*s\n", indent, ""); + } + } + + int getDepth() const { return numCallers_ - skip_; } + + void* getFrame(int index) const { + int adjusted_index = index + skip_; + if (adjusted_index < 0 || adjusted_index >= numCallers_) { + return NULL; + } + return callers_[adjusted_index]; + } + +private: + void* callers_[MAX_STACK_DEPTH]; + int numCallers_; + int skip_; +}; + +// Define the constructors non-inline, so they consistently add a single +// frame to the stack trace, regardless of whether optimization is enabled +Backtrace::Backtrace(int skip) + : skip_(skip + 1) // ignore the constructor itself +{ + numCallers_ = backtrace(callers_, MAX_STACK_DEPTH); + if (skip_ > numCallers_) { + skip_ = numCallers_; + } +} + +Backtrace::Backtrace(Backtrace const& bt) : numCallers_(bt.numCallers_), skip_(bt.skip_) { + if (numCallers_ >= 0) { + memcpy(callers_, bt.callers_, numCallers_ * sizeof(void*)); + } +} + +/** + * A backtrace, plus one or two type names + */ +class Key { +public: + class Hash { + public: + size_t operator()(Key const& k) const { return k.hash(); } + }; + + Key(const Backtrace* bt, const std::type_info& type_info) + : backtrace_(bt), typeName1_(type_info.name()), typeName2_(NULL) {} + + Key(const Backtrace* bt, const std::type_info& type_info1, const std::type_info& type_info2) + : backtrace_(bt), typeName1_(type_info1.name()), typeName2_(type_info2.name()) {} + + Key(const Key& k) + : backtrace_(k.backtrace_), typeName1_(k.typeName1_), typeName2_(k.typeName2_) {} + + void operator=(const Key& k) { + backtrace_ = k.backtrace_; + typeName1_ = k.typeName1_; + typeName2_ = k.typeName2_; + } + + const Backtrace* getBacktrace() const { return backtrace_; } + + const char* getTypeName() const { return typeName1_; } + + const char* getTypeName2() const { return typeName2_; } + + void makePersistent() { + // Copy the Backtrace object + backtrace_ = new Backtrace(*backtrace_); + + // NOTE: We don't copy the type name. + // The GNU libstdc++ implementation of type_info::name() returns a value + // that will be valid for the lifetime of the program. (Although the C++ + // standard doesn't guarantee this will be true on all implementations.) + } + + /** + * Clean up memory allocated by makePersistent() + * + * Should only be invoked if makePersistent() has previously been called. + * The Key should no longer be used after cleanup() is called. + */ + void cleanup() { + delete backtrace_; + backtrace_ = NULL; + } + + int cmp(const Key& k) const { + int ret = backtrace_->cmp(*k.backtrace_); + if (ret != 0) { + return ret; + } + + // NOTE: We compare just the name pointers. + // With GNU libstdc++, every type_info object for the same type points to + // exactly the same name string. (Although this isn't guaranteed by the + // C++ standard.) + ret = k.typeName1_ - typeName1_; + if (ret != 0) { + return ret; + } + return k.typeName2_ - typeName2_; + } + + bool operator==(const Key& k) const { return cmp(k) == 0; } + + size_t hash() const { + // NOTE: As above, we just use the name pointer value. + // Works with GNU libstdc++, but not guaranteed to be correct on all + // implementations. + return backtrace_->hash() ^ reinterpret_cast(typeName1_) + ^ reinterpret_cast(typeName2_); + } + +private: + const Backtrace* backtrace_; + const char* typeName1_; + const char* typeName2_; +}; + +/** + * A functor that determines which of two BacktraceMap entries + * has a higher count. + */ +class CountGreater { +public: + bool operator()(std::pair bt1, std::pair bt2) const { + return bt1.second > bt2.second; + } +}; + +typedef __gnu_cxx::hash_map BacktraceMap; + +/** + * A map describing how many times T_VIRTUAL_CALL() has been invoked. + */ +BacktraceMap virtual_calls; +Mutex virtual_calls_mutex; + +/** + * A map describing how many times T_GENERIC_PROTOCOL() has been invoked. + */ +BacktraceMap generic_calls; +Mutex generic_calls_mutex; + +void _record_backtrace(BacktraceMap* map, const Mutex& mutex, Key* k) { + Guard guard(mutex); + + BacktraceMap::iterator it = map->find(*k); + if (it == map->end()) { + k->makePersistent(); + map->insert(std::make_pair(*k, 1)); + } else { + // increment the count + // NOTE: we could assert if it->second is 0 afterwards, since that would + // mean we've wrapped. + ++(it->second); + } +} + +/** + * Record an unnecessary virtual function call. + * + * This method is invoked by the T_VIRTUAL_CALL() macro. + */ +void profile_virtual_call(const std::type_info& type) { + int const skip = 1; // ignore this frame + Backtrace bt(skip); + Key k(&bt, type); + _record_backtrace(&virtual_calls, virtual_calls_mutex, &k); +} + +/** + * Record a call to a template processor with a protocol that is not the one + * specified in the template parameter. + * + * This method is invoked by the T_GENERIC_PROTOCOL() macro. + */ +void profile_generic_protocol(const std::type_info& template_type, + const std::type_info& prot_type) { + int const skip = 1; // ignore this frame + Backtrace bt(skip); + Key k(&bt, template_type, prot_type); + _record_backtrace(&generic_calls, generic_calls_mutex, &k); +} + +/** + * Print the recorded profiling information to the specified file. + */ +void profile_print_info(FILE* f) { + typedef std::vector > BacktraceVector; + + CountGreater is_greater; + + // Grab both locks for the duration of the print operation, + // to ensure the output is a consistent snapshot of a single point in time + Guard generic_calls_guard(generic_calls_mutex); + Guard virtual_calls_guard(virtual_calls_mutex); + + // print the info from generic_calls, sorted by frequency + // + // We print the generic_calls info ahead of virtual_calls, since it is more + // useful in some cases. All T_GENERIC_PROTOCOL calls can be eliminated + // from most programs. Not all T_VIRTUAL_CALLs will be eliminated by + // converting to templates. + BacktraceVector gp_sorted(generic_calls.begin(), generic_calls.end()); + std::sort(gp_sorted.begin(), gp_sorted.end(), is_greater); + + for (BacktraceVector::const_iterator it = gp_sorted.begin(); it != gp_sorted.end(); ++it) { + Key const& key = it->first; + size_t const count = it->second; + fprintf(f, + "T_GENERIC_PROTOCOL: %zu calls to %s with a %s:\n", + count, + key.getTypeName(), + key.getTypeName2()); + key.getBacktrace()->print(f, 2); + fprintf(f, "\n"); + } + + // print the info from virtual_calls, sorted by frequency + BacktraceVector vc_sorted(virtual_calls.begin(), virtual_calls.end()); + std::sort(vc_sorted.begin(), vc_sorted.end(), is_greater); + + for (BacktraceVector::const_iterator it = vc_sorted.begin(); it != vc_sorted.end(); ++it) { + Key const& key = it->first; + size_t const count = it->second; + fprintf(f, "T_VIRTUAL_CALL: %zu calls on %s:\n", count, key.getTypeName()); + key.getBacktrace()->print(f, 2); + fprintf(f, "\n"); + } +} + +/** + * Print the recorded profiling information to stdout. + */ +void profile_print_info() { + profile_print_info(stdout); +} + +/** + * Write a BacktraceMap as Google CPU profiler binary data. + */ +static void profile_write_pprof_file(FILE* f, BacktraceMap const& map) { + // Write the header + uintptr_t header[5] = {0, 3, 0, 0, 0}; + fwrite(&header, sizeof(header), 1, f); + + // Write the profile records + for (BacktraceMap::const_iterator it = map.begin(); it != map.end(); ++it) { + uintptr_t count = it->second; + fwrite(&count, sizeof(count), 1, f); + + Backtrace const* bt = it->first.getBacktrace(); + uintptr_t num_pcs = bt->getDepth(); + fwrite(&num_pcs, sizeof(num_pcs), 1, f); + + for (uintptr_t n = 0; n < num_pcs; ++n) { + void* pc = bt->getFrame(n); + fwrite(&pc, sizeof(pc), 1, f); + } + } + + // Write the trailer + uintptr_t trailer[3] = {0, 1, 0}; + fwrite(&trailer, sizeof(trailer), 1, f); + + // Write /proc/self/maps + // TODO(simpkins): This only works on linux + FILE* proc_maps = fopen("/proc/self/maps", "r"); + if (proc_maps) { + uint8_t buf[4096]; + while (true) { + size_t bytes_read = fread(buf, 1, sizeof(buf), proc_maps); + if (bytes_read == 0) { + break; + } + fwrite(buf, 1, bytes_read, f); + } + fclose(proc_maps); + } +} + +/** + * Write the recorded profiling information as pprof files. + * + * This writes the information using the Google CPU profiler binary data + * format, so it can be analyzed with pprof. Note that information about the + * protocol/transport data types cannot be stored in this file format. + * + * See http://code.google.com/p/google-perftools/ for more details. + * + * @param gen_calls_f The information about calls to + * profile_generic_protocol() will be written to this + * file. + * @param virtual_calls_f The information about calls to + * profile_virtual_call() will be written to this file. + */ +void profile_write_pprof(FILE* gen_calls_f, FILE* virtual_calls_f) { + typedef std::vector > BacktraceVector; + + CountGreater is_greater; + + // Grab both locks for the duration of the print operation, + // to ensure the output is a consistent snapshot of a single point in time + Guard generic_calls_guard(generic_calls_mutex); + Guard virtual_calls_guard(virtual_calls_mutex); + + // write the info from generic_calls + profile_write_pprof_file(gen_calls_f, generic_calls); + + // write the info from virtual_calls + profile_write_pprof_file(virtual_calls_f, virtual_calls); +} +} +} // apache::thrift + +#endif // T_GLOBAL_PROFILE_VIRTUAL > 0 diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/async/TAsyncBufferProcessor.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/async/TAsyncBufferProcessor.h new file mode 100644 index 000000000..e3c3597c2 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/async/TAsyncBufferProcessor.h @@ -0,0 +1,46 @@ +/* + * 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. + */ + +#ifndef _THRIFT_TASYNC_BUFFER_PROCESSOR_H_ +#define _THRIFT_TASYNC_BUFFER_PROCESSOR_H_ 1 + +#include +#include + +namespace apache { +namespace thrift { +namespace async { + +class TAsyncBufferProcessor { +public: + // Process data in "in", putting the result in "out". + // Call _return(true) when done, or _return(false) to + // forcefully close the connection (if applicable). + // "in" and "out" should be TMemoryBuffer or similar, + // not a wrapper around a socket. + virtual void process(std::function _return, + std::shared_ptr ibuf, + std::shared_ptr obuf) = 0; + virtual ~TAsyncBufferProcessor() = default; +}; +} +} +} // apache::thrift::async + +#endif // #ifndef _THRIFT_TASYNC_BUFFER_PROCESSOR_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/async/TAsyncChannel.cpp b/src/jaegertracing/thrift/lib/cpp/src/thrift/async/TAsyncChannel.cpp new file mode 100644 index 000000000..01b91131f --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/async/TAsyncChannel.cpp @@ -0,0 +1,36 @@ +/* + * 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. + */ + +#include + +namespace apache { +namespace thrift { +namespace async { + +void TAsyncChannel::sendAndRecvMessage(const VoidCallback& cob, + TMemoryBuffer* sendBuf, + TMemoryBuffer* recvBuf) { + std::function send_done + = std::bind(&TAsyncChannel::recvMessage, this, cob, recvBuf); + + sendMessage(send_done, sendBuf); +} +} +} +} // apache::thrift::async diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/async/TAsyncChannel.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/async/TAsyncChannel.h new file mode 100644 index 000000000..22cf38388 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/async/TAsyncChannel.h @@ -0,0 +1,74 @@ +/* + * 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. + */ + +#ifndef _THRIFT_ASYNC_TASYNCCHANNEL_H_ +#define _THRIFT_ASYNC_TASYNCCHANNEL_H_ 1 + +#include +#include +#include + +namespace apache { +namespace thrift { +namespace transport { +class TMemoryBuffer; +} +} +} + +namespace apache { +namespace thrift { +namespace async { +using apache::thrift::transport::TMemoryBuffer; + +class TAsyncChannel { +public: + typedef std::function VoidCallback; + + virtual ~TAsyncChannel() = default; + + // is the channel in a good state? + virtual bool good() const = 0; + virtual bool error() const = 0; + virtual bool timedOut() const = 0; + + /** + * Send a message over the channel. + */ + virtual void sendMessage(const VoidCallback& cob, + apache::thrift::transport::TMemoryBuffer* message) = 0; + + /** + * Receive a message from the channel. + */ + virtual void recvMessage(const VoidCallback& cob, + apache::thrift::transport::TMemoryBuffer* message) = 0; + + /** + * Send a message over the channel and receive a response. + */ + virtual void sendAndRecvMessage(const VoidCallback& cob, + apache::thrift::transport::TMemoryBuffer* sendBuf, + apache::thrift::transport::TMemoryBuffer* recvBuf); +}; +} +} +} // apache::thrift::async + +#endif // #ifndef _THRIFT_ASYNC_TASYNCCHANNEL_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/async/TAsyncDispatchProcessor.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/async/TAsyncDispatchProcessor.h new file mode 100644 index 000000000..2a694ac53 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/async/TAsyncDispatchProcessor.h @@ -0,0 +1,151 @@ +/* + * 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. + */ +#ifndef _THRIFT_ASYNC_TASYNCDISPATCHPROCESSOR_H_ +#define _THRIFT_ASYNC_TASYNCDISPATCHPROCESSOR_H_ 1 + +#include + +namespace apache { +namespace thrift { +namespace async { + +/** + * TAsyncDispatchProcessor is a helper class to parse the message header then + * call another function to dispatch based on the function name. + * + * Subclasses must implement dispatchCall() to dispatch on the function name. + */ +template +class TAsyncDispatchProcessorT : public TAsyncProcessor { +public: + void process(std::function _return, + std::shared_ptr in, + std::shared_ptr out) override { + protocol::TProtocol* inRaw = in.get(); + protocol::TProtocol* outRaw = out.get(); + + // Try to dynamic cast to the template protocol type + auto* specificIn = dynamic_cast(inRaw); + auto* specificOut = dynamic_cast(outRaw); + if (specificIn && specificOut) { + return processFast(_return, specificIn, specificOut); + } + + // Log the fact that we have to use the slow path + T_GENERIC_PROTOCOL(this, inRaw, specificIn); + T_GENERIC_PROTOCOL(this, outRaw, specificOut); + + std::string fname; + protocol::TMessageType mtype; + int32_t seqid; + inRaw->readMessageBegin(fname, mtype, seqid); + + // If this doesn't look like a valid call, log an error and return false so + // that the server will close the connection. + // + // (The old generated processor code used to try to skip a T_STRUCT and + // continue. However, that seems unsafe.) + if (mtype != protocol::T_CALL && mtype != protocol::T_ONEWAY) { + GlobalOutput.printf("received invalid message type %d from client", mtype); + _return(false); + return; + } + + return this->dispatchCall(_return, inRaw, outRaw, fname, seqid); + } + + void processFast(std::function _return, + Protocol_* in, + Protocol_* out) { + std::string fname; + protocol::TMessageType mtype; + int32_t seqid; + in->readMessageBegin(fname, mtype, seqid); + + if (mtype != protocol::T_CALL && mtype != protocol::T_ONEWAY) { + GlobalOutput.printf("received invalid message type %d from client", mtype); + _return(false); + return; + } + + return this->dispatchCallTemplated(_return, in, out, fname, seqid); + } + + virtual void dispatchCall(std::function _return, + apache::thrift::protocol::TProtocol* in, + apache::thrift::protocol::TProtocol* out, + const std::string& fname, + int32_t seqid) = 0; + + virtual void dispatchCallTemplated(std::function _return, + Protocol_* in, + Protocol_* out, + const std::string& fname, + int32_t seqid) = 0; +}; + +/** + * Non-templatized version of TAsyncDispatchProcessor, + * that doesn't bother trying to perform a dynamic_cast. + */ +class TAsyncDispatchProcessor : public TAsyncProcessor { +public: + void process(std::function _return, + std::shared_ptr in, + std::shared_ptr out) override { + protocol::TProtocol* inRaw = in.get(); + protocol::TProtocol* outRaw = out.get(); + + std::string fname; + protocol::TMessageType mtype; + int32_t seqid; + inRaw->readMessageBegin(fname, mtype, seqid); + + // If this doesn't look like a valid call, log an error and return false so + // that the server will close the connection. + // + // (The old generated processor code used to try to skip a T_STRUCT and + // continue. However, that seems unsafe.) + if (mtype != protocol::T_CALL && mtype != protocol::T_ONEWAY) { + GlobalOutput.printf("received invalid message type %d from client", mtype); + _return(false); + return; + } + + return dispatchCall(_return, inRaw, outRaw, fname, seqid); + } + + virtual void dispatchCall(std::function _return, + apache::thrift::protocol::TProtocol* in, + apache::thrift::protocol::TProtocol* out, + const std::string& fname, + int32_t seqid) = 0; +}; + +// Specialize TAsyncDispatchProcessorT for TProtocol and TDummyProtocol just to +// use the generic TDispatchProcessor. +template <> +class TAsyncDispatchProcessorT : public TAsyncDispatchProcessor {}; +template <> +class TAsyncDispatchProcessorT : public TAsyncDispatchProcessor {}; +} +} +} // apache::thrift::async + +#endif // _THRIFT_ASYNC_TASYNCDISPATCHPROCESSOR_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/async/TAsyncProcessor.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/async/TAsyncProcessor.h new file mode 100644 index 000000000..019233945 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/async/TAsyncProcessor.h @@ -0,0 +1,84 @@ +/* + * 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. + */ + +#ifndef _THRIFT_TASYNCPROCESSOR_H_ +#define _THRIFT_TASYNCPROCESSOR_H_ 1 + +#include +#include +#include + +namespace apache { +namespace thrift { +namespace async { + +/** + * Async version of a TProcessor. It is not expected to complete by the time + * the call to process returns. Instead, it calls a cob to signal completion. + */ + +class TAsyncProcessor { +public: + virtual ~TAsyncProcessor() = default; + + virtual void process(std::function _return, + std::shared_ptr in, + std::shared_ptr out) = 0; + + void process(std::function _return, + std::shared_ptr io) { + return process(_return, io, io); + } + + std::shared_ptr getEventHandler() const { return eventHandler_; } + + void setEventHandler(std::shared_ptr eventHandler) { + eventHandler_ = eventHandler; + } + +protected: + TAsyncProcessor() = default; + + std::shared_ptr eventHandler_; +}; + +class TAsyncProcessorFactory { +public: + virtual ~TAsyncProcessorFactory() = default; + + /** + * Get the TAsyncProcessor to use for a particular connection. + * + * This method is always invoked in the same thread that the connection was + * accepted on. This generally means that this call does not need to be + * thread safe, as it will always be invoked from a single thread. + */ + virtual std::shared_ptr getProcessor(const TConnectionInfo& connInfo) = 0; +}; +} +} +} // apache::thrift::async + +namespace apache { +namespace thrift { + using apache::thrift::async::TAsyncProcessor; +} +} + +#endif // #ifndef _THRIFT_TASYNCPROCESSOR_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/async/TAsyncProtocolProcessor.cpp b/src/jaegertracing/thrift/lib/cpp/src/thrift/async/TAsyncProtocolProcessor.cpp new file mode 100644 index 000000000..cb5201bf6 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/async/TAsyncProtocolProcessor.cpp @@ -0,0 +1,53 @@ +/* + * 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. + */ + +#include + +using apache::thrift::transport::TBufferBase; +using apache::thrift::protocol::TProtocol; + +namespace apache { +namespace thrift { +namespace async { + +void TAsyncProtocolProcessor::process(std::function _return, + std::shared_ptr ibuf, + std::shared_ptr obuf) { + std::shared_ptr iprot(pfact_->getProtocol(ibuf)); + std::shared_ptr oprot(pfact_->getProtocol(obuf)); + return underlying_ + ->process(std::bind(&TAsyncProtocolProcessor::finish, + _return, + oprot, + std::placeholders::_1), + iprot, + oprot); +} + +/* static */ void TAsyncProtocolProcessor::finish( + std::function _return, + std::shared_ptr oprot, + bool healthy) { + (void)oprot; + // This is a stub function to hold a reference to oprot. + return _return(healthy); +} +} +} +} // apache::thrift::async diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/async/TAsyncProtocolProcessor.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/async/TAsyncProtocolProcessor.h new file mode 100644 index 000000000..ace72b6dc --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/async/TAsyncProtocolProcessor.h @@ -0,0 +1,55 @@ +/* + * 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. + */ + +#ifndef _THRIFT_TNAME_ME_H_ +#define _THRIFT_TNAME_ME_H_ 1 + +#include +#include +#include + +namespace apache { +namespace thrift { +namespace async { + +class TAsyncProtocolProcessor : public TAsyncBufferProcessor { +public: + TAsyncProtocolProcessor(std::shared_ptr underlying, + std::shared_ptr pfact) + : underlying_(underlying), pfact_(pfact) {} + + void process(std::function _return, + std::shared_ptr ibuf, + std::shared_ptr obuf) override; + + ~TAsyncProtocolProcessor() override = default; + +private: + static void finish(std::function _return, + std::shared_ptr oprot, + bool healthy); + + std::shared_ptr underlying_; + std::shared_ptr pfact_; +}; +} +} +} // apache::thrift::async + +#endif // #ifndef _THRIFT_TNAME_ME_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/async/TConcurrentClientSyncInfo.cpp b/src/jaegertracing/thrift/lib/cpp/src/thrift/async/TConcurrentClientSyncInfo.cpp new file mode 100644 index 000000000..0dac52458 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/async/TConcurrentClientSyncInfo.cpp @@ -0,0 +1,243 @@ +/* + * 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. + */ + +#include +#include +#include +#include +#include + +namespace apache { namespace thrift { namespace async { + +using namespace ::apache::thrift::concurrency; + +TConcurrentClientSyncInfo::TConcurrentClientSyncInfo() : + stop_(false), + seqidMutex_(), + // test rollover all the time + nextseqid_((std::numeric_limits::max)()-10), + seqidToMonitorMap_(), + freeMonitors_(), + writeMutex_(), + readMutex_(), + recvPending_(false), + wakeupSomeone_(false), + seqidPending_(0), + fnamePending_(), + mtypePending_(::apache::thrift::protocol::T_CALL) +{ + freeMonitors_.reserve(MONITOR_CACHE_SIZE); +} + +bool TConcurrentClientSyncInfo::getPending( + std::string &fname, + ::apache::thrift::protocol::TMessageType &mtype, + int32_t &rseqid) +{ + if(stop_) + throwDeadConnection_(); + wakeupSomeone_ = false; + if(recvPending_) + { + recvPending_ = false; + rseqid = seqidPending_; + fname = fnamePending_; + mtype = mtypePending_; + return true; + } + return false; +} + +void TConcurrentClientSyncInfo::updatePending( + const std::string &fname, + ::apache::thrift::protocol::TMessageType mtype, + int32_t rseqid) +{ + recvPending_ = true; + seqidPending_ = rseqid; + fnamePending_ = fname; + mtypePending_ = mtype; + MonitorPtr monitor; + { + Guard seqidGuard(seqidMutex_); + auto i = seqidToMonitorMap_.find(rseqid); + if(i == seqidToMonitorMap_.end()) + throwBadSeqId_(); + monitor = i->second; + } + monitor->notify(); +} + +void TConcurrentClientSyncInfo::waitForWork(int32_t seqid) +{ + MonitorPtr m; + { + Guard seqidGuard(seqidMutex_); + m = seqidToMonitorMap_[seqid]; + } + while(true) + { + // be very careful about setting state in this loop that affects waking up. You may exit + // this function, attempt to grab some work, and someone else could have beaten you (or not + // left) the read mutex, and that will put you right back in this loop, with the mangled + // state you left behind. + if(stop_) + throwDeadConnection_(); + if(wakeupSomeone_) + return; + if(recvPending_ && seqidPending_ == seqid) + return; + m->waitForever(); + } +} + +void TConcurrentClientSyncInfo::throwBadSeqId_() +{ + throw apache::thrift::TApplicationException( + TApplicationException::BAD_SEQUENCE_ID, + "server sent a bad seqid"); +} + +void TConcurrentClientSyncInfo::throwDeadConnection_() +{ + throw apache::thrift::transport::TTransportException( + apache::thrift::transport::TTransportException::NOT_OPEN, + "this client died on another thread, and is now in an unusable state"); +} + +void TConcurrentClientSyncInfo::wakeupAnyone_(const Guard &) +{ + wakeupSomeone_ = true; + if(!seqidToMonitorMap_.empty()) + { + // The monitor map maps integers to monitors. Larger integers are more recent + // messages. Since this is ordered, it means that the last element is the most recent. + // We are trying to guess which thread will have its message complete next, so we are picking + // the most recent. The oldest message is likely to be some polling, long lived message. + // If we guess right, the thread we wake up will handle the message that comes in. + // If we guess wrong, the thread we wake up will hand off the work to the correct thread, + // costing us an extra context switch. + seqidToMonitorMap_.rbegin()->second->notify(); + } +} + +void TConcurrentClientSyncInfo::markBad_(const Guard &) +{ + wakeupSomeone_ = true; + stop_ = true; + for(auto & i : seqidToMonitorMap_) + i.second->notify(); +} + +TConcurrentClientSyncInfo::MonitorPtr +TConcurrentClientSyncInfo::newMonitor_(const Guard &) +{ + if(freeMonitors_.empty()) + return std::make_shared(&readMutex_); + MonitorPtr retval; + //swapping to avoid an atomic operation + retval.swap(freeMonitors_.back()); + freeMonitors_.pop_back(); + return retval; +} + +void TConcurrentClientSyncInfo::deleteMonitor_( + const Guard &, + TConcurrentClientSyncInfo::MonitorPtr &m) /*noexcept*/ +{ + if(freeMonitors_.size() > MONITOR_CACHE_SIZE) + { + m.reset(); + return; + } + //freeMonitors_ was reserved up to MONITOR_CACHE_SIZE in the ctor, + //so this shouldn't throw + freeMonitors_.push_back(TConcurrentClientSyncInfo::MonitorPtr()); + //swapping to avoid an atomic operation + m.swap(freeMonitors_.back()); +} + +int32_t TConcurrentClientSyncInfo::generateSeqId() +{ + Guard seqidGuard(seqidMutex_); + if(stop_) + throwDeadConnection_(); + + if(!seqidToMonitorMap_.empty()) + if(nextseqid_ == seqidToMonitorMap_.begin()->first) + throw apache::thrift::TApplicationException( + TApplicationException::BAD_SEQUENCE_ID, + "about to repeat a seqid"); + int32_t newSeqId = nextseqid_++; + seqidToMonitorMap_[newSeqId] = newMonitor_(seqidGuard); + return newSeqId; +} + +TConcurrentRecvSentry::TConcurrentRecvSentry(TConcurrentClientSyncInfo *sync, int32_t seqid) : + sync_(*sync), + seqid_(seqid), + committed_(false) +{ + sync_.getReadMutex().lock(); +} + +TConcurrentRecvSentry::~TConcurrentRecvSentry() +{ + { + Guard seqidGuard(sync_.seqidMutex_); + sync_.deleteMonitor_(seqidGuard, sync_.seqidToMonitorMap_[seqid_]); + + sync_.seqidToMonitorMap_.erase(seqid_); + if(committed_) + sync_.wakeupAnyone_(seqidGuard); + else + sync_.markBad_(seqidGuard); + } + sync_.getReadMutex().unlock(); +} + +void TConcurrentRecvSentry::commit() +{ + committed_ = true; +} + +TConcurrentSendSentry::TConcurrentSendSentry(TConcurrentClientSyncInfo *sync) : + sync_(*sync), + committed_(false) +{ + sync_.getWriteMutex().lock(); +} + +TConcurrentSendSentry::~TConcurrentSendSentry() +{ + if(!committed_) + { + Guard seqidGuard(sync_.seqidMutex_); + sync_.markBad_(seqidGuard); + } + sync_.getWriteMutex().unlock(); +} + +void TConcurrentSendSentry::commit() +{ + committed_ = true; +} + + +}}} // apache::thrift::async diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/async/TConcurrentClientSyncInfo.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/async/TConcurrentClientSyncInfo.h new file mode 100644 index 000000000..0bc5eb565 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/async/TConcurrentClientSyncInfo.h @@ -0,0 +1,126 @@ +/* + * 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. + */ +#ifndef _THRIFT_TCONCURRENTCLIENTSYNCINFO_H_ +#define _THRIFT_TCONCURRENTCLIENTSYNCINFO_H_ 1 + +#include +#include +#include +#include +#include +#include +#include + +namespace apache { +namespace thrift { +namespace async { + +class TConcurrentClientSyncInfo; + +class TConcurrentSendSentry { +public: + explicit TConcurrentSendSentry(TConcurrentClientSyncInfo* sync); + ~TConcurrentSendSentry(); + + void commit(); + +private: + TConcurrentClientSyncInfo& sync_; + bool committed_; +}; + +class TConcurrentRecvSentry { +public: + TConcurrentRecvSentry(TConcurrentClientSyncInfo* sync, int32_t seqid); + ~TConcurrentRecvSentry(); + + void commit(); + +private: + TConcurrentClientSyncInfo& sync_; + int32_t seqid_; + bool committed_; +}; + +class TConcurrentClientSyncInfo { +private: // typedefs + typedef std::shared_ptr< ::apache::thrift::concurrency::Monitor> MonitorPtr; + typedef std::map MonitorMap; + +public: + TConcurrentClientSyncInfo(); + + int32_t generateSeqId(); + + bool getPending(std::string& fname, + ::apache::thrift::protocol::TMessageType& mtype, + int32_t& rseqid); /* requires readMutex_ */ + + void updatePending(const std::string& fname, + ::apache::thrift::protocol::TMessageType mtype, + int32_t rseqid); /* requires readMutex_ */ + + void waitForWork(int32_t seqid); /* requires readMutex_ */ + + ::apache::thrift::concurrency::Mutex& getReadMutex() { return readMutex_; } + ::apache::thrift::concurrency::Mutex& getWriteMutex() { return writeMutex_; } + +private: // constants + enum { MONITOR_CACHE_SIZE = 10 }; + +private: // functions + MonitorPtr newMonitor_( + const ::apache::thrift::concurrency::Guard& seqidGuard); /* requires seqidMutex_ */ + void deleteMonitor_(const ::apache::thrift::concurrency::Guard& seqidGuard, MonitorPtr& m); + /*noexcept*/ /* requires seqidMutex_ */ + void wakeupAnyone_( + const ::apache::thrift::concurrency::Guard& seqidGuard); /* requires seqidMutex_ */ + void markBad_(const ::apache::thrift::concurrency::Guard& seqidGuard); /* requires seqidMutex_ */ + void throwBadSeqId_(); + void throwDeadConnection_(); + +private: // data members + volatile bool stop_; + + ::apache::thrift::concurrency::Mutex seqidMutex_; + // begin seqidMutex_ protected members + int32_t nextseqid_; + MonitorMap seqidToMonitorMap_; + std::vector freeMonitors_; + // end seqidMutex_ protected members + + ::apache::thrift::concurrency::Mutex writeMutex_; + + ::apache::thrift::concurrency::Mutex readMutex_; + // begin readMutex_ protected members + bool recvPending_; + bool wakeupSomeone_; + int32_t seqidPending_; + std::string fnamePending_; + ::apache::thrift::protocol::TMessageType mtypePending_; + // end readMutex_ protected members + + friend class TConcurrentSendSentry; + friend class TConcurrentRecvSentry; +}; +} +} +} // apache::thrift::async + +#endif // _THRIFT_TCONCURRENTCLIENTSYNCINFO_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/async/TEvhttpClientChannel.cpp b/src/jaegertracing/thrift/lib/cpp/src/thrift/async/TEvhttpClientChannel.cpp new file mode 100644 index 000000000..765659685 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/async/TEvhttpClientChannel.cpp @@ -0,0 +1,156 @@ +/* + * 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. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace apache::thrift::protocol; +using apache::thrift::transport::TTransportException; + +namespace apache { +namespace thrift { +namespace async { + +TEvhttpClientChannel::TEvhttpClientChannel(const std::string& host, + const std::string& path, + const char* address, + int port, + struct event_base* eb, + struct evdns_base* dnsbase) + + : host_(host), path_(path), conn_(nullptr) { + conn_ = evhttp_connection_base_new(eb, dnsbase, address, port); + if (conn_ == nullptr) { + throw TException("evhttp_connection_new failed"); + } +} + +TEvhttpClientChannel::~TEvhttpClientChannel() { + if (conn_ != nullptr) { + evhttp_connection_free(conn_); + } +} + +void TEvhttpClientChannel::sendAndRecvMessage(const VoidCallback& cob, + apache::thrift::transport::TMemoryBuffer* sendBuf, + apache::thrift::transport::TMemoryBuffer* recvBuf) { + struct evhttp_request* req = evhttp_request_new(response, this); + if (req == nullptr) { + throw TException("evhttp_request_new failed"); + } + + int rv; + + rv = evhttp_add_header(req->output_headers, "Host", host_.c_str()); + if (rv != 0) { + throw TException("evhttp_add_header failed"); + } + + rv = evhttp_add_header(req->output_headers, "Content-Type", "application/x-thrift"); + if (rv != 0) { + throw TException("evhttp_add_header failed"); + } + + uint8_t* obuf; + uint32_t sz; + sendBuf->getBuffer(&obuf, &sz); + rv = evbuffer_add(req->output_buffer, obuf, sz); + if (rv != 0) { + throw TException("evbuffer_add failed"); + } + + rv = evhttp_make_request(conn_, req, EVHTTP_REQ_POST, path_.c_str()); + if (rv != 0) { + throw TException("evhttp_make_request failed"); + } + + completionQueue_.push(Completion(cob, recvBuf)); +} + +void TEvhttpClientChannel::sendMessage(const VoidCallback& cob, + apache::thrift::transport::TMemoryBuffer* message) { + (void)cob; + (void)message; + throw TProtocolException(TProtocolException::NOT_IMPLEMENTED, + "Unexpected call to TEvhttpClientChannel::sendMessage"); +} + +void TEvhttpClientChannel::recvMessage(const VoidCallback& cob, + apache::thrift::transport::TMemoryBuffer* message) { + (void)cob; + (void)message; + throw TProtocolException(TProtocolException::NOT_IMPLEMENTED, + "Unexpected call to TEvhttpClientChannel::recvMessage"); +} + +void TEvhttpClientChannel::finish(struct evhttp_request* req) { + assert(!completionQueue_.empty()); + Completion completion = completionQueue_.front(); + completionQueue_.pop(); + if (req == nullptr) { + try { + completion.first(); + } catch (const TTransportException& e) { + if (e.getType() == TTransportException::END_OF_FILE) + throw TException("connect failed"); + else + throw; + } + return; + } else if (req->response_code != 200) { + try { + completion.first(); + } catch (const TTransportException& e) { + std::stringstream ss; + ss << "server returned code " << req->response_code; + if (req->response_code_line) + ss << ": " << req->response_code_line; + if (e.getType() == TTransportException::END_OF_FILE) + throw TException(ss.str()); + else + throw; + } + return; + } + completion.second->resetBuffer(EVBUFFER_DATA(req->input_buffer), + static_cast(EVBUFFER_LENGTH(req->input_buffer))); + completion.first(); + return; +} + +/* static */ void TEvhttpClientChannel::response(struct evhttp_request* req, void* arg) { + auto* self = (TEvhttpClientChannel*)arg; + try { + self->finish(req); + } catch (std::exception& e) { + // don't propagate a C++ exception in C code (e.g. libevent) + std::cerr << "TEvhttpClientChannel::response exception thrown (ignored): " << e.what() + << std::endl; + } +} +} +} +} // apache::thrift::async diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/async/TEvhttpClientChannel.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/async/TEvhttpClientChannel.h new file mode 100644 index 000000000..f74272665 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/async/TEvhttpClientChannel.h @@ -0,0 +1,88 @@ +/* + * 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. + */ + +#ifndef _THRIFT_TEVHTTP_CLIENT_CHANNEL_H_ +#define _THRIFT_TEVHTTP_CLIENT_CHANNEL_H_ 1 + +#include +#include +#include +#include +#include + +struct event_base; +struct evdns_base; +struct evhttp_connection; +struct evhttp_request; + +namespace apache { +namespace thrift { +namespace transport { +class TMemoryBuffer; +} +} +} + +namespace apache { +namespace thrift { +namespace async { + +class TEvhttpClientChannel : public TAsyncChannel { +public: + using TAsyncChannel::VoidCallback; + + TEvhttpClientChannel(const std::string& host, + const std::string& path, + const char* address, + int port, + struct event_base* eb, + struct evdns_base *dnsbase = nullptr); + ~TEvhttpClientChannel() override; + + void sendAndRecvMessage(const VoidCallback& cob, + apache::thrift::transport::TMemoryBuffer* sendBuf, + apache::thrift::transport::TMemoryBuffer* recvBuf) override; + + void sendMessage(const VoidCallback& cob, + apache::thrift::transport::TMemoryBuffer* message) override; + void recvMessage(const VoidCallback& cob, + apache::thrift::transport::TMemoryBuffer* message) override; + + void finish(struct evhttp_request* req); + + // XXX + bool good() const override { return true; } + bool error() const override { return false; } + bool timedOut() const override { return false; } + +private: + static void response(struct evhttp_request* req, void* arg); + + std::string host_; + std::string path_; + typedef std::pair Completion; + typedef std::queue CompletionQueue; + CompletionQueue completionQueue_; + struct evhttp_connection* conn_; +}; +} +} +} // apache::thrift::async + +#endif // #ifndef _THRIFT_TEVHTTP_CLIENT_CHANNEL_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/async/TEvhttpServer.cpp b/src/jaegertracing/thrift/lib/cpp/src/thrift/async/TEvhttpServer.cpp new file mode 100644 index 000000000..7d2cf21c0 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/async/TEvhttpServer.cpp @@ -0,0 +1,160 @@ +/* + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef HTTP_INTERNAL // libevent < 2 +#define HTTP_INTERNAL 500 +#endif + +using apache::thrift::transport::TMemoryBuffer; +using std::shared_ptr; + +namespace apache { +namespace thrift { +namespace async { + +struct TEvhttpServer::RequestContext { + struct evhttp_request* req; + std::shared_ptr ibuf; + std::shared_ptr obuf; + + RequestContext(struct evhttp_request* req); +}; + +TEvhttpServer::TEvhttpServer(std::shared_ptr processor) + : processor_(processor), eb_(nullptr), eh_(nullptr) { +} + +TEvhttpServer::TEvhttpServer(std::shared_ptr processor, int port) + : processor_(processor), eb_(nullptr), eh_(nullptr) { + // Create event_base and evhttp. + eb_ = event_base_new(); + if (eb_ == nullptr) { + throw TException("event_base_new failed"); + } + eh_ = evhttp_new(eb_); + if (eh_ == nullptr) { + event_base_free(eb_); + throw TException("evhttp_new failed"); + } + + // Bind to port. + int ret = evhttp_bind_socket(eh_, nullptr, port); + if (ret < 0) { + evhttp_free(eh_); + event_base_free(eb_); + throw TException("evhttp_bind_socket failed"); + } + + // Register a handler. If you use the other constructor, + // you will want to do this yourself. + // Don't forget to unregister before destorying this TEvhttpServer. + evhttp_set_cb(eh_, "/", request, (void*)this); +} + +TEvhttpServer::~TEvhttpServer() { + if (eh_ != nullptr) { + evhttp_free(eh_); + } + if (eb_ != nullptr) { + event_base_free(eb_); + } +} + +int TEvhttpServer::serve() { + if (eb_ == nullptr) { + throw TException("Unexpected call to TEvhttpServer::serve"); + } + return event_base_dispatch(eb_); +} + +TEvhttpServer::RequestContext::RequestContext(struct evhttp_request* req) + : req(req), + ibuf(new TMemoryBuffer(EVBUFFER_DATA(req->input_buffer), + static_cast(EVBUFFER_LENGTH(req->input_buffer)))), + obuf(new TMemoryBuffer()) { +} + +void TEvhttpServer::request(struct evhttp_request* req, void* self) { + try { + static_cast(self)->process(req); + } catch (std::exception& e) { + evhttp_send_reply(req, HTTP_INTERNAL, e.what(), nullptr); + } +} + +void TEvhttpServer::process(struct evhttp_request* req) { + auto* ctx = new RequestContext(req); + return processor_->process(std::bind(&TEvhttpServer::complete, + this, + ctx, + std::placeholders::_1), + ctx->ibuf, + ctx->obuf); +} + +void TEvhttpServer::complete(RequestContext* ctx, bool success) { + (void)success; + std::unique_ptr ptr(ctx); + + int code = success ? 200 : 400; + const char* reason = success ? "OK" : "Bad Request"; + + int rv = evhttp_add_header(ctx->req->output_headers, "Content-Type", "application/x-thrift"); + if (rv != 0) { + // TODO: Log an error. + std::cerr << "evhttp_add_header failed " << __FILE__ << ":" << __LINE__ << std::endl; + } + + struct evbuffer* buf = evbuffer_new(); + if (buf == nullptr) { + // TODO: Log an error. + std::cerr << "evbuffer_new failed " << __FILE__ << ":" << __LINE__ << std::endl; + } else { + uint8_t* obuf; + uint32_t sz; + ctx->obuf->getBuffer(&obuf, &sz); + int ret = evbuffer_add(buf, obuf, sz); + if (ret != 0) { + // TODO: Log an error. + std::cerr << "evhttp_add failed with " << ret << " " << __FILE__ << ":" << __LINE__ + << std::endl; + } + } + + evhttp_send_reply(ctx->req, code, reason, buf); + if (buf != nullptr) { + evbuffer_free(buf); + } +} + +struct event_base* TEvhttpServer::getEventBase() { + return eb_; +} +} +} +} // apache::thrift::async diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/async/TEvhttpServer.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/async/TEvhttpServer.h new file mode 100644 index 000000000..c5bf3b6ee --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/async/TEvhttpServer.h @@ -0,0 +1,74 @@ +/* + * 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. + */ + +#ifndef _THRIFT_TEVHTTP_SERVER_H_ +#define _THRIFT_TEVHTTP_SERVER_H_ 1 + +#include + +struct event_base; +struct evhttp; +struct evhttp_request; + +namespace apache { +namespace thrift { +namespace async { + +class TAsyncBufferProcessor; + +class TEvhttpServer { +public: + /** + * Create a TEvhttpServer for use with an external evhttp instance. + * Must be manually installed with evhttp_set_cb, using + * TEvhttpServer::request as the callback and the + * address of the server as the extra arg. + * Do not call "serve" on this server. + */ + TEvhttpServer(std::shared_ptr processor); + + /** + * Create a TEvhttpServer with an embedded event_base and evhttp, + * listening on port and responding on the endpoint "/". + * Call "serve" on this server to serve forever. + */ + TEvhttpServer(std::shared_ptr processor, int port); + + ~TEvhttpServer(); + + static void request(struct evhttp_request* req, void* self); + int serve(); + + struct event_base* getEventBase(); + +private: + struct RequestContext; + + void process(struct evhttp_request* req); + void complete(RequestContext* ctx, bool success); + + std::shared_ptr processor_; + struct event_base* eb_; + struct evhttp* eh_; +}; +} +} +} // apache::thrift::async + +#endif // #ifndef _THRIFT_TEVHTTP_SERVER_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/concurrency/Exception.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/concurrency/Exception.h new file mode 100644 index 000000000..947fc9f04 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/concurrency/Exception.h @@ -0,0 +1,64 @@ +/* + * 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. + */ + +#ifndef _THRIFT_CONCURRENCY_EXCEPTION_H_ +#define _THRIFT_CONCURRENCY_EXCEPTION_H_ 1 + +#include +#include + +namespace apache { +namespace thrift { +namespace concurrency { + +class NoSuchTaskException : public apache::thrift::TException {}; + +class UncancellableTaskException : public apache::thrift::TException {}; + +class InvalidArgumentException : public apache::thrift::TException {}; + +class IllegalStateException : public apache::thrift::TException { +public: + IllegalStateException() = default; + IllegalStateException(const std::string& message) : TException(message) {} +}; + +class TimedOutException : public apache::thrift::TException { +public: + TimedOutException() : TException("TimedOutException"){}; + TimedOutException(const std::string& message) : TException(message) {} +}; + +class TooManyPendingTasksException : public apache::thrift::TException { +public: + TooManyPendingTasksException() : TException("TooManyPendingTasksException"){}; + TooManyPendingTasksException(const std::string& message) : TException(message) {} +}; + +class SystemResourceException : public apache::thrift::TException { +public: + SystemResourceException() = default; + + SystemResourceException(const std::string& message) : TException(message) {} +}; +} +} +} // apache::thrift::concurrency + +#endif // #ifndef _THRIFT_CONCURRENCY_EXCEPTION_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/concurrency/FunctionRunner.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/concurrency/FunctionRunner.h new file mode 100644 index 000000000..468834416 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/concurrency/FunctionRunner.h @@ -0,0 +1,118 @@ +/* + * 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. + */ + +#ifndef _THRIFT_CONCURRENCY_FUNCTION_RUNNER_H +#define _THRIFT_CONCURRENCY_FUNCTION_RUNNER_H 1 + +#include +#include + +namespace apache { +namespace thrift { +namespace concurrency { + +/** + * Convenient implementation of Runnable that will execute arbitrary callbacks. + * Interfaces are provided to accept both a generic 'void(void)' callback, and + * a 'void* (void*)' pthread_create-style callback. + * + * Example use: + * void* my_thread_main(void* arg); + * shared_ptr factory = ...; + * // To create a thread that executes my_thread_main once: + * shared_ptr thread = factory->newThread( + * FunctionRunner::create(my_thread_main, some_argument)); + * thread->start(); + * + * bool A::foo(); + * A* a = new A(); + * // To create a thread that executes a.foo() every 100 milliseconds: + * factory->newThread(FunctionRunner::create( + * std::bind(&A::foo, a), 100))->start(); + * + */ + +class FunctionRunner : public Runnable { +public: + // This is the type of callback 'pthread_create()' expects. + typedef void* (*PthreadFuncPtr)(void* arg); + // This a fully-generic void(void) callback for custom bindings. + typedef std::function VoidFunc; + + typedef std::function BoolFunc; + + /** + * Syntactic sugar to make it easier to create new FunctionRunner + * objects wrapped in shared_ptr. + */ + static std::shared_ptr create(const VoidFunc& cob) { + return std::shared_ptr(new FunctionRunner(cob)); + } + + static std::shared_ptr create(PthreadFuncPtr func, void* arg) { + return std::shared_ptr(new FunctionRunner(func, arg)); + } + +private: + static void pthread_func_wrapper(PthreadFuncPtr func, void* arg) { + // discard return value + func(arg); + } + +public: + /** + * Given a 'pthread_create' style callback, this FunctionRunner will + * execute the given callback. Note that the 'void*' return value is ignored. + */ + FunctionRunner(PthreadFuncPtr func, void* arg) + : func_(std::bind(pthread_func_wrapper, func, arg)), intervalMs_(-1) {} + + /** + * Given a generic callback, this FunctionRunner will execute it. + */ + FunctionRunner(const VoidFunc& cob) : func_(cob), intervalMs_(-1) {} + + /** + * Given a bool foo(...) type callback, FunctionRunner will execute + * the callback repeatedly with 'intervalMs' milliseconds between the calls, + * until it returns false. Note that the actual interval between calls will + * be intervalMs plus execution time of the callback. + */ + FunctionRunner(const BoolFunc& cob, int intervalMs) : repFunc_(cob), intervalMs_(intervalMs) {} + + void run() override { + if (repFunc_) { + while (repFunc_()) { + THRIFT_SLEEP_USEC(intervalMs_ * 1000); + } + } else { + func_(); + } + } + +private: + VoidFunc func_; + BoolFunc repFunc_; + int intervalMs_; +}; +} +} +} // apache::thrift::concurrency + +#endif // #ifndef _THRIFT_CONCURRENCY_FUNCTION_RUNNER_H diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/concurrency/Monitor.cpp b/src/jaegertracing/thrift/lib/cpp/src/thrift/concurrency/Monitor.cpp new file mode 100644 index 000000000..dc92efd6c --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/concurrency/Monitor.cpp @@ -0,0 +1,185 @@ +/* + * 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. + */ + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace apache { +namespace thrift { +namespace concurrency { + +/** + * Monitor implementation using the std thread library + * + * @version $Id:$ + */ +class Monitor::Impl { + +public: + Impl() : ownedMutex_(new Mutex()), conditionVariable_(), mutex_(nullptr) { init(ownedMutex_.get()); } + + Impl(Mutex* mutex) : ownedMutex_(), conditionVariable_(), mutex_(nullptr) { init(mutex); } + + Impl(Monitor* monitor) : ownedMutex_(), conditionVariable_(), mutex_(nullptr) { + init(&(monitor->mutex())); + } + + Mutex& mutex() { return *mutex_; } + void lock() { mutex_->lock(); } + void unlock() { mutex_->unlock(); } + + /** + * Exception-throwing version of waitForTimeRelative(), called simply + * wait(int64) for historical reasons. Timeout is in milliseconds. + * + * If the condition occurs, this function returns cleanly; on timeout or + * error an exception is thrown. + */ + void wait(const std::chrono::milliseconds &timeout) { + int result = waitForTimeRelative(timeout); + if (result == THRIFT_ETIMEDOUT) { + throw TimedOutException(); + } else if (result != 0) { + throw TException("Monitor::wait() failed"); + } + } + + /** + * Waits until the specified timeout in milliseconds for the condition to + * occur, or waits forever if timeout is zero. + * + * Returns 0 if condition occurs, THRIFT_ETIMEDOUT on timeout, or an error code. + */ + int waitForTimeRelative(const std::chrono::milliseconds &timeout) { + if (timeout.count() == 0) { + return waitForever(); + } + + assert(mutex_); + auto* mutexImpl = static_cast(mutex_->getUnderlyingImpl()); + assert(mutexImpl); + + std::unique_lock lock(*mutexImpl, std::adopt_lock); + bool timedout = (conditionVariable_.wait_for(lock, timeout) + == std::cv_status::timeout); + lock.release(); + return (timedout ? THRIFT_ETIMEDOUT : 0); + } + + /** + * Waits until the absolute time specified by abstime. + * Returns 0 if condition occurs, THRIFT_ETIMEDOUT on timeout, or an error code. + */ + int waitForTime(const std::chrono::time_point& abstime) { + assert(mutex_); + auto* mutexImpl = static_cast(mutex_->getUnderlyingImpl()); + assert(mutexImpl); + + std::unique_lock lock(*mutexImpl, std::adopt_lock); + bool timedout = (conditionVariable_.wait_until(lock, abstime) + == std::cv_status::timeout); + lock.release(); + return (timedout ? THRIFT_ETIMEDOUT : 0); + } + + /** + * Waits forever until the condition occurs. + * Returns 0 if condition occurs, or an error code otherwise. + */ + int waitForever() { + assert(mutex_); + auto* mutexImpl = static_cast(mutex_->getUnderlyingImpl()); + assert(mutexImpl); + + std::unique_lock lock(*mutexImpl, std::adopt_lock); + conditionVariable_.wait(lock); + lock.release(); + return 0; + } + + void notify() { conditionVariable_.notify_one(); } + + void notifyAll() { conditionVariable_.notify_all(); } + +private: + void init(Mutex* mutex) { mutex_ = mutex; } + + const std::unique_ptr ownedMutex_; + std::condition_variable_any conditionVariable_; + Mutex* mutex_; +}; + +Monitor::Monitor() : impl_(new Monitor::Impl()) { +} +Monitor::Monitor(Mutex* mutex) : impl_(new Monitor::Impl(mutex)) { +} +Monitor::Monitor(Monitor* monitor) : impl_(new Monitor::Impl(monitor)) { +} + +Monitor::~Monitor() { + delete impl_; +} + +Mutex& Monitor::mutex() const { + return const_cast(impl_)->mutex(); +} + +void Monitor::lock() const { + const_cast(impl_)->lock(); +} + +void Monitor::unlock() const { + const_cast(impl_)->unlock(); +} + +void Monitor::wait(const std::chrono::milliseconds &timeout) const { + const_cast(impl_)->wait(timeout); +} + +int Monitor::waitForTime(const std::chrono::time_point& abstime) const { + return const_cast(impl_)->waitForTime(abstime); +} + +int Monitor::waitForTimeRelative(const std::chrono::milliseconds &timeout) const { + return const_cast(impl_)->waitForTimeRelative(timeout); +} + +int Monitor::waitForever() const { + return const_cast(impl_)->waitForever(); +} + +void Monitor::notify() const { + const_cast(impl_)->notify(); +} + +void Monitor::notifyAll() const { + const_cast(impl_)->notifyAll(); +} +} +} +} // apache::thrift::concurrency diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/concurrency/Monitor.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/concurrency/Monitor.h new file mode 100644 index 000000000..b3939cb01 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/concurrency/Monitor.h @@ -0,0 +1,126 @@ +/* + * 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. + */ + +#ifndef _THRIFT_CONCURRENCY_MONITOR_H_ +#define _THRIFT_CONCURRENCY_MONITOR_H_ 1 + +#include +#include +#include + +namespace apache { +namespace thrift { +namespace concurrency { + +/** + * A monitor is a combination mutex and condition-event. Waiting and + * notifying condition events requires that the caller own the mutex. Mutex + * lock and unlock operations can be performed independently of condition + * events. This is more or less analogous to java.lang.Object multi-thread + * operations. + * + * Note the Monitor can create a new, internal mutex; alternatively, a + * separate Mutex can be passed in and the Monitor will re-use it without + * taking ownership. It's the user's responsibility to make sure that the + * Mutex is not deallocated before the Monitor. + * + * Note that all methods are const. Monitors implement logical constness, not + * bit constness. This allows const methods to call monitor methods without + * needing to cast away constness or change to non-const signatures. + * + * @version $Id:$ + */ +class Monitor : boost::noncopyable { +public: + /** Creates a new mutex, and takes ownership of it. */ + Monitor(); + + /** Uses the provided mutex without taking ownership. */ + explicit Monitor(Mutex* mutex); + + /** Uses the mutex inside the provided Monitor without taking ownership. */ + explicit Monitor(Monitor* monitor); + + /** Deallocates the mutex only if we own it. */ + virtual ~Monitor(); + + Mutex& mutex() const; + + virtual void lock() const; + + virtual void unlock() const; + + /** + * Waits a maximum of the specified timeout in milliseconds for the condition + * to occur, or waits forever if timeout is zero. + * + * Returns 0 if condition occurs, THRIFT_ETIMEDOUT on timeout, or an error code. + */ + int waitForTimeRelative(const std::chrono::milliseconds &timeout) const; + + int waitForTimeRelative(uint64_t timeout_ms) const { return waitForTimeRelative(std::chrono::milliseconds(timeout_ms)); } + + /** + * Waits until the absolute time specified by abstime. + * Returns 0 if condition occurs, THRIFT_ETIMEDOUT on timeout, or an error code. + */ + int waitForTime(const std::chrono::time_point& abstime) const; + + /** + * Waits forever until the condition occurs. + * Returns 0 if condition occurs, or an error code otherwise. + */ + int waitForever() const; + + /** + * Exception-throwing version of waitForTimeRelative(), called simply + * wait(std::chrono::milliseconds) for historical reasons. Timeout is in milliseconds. + * + * If the condition occurs, this function returns cleanly; on timeout or + * error an exception is thrown. + */ + void wait(const std::chrono::milliseconds &timeout) const; + + void wait(uint64_t timeout_ms = 0ULL) const { this->wait(std::chrono::milliseconds(timeout_ms)); } + + /** Wakes up one thread waiting on this monitor. */ + virtual void notify() const; + + /** Wakes up all waiting threads on this monitor. */ + virtual void notifyAll() const; + +private: + class Impl; + + Impl* impl_; +}; + +class Synchronized { +public: + Synchronized(const Monitor* monitor) : g(monitor->mutex()) {} + Synchronized(const Monitor& monitor) : g(monitor.mutex()) {} + +private: + Guard g; +}; +} +} +} // apache::thrift::concurrency + +#endif // #ifndef _THRIFT_CONCURRENCY_MONITOR_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/concurrency/Mutex.cpp b/src/jaegertracing/thrift/lib/cpp/src/thrift/concurrency/Mutex.cpp new file mode 100644 index 000000000..75802835d --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/concurrency/Mutex.cpp @@ -0,0 +1,63 @@ +/* + * 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. + */ + +#include + +#include +#include + +namespace apache { +namespace thrift { +namespace concurrency { + +/** + * Implementation of Mutex class using C++11 std::timed_mutex + * + * Methods throw std::system_error on error. + * + * @version $Id:$ + */ +class Mutex::impl : public std::timed_mutex {}; + +Mutex::Mutex() : impl_(new Mutex::impl()) { +} + +void* Mutex::getUnderlyingImpl() const { + return impl_.get(); +} + +void Mutex::lock() const { + impl_->lock(); +} + +bool Mutex::trylock() const { + return impl_->try_lock(); +} + +bool Mutex::timedlock(int64_t ms) const { + return impl_->try_lock_for(std::chrono::milliseconds(ms)); +} + +void Mutex::unlock() const { + impl_->unlock(); +} + +} +} +} // apache::thrift::concurrency diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/concurrency/Mutex.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/concurrency/Mutex.h new file mode 100644 index 000000000..27e386ed4 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/concurrency/Mutex.h @@ -0,0 +1,89 @@ +/* + * 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. + */ + +#ifndef _THRIFT_CONCURRENCY_MUTEX_H_ +#define _THRIFT_CONCURRENCY_MUTEX_H_ 1 + +#include +#include + +namespace apache { +namespace thrift { +namespace concurrency { + +/** + * NOTE: All mutex implementations throw an exception on failure. See each + * specific implementation to understand the exception type(s) used. + */ + +/** + * A simple mutex class + * + * @version $Id:$ + */ +class Mutex { +public: + Mutex(); + virtual ~Mutex() = default; + + virtual void lock() const; + virtual bool trylock() const; + virtual bool timedlock(int64_t milliseconds) const; + virtual void unlock() const; + + void* getUnderlyingImpl() const; + +private: + class impl; + std::shared_ptr impl_; +}; + + +class Guard : boost::noncopyable { +public: + Guard(const Mutex& value, int64_t timeout = 0) : mutex_(&value) { + if (timeout == 0) { + value.lock(); + } else if (timeout < 0) { + if (!value.trylock()) { + mutex_ = nullptr; + } + } else { + if (!value.timedlock(timeout)) { + mutex_ = nullptr; + } + } + } + ~Guard() { + if (mutex_) { + mutex_->unlock(); + } + } + + operator bool() const { return (mutex_ != nullptr); } + +private: + const Mutex* mutex_; +}; + +} +} +} // apache::thrift::concurrency + +#endif // #ifndef _THRIFT_CONCURRENCY_MUTEX_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/concurrency/Thread.cpp b/src/jaegertracing/thrift/lib/cpp/src/thrift/concurrency/Thread.cpp new file mode 100644 index 000000000..a2bb1270f --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/concurrency/Thread.cpp @@ -0,0 +1,37 @@ +/* + * 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. + */ + +#include + +namespace apache { +namespace thrift { +namespace concurrency { + +void Thread::threadMain(std::shared_ptr thread) { + thread->setState(started); + thread->runnable()->run(); + + if (thread->getState() != stopping && thread->getState() != stopped) { + thread->setState(stopping); + } +} + +} +} +} // apache::thrift::concurrency diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/concurrency/Thread.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/concurrency/Thread.h new file mode 100644 index 000000000..e803a82ce --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/concurrency/Thread.h @@ -0,0 +1,174 @@ +/* + * 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. + */ + +#ifndef _THRIFT_CONCURRENCY_THREAD_H_ +#define _THRIFT_CONCURRENCY_THREAD_H_ 1 + +#include +#include + +#include + +namespace apache { +namespace thrift { +namespace concurrency { + +class Thread; + +/** + * Minimal runnable class. More or less analogous to java.lang.Runnable. + * + * @version $Id:$ + */ +class Runnable { + +public: + virtual ~Runnable() = default; + virtual void run() = 0; + + /** + * Gets the thread object that is hosting this runnable object - can return + * an empty boost::shared pointer if no references remain on that thread object + */ + virtual std::shared_ptr thread() { return thread_.lock(); } + + /** + * Sets the thread that is executing this object. This is only meant for + * use by concrete implementations of Thread. + */ + virtual void thread(std::shared_ptr value) { thread_ = value; } + +private: + std::weak_ptr thread_; +}; + +/** + * Minimal thread class. Returned by thread factory bound to a Runnable object + * and ready to start execution. More or less analogous to java.lang.Thread + * (minus all the thread group, priority, mode and other baggage, since that + * is difficult to abstract across platforms and is left for platform-specific + * ThreadFactory implemtations to deal with + * + * @see apache::thrift::concurrency::ThreadFactory) + */ +class Thread final : public std::enable_shared_from_this { + +public: + typedef std::thread::id id_t; + + enum STATE { uninitialized, starting, started, stopping, stopped }; + + static void threadMain(std::shared_ptr thread); + + static inline bool is_current(id_t t) { return t == std::this_thread::get_id(); } + static inline id_t get_current() { return std::this_thread::get_id(); } + + Thread(bool detached, std::shared_ptr runnable) + : state_(uninitialized), detached_(detached) { + this->_runnable = runnable; + } + + ~Thread() { + if (!detached_ && thread_->joinable()) { + try { + join(); + } catch (...) { + // We're really hosed. + } + } + } + + STATE getState() const + { + Synchronized sync(monitor_); + return state_; + } + + void setState(STATE newState) + { + Synchronized sync(monitor_); + state_ = newState; + + // unblock start() with the knowledge that the thread has actually + // started running, which avoids a race in detached threads. + if (newState == started) { + monitor_.notify(); + } + } + + /** + * Starts the thread. Does platform specific thread creation and + * configuration then invokes the run method of the Runnable object bound + * to this thread. + */ + void start() { + if (getState() != uninitialized) { + return; + } + + std::shared_ptr selfRef = shared_from_this(); + setState(starting); + + Synchronized sync(monitor_); + thread_ = std::unique_ptr(new std::thread(threadMain, selfRef)); + + if (detached_) + thread_->detach(); + + // Wait for the thread to start and get far enough to grab everything + // that it needs from the calling context, thus absolving the caller + // from being required to hold on to runnable indefinitely. + monitor_.wait(); + } + + /** + * Join this thread. If this thread is joinable, the calling thread blocks + * until this thread completes. If the target thread is not joinable, then + * nothing happens. + */ + void join() { + if (!detached_ && state_ != uninitialized) { + thread_->join(); + } + } + + /** + * Gets the thread's platform-specific ID + */ + Thread::id_t getId() const { return thread_.get() ? thread_->get_id() : std::thread::id(); } + + /** + * Gets the runnable object this thread is hosting + */ + std::shared_ptr runnable() const { return _runnable; } + +private: + std::shared_ptr _runnable; + std::unique_ptr thread_; + Monitor monitor_; + STATE state_; + bool detached_; +}; + + +} +} +} // apache::thrift::concurrency + +#endif // #ifndef _THRIFT_CONCURRENCY_THREAD_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/concurrency/ThreadFactory.cpp b/src/jaegertracing/thrift/lib/cpp/src/thrift/concurrency/ThreadFactory.cpp new file mode 100644 index 000000000..becb3b244 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/concurrency/ThreadFactory.cpp @@ -0,0 +1,40 @@ +/* + * 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. + */ + +#include + +#include +#include + +namespace apache { +namespace thrift { +namespace concurrency { + +std::shared_ptr ThreadFactory::newThread(std::shared_ptr runnable) const { + std::shared_ptr result = std::make_shared(isDetached(), runnable); + runnable->thread(result); + return result; +} + +Thread::id_t ThreadFactory::getCurrentThreadId() const { + return std::this_thread::get_id(); +} +} +} +} // apache::thrift::concurrency diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/concurrency/ThreadFactory.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/concurrency/ThreadFactory.h new file mode 100644 index 000000000..a1547a6e0 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/concurrency/ThreadFactory.h @@ -0,0 +1,76 @@ +/* + * 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. + */ + +#ifndef _THRIFT_CONCURRENCY_THREADFACTORY_H_ +#define _THRIFT_CONCURRENCY_THREADFACTORY_H_ 1 + +#include + +#include +namespace apache { +namespace thrift { +namespace concurrency { + +/** + * Factory to create thread object and bind them to Runnable + * object for execution + */ +class ThreadFactory final { +public: + /** + * All threads created by a factory are reference-counted + * via std::shared_ptr. The factory guarantees that threads and the Runnable tasks + * they host will be properly cleaned up once the last strong reference + * to both is given up. + * + * By default threads are not joinable. + */ + ThreadFactory(bool detached = true) : detached_(detached) { } + + ~ThreadFactory() = default; + + /** + * Gets current detached mode + */ + bool isDetached() const { return detached_; } + + /** + * Sets the detached disposition of newly created threads. + */ + void setDetached(bool detached) { detached_ = detached; } + + /** + * Create a new thread. + */ + std::shared_ptr newThread(std::shared_ptr runnable) const; + + /** + * Gets the current thread id or unknown_thread_id if the current thread is not a thrift thread + */ + Thread::id_t getCurrentThreadId() const; + +private: + bool detached_; +}; + +} +} +} // apache::thrift::concurrency + +#endif // #ifndef _THRIFT_CONCURRENCY_THREADFACTORY_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/concurrency/ThreadManager.cpp b/src/jaegertracing/thrift/lib/cpp/src/thrift/concurrency/ThreadManager.cpp new file mode 100644 index 000000000..25b838aef --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/concurrency/ThreadManager.cpp @@ -0,0 +1,583 @@ +/* + * 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. + */ + +#include + +#include +#include +#include + +#include + +#include +#include +#include + +namespace apache { +namespace thrift { +namespace concurrency { + +using std::shared_ptr; +using std::unique_ptr; +using std::dynamic_pointer_cast; + +/** + * ThreadManager class + * + * This class manages a pool of threads. It uses a ThreadFactory to create + * threads. It never actually creates or destroys worker threads, rather + * it maintains statistics on number of idle threads, number of active threads, + * task backlog, and average wait and service times. + * + * There are three different monitors used for signaling different conditions + * however they all share the same mutex_. + * + * @version $Id:$ + */ +class ThreadManager::Impl : public ThreadManager { + +public: + Impl() + : workerCount_(0), + workerMaxCount_(0), + idleCount_(0), + pendingTaskCountMax_(0), + expiredCount_(0), + state_(ThreadManager::UNINITIALIZED), + monitor_(&mutex_), + maxMonitor_(&mutex_), + workerMonitor_(&mutex_) {} + + ~Impl() override { stop(); } + + void start() override; + void stop() override; + + ThreadManager::STATE state() const override { return state_; } + + shared_ptr threadFactory() const override { + Guard g(mutex_); + return threadFactory_; + } + + void threadFactory(shared_ptr value) override { + Guard g(mutex_); + if (threadFactory_ && threadFactory_->isDetached() != value->isDetached()) { + throw InvalidArgumentException(); + } + threadFactory_ = value; + } + + void addWorker(size_t value) override; + + void removeWorker(size_t value) override; + + size_t idleWorkerCount() const override { return idleCount_; } + + size_t workerCount() const override { + Guard g(mutex_); + return workerCount_; + } + + size_t pendingTaskCount() const override { + Guard g(mutex_); + return tasks_.size(); + } + + size_t totalTaskCount() const override { + Guard g(mutex_); + return tasks_.size() + workerCount_ - idleCount_; + } + + size_t pendingTaskCountMax() const override { + Guard g(mutex_); + return pendingTaskCountMax_; + } + + size_t expiredTaskCount() const override { + Guard g(mutex_); + return expiredCount_; + } + + void pendingTaskCountMax(const size_t value) { + Guard g(mutex_); + pendingTaskCountMax_ = value; + } + + void add(shared_ptr value, int64_t timeout, int64_t expiration) override; + + void remove(shared_ptr task) override; + + shared_ptr removeNextPending() override; + + void removeExpiredTasks() override { + removeExpired(false); + } + + void setExpireCallback(ExpireCallback expireCallback) override; + +private: + /** + * Remove one or more expired tasks. + * \param[in] justOne if true, try to remove just one task and return + */ + void removeExpired(bool justOne); + + /** + * \returns whether it is acceptable to block, depending on the current thread id + */ + bool canSleep() const; + + /** + * Lowers the maximum worker count and blocks until enough worker threads complete + * to get to the new maximum worker limit. The caller is responsible for acquiring + * a lock on the class mutex_. + */ + void removeWorkersUnderLock(size_t value); + + size_t workerCount_; + size_t workerMaxCount_; + size_t idleCount_; + size_t pendingTaskCountMax_; + size_t expiredCount_; + ExpireCallback expireCallback_; + + ThreadManager::STATE state_; + shared_ptr threadFactory_; + + friend class ThreadManager::Task; + typedef std::deque > TaskQueue; + TaskQueue tasks_; + Mutex mutex_; + Monitor monitor_; + Monitor maxMonitor_; + Monitor workerMonitor_; // used to synchronize changes in worker count + + friend class ThreadManager::Worker; + std::set > workers_; + std::set > deadWorkers_; + std::map > idMap_; +}; + +class ThreadManager::Task : public Runnable { + +public: + enum STATE { WAITING, EXECUTING, TIMEDOUT, COMPLETE }; + + Task(shared_ptr runnable, uint64_t expiration = 0ULL) + : runnable_(runnable), + state_(WAITING) { + if (expiration != 0ULL) { + expireTime_.reset(new std::chrono::steady_clock::time_point(std::chrono::steady_clock::now() + std::chrono::milliseconds(expiration))); + } + } + + ~Task() override = default; + + void run() override { + if (state_ == EXECUTING) { + runnable_->run(); + state_ = COMPLETE; + } + } + + shared_ptr getRunnable() { return runnable_; } + + const unique_ptr & getExpireTime() const { return expireTime_; } + +private: + shared_ptr runnable_; + friend class ThreadManager::Worker; + STATE state_; + unique_ptr expireTime_; +}; + +class ThreadManager::Worker : public Runnable { + enum STATE { UNINITIALIZED, STARTING, STARTED, STOPPING, STOPPED }; + +public: + Worker(ThreadManager::Impl* manager) : manager_(manager), state_(UNINITIALIZED) {} + + ~Worker() override = default; + +private: + bool isActive() const { + return (manager_->workerCount_ <= manager_->workerMaxCount_) + || (manager_->state_ == JOINING && !manager_->tasks_.empty()); + } + +public: + /** + * Worker entry point + * + * As long as worker thread is running, pull tasks off the task queue and + * execute. + */ + void run() override { + Guard g(manager_->mutex_); + + /** + * This method has three parts; one is to check for and account for + * admitting a task which happens under a lock. Then the lock is released + * and the task itself is executed. Finally we do some accounting + * under lock again when the task completes. + */ + + /** + * Admitting + */ + + /** + * Increment worker semaphore and notify manager if worker count reached + * desired max + */ + bool active = manager_->workerCount_ < manager_->workerMaxCount_; + if (active) { + if (++manager_->workerCount_ == manager_->workerMaxCount_) { + manager_->workerMonitor_.notify(); + } + } + + while (active) { + /** + * While holding manager monitor block for non-empty task queue (Also + * check that the thread hasn't been requested to stop). Once the queue + * is non-empty, dequeue a task, release monitor, and execute. If the + * worker max count has been decremented such that we exceed it, mark + * ourself inactive, decrement the worker count and notify the manager + * (technically we're notifying the next blocked thread but eventually + * the manager will see it. + */ + active = isActive(); + + while (active && manager_->tasks_.empty()) { + manager_->idleCount_++; + manager_->monitor_.wait(); + active = isActive(); + manager_->idleCount_--; + } + + shared_ptr task; + + if (active) { + if (!manager_->tasks_.empty()) { + task = manager_->tasks_.front(); + manager_->tasks_.pop_front(); + if (task->state_ == ThreadManager::Task::WAITING) { + // If the state is changed to anything other than EXECUTING or TIMEDOUT here + // then the execution loop needs to be changed below. + task->state_ = + (task->getExpireTime() && *(task->getExpireTime()) < std::chrono::steady_clock::now()) ? + ThreadManager::Task::TIMEDOUT : + ThreadManager::Task::EXECUTING; + } + } + + /* If we have a pending task max and we just dropped below it, wakeup any + thread that might be blocked on add. */ + if (manager_->pendingTaskCountMax_ != 0 + && manager_->tasks_.size() <= manager_->pendingTaskCountMax_ - 1) { + manager_->maxMonitor_.notify(); + } + } + + /** + * Execution - not holding a lock + */ + if (task) { + if (task->state_ == ThreadManager::Task::EXECUTING) { + + // Release the lock so we can run the task without blocking the thread manager + manager_->mutex_.unlock(); + + try { + task->run(); + } catch (const std::exception& e) { + GlobalOutput.printf("[ERROR] task->run() raised an exception: %s", e.what()); + } catch (...) { + GlobalOutput.printf("[ERROR] task->run() raised an unknown exception"); + } + + // Re-acquire the lock to proceed in the thread manager + manager_->mutex_.lock(); + + } else if (manager_->expireCallback_) { + // The only other state the task could have been in is TIMEDOUT (see above) + manager_->expireCallback_(task->getRunnable()); + manager_->expiredCount_++; + } + } + } + + /** + * Final accounting for the worker thread that is done working + */ + manager_->deadWorkers_.insert(this->thread()); + if (--manager_->workerCount_ == manager_->workerMaxCount_) { + manager_->workerMonitor_.notify(); + } + } + +private: + ThreadManager::Impl* manager_; + friend class ThreadManager::Impl; + STATE state_; +}; + +void ThreadManager::Impl::addWorker(size_t value) { + std::set > newThreads; + for (size_t ix = 0; ix < value; ix++) { + shared_ptr worker + = std::make_shared(this); + newThreads.insert(threadFactory_->newThread(worker)); + } + + Guard g(mutex_); + workerMaxCount_ += value; + workers_.insert(newThreads.begin(), newThreads.end()); + + for (const auto & newThread : newThreads) { + shared_ptr worker + = dynamic_pointer_cast(newThread->runnable()); + worker->state_ = ThreadManager::Worker::STARTING; + newThread->start(); + idMap_.insert(std::pair >(newThread->getId(), newThread)); + } + + while (workerCount_ != workerMaxCount_) { + workerMonitor_.wait(); + } +} + +void ThreadManager::Impl::start() { + Guard g(mutex_); + if (state_ == ThreadManager::STOPPED) { + return; + } + + if (state_ == ThreadManager::UNINITIALIZED) { + if (!threadFactory_) { + throw InvalidArgumentException(); + } + state_ = ThreadManager::STARTED; + monitor_.notifyAll(); + } + + while (state_ == STARTING) { + monitor_.wait(); + } +} + +void ThreadManager::Impl::stop() { + Guard g(mutex_); + bool doStop = false; + + if (state_ != ThreadManager::STOPPING && state_ != ThreadManager::JOINING + && state_ != ThreadManager::STOPPED) { + doStop = true; + state_ = ThreadManager::JOINING; + } + + if (doStop) { + removeWorkersUnderLock(workerCount_); + } + + state_ = ThreadManager::STOPPED; +} + +void ThreadManager::Impl::removeWorker(size_t value) { + Guard g(mutex_); + removeWorkersUnderLock(value); +} + +void ThreadManager::Impl::removeWorkersUnderLock(size_t value) { + if (value > workerMaxCount_) { + throw InvalidArgumentException(); + } + + workerMaxCount_ -= value; + + if (idleCount_ > value) { + // There are more idle workers than we need to remove, + // so notify enough of them so they can terminate. + for (size_t ix = 0; ix < value; ix++) { + monitor_.notify(); + } + } else { + // There are as many or less idle workers than we need to remove, + // so just notify them all so they can terminate. + monitor_.notifyAll(); + } + + while (workerCount_ != workerMaxCount_) { + workerMonitor_.wait(); + } + + for (const auto & deadWorker : deadWorkers_) { + + // when used with a joinable thread factory, we join the threads as we remove them + if (!threadFactory_->isDetached()) { + deadWorker->join(); + } + + idMap_.erase(deadWorker->getId()); + workers_.erase(deadWorker); + } + + deadWorkers_.clear(); +} + +bool ThreadManager::Impl::canSleep() const { + const Thread::id_t id = threadFactory_->getCurrentThreadId(); + return idMap_.find(id) == idMap_.end(); +} + +void ThreadManager::Impl::add(shared_ptr value, int64_t timeout, int64_t expiration) { + Guard g(mutex_, timeout); + + if (!g) { + throw TimedOutException(); + } + + if (state_ != ThreadManager::STARTED) { + throw IllegalStateException( + "ThreadManager::Impl::add ThreadManager " + "not started"); + } + + // if we're at a limit, remove an expired task to see if the limit clears + if (pendingTaskCountMax_ > 0 && (tasks_.size() >= pendingTaskCountMax_)) { + removeExpired(true); + } + + if (pendingTaskCountMax_ > 0 && (tasks_.size() >= pendingTaskCountMax_)) { + if (canSleep() && timeout >= 0) { + while (pendingTaskCountMax_ > 0 && tasks_.size() >= pendingTaskCountMax_) { + // This is thread safe because the mutex is shared between monitors. + maxMonitor_.wait(timeout); + } + } else { + throw TooManyPendingTasksException(); + } + } + + tasks_.push_back(std::make_shared(value, expiration)); + + // If idle thread is available notify it, otherwise all worker threads are + // running and will get around to this task in time. + if (idleCount_ > 0) { + monitor_.notify(); + } +} + +void ThreadManager::Impl::remove(shared_ptr task) { + Guard g(mutex_); + if (state_ != ThreadManager::STARTED) { + throw IllegalStateException( + "ThreadManager::Impl::remove ThreadManager not " + "started"); + } + + for (auto it = tasks_.begin(); it != tasks_.end(); ++it) + { + if ((*it)->getRunnable() == task) + { + tasks_.erase(it); + return; + } + } +} + +std::shared_ptr ThreadManager::Impl::removeNextPending() { + Guard g(mutex_); + if (state_ != ThreadManager::STARTED) { + throw IllegalStateException( + "ThreadManager::Impl::removeNextPending " + "ThreadManager not started"); + } + + if (tasks_.empty()) { + return std::shared_ptr(); + } + + shared_ptr task = tasks_.front(); + tasks_.pop_front(); + + return task->getRunnable(); +} + +void ThreadManager::Impl::removeExpired(bool justOne) { + // this is always called under a lock + if (tasks_.empty()) { + return; + } + auto now = std::chrono::steady_clock::now(); + + for (auto it = tasks_.begin(); it != tasks_.end(); ) + { + if ((*it)->getExpireTime() && *((*it)->getExpireTime()) < now) { + if (expireCallback_) { + expireCallback_((*it)->getRunnable()); + } + it = tasks_.erase(it); + ++expiredCount_; + if (justOne) { + return; + } + } + else + { + ++it; + } + } +} + +void ThreadManager::Impl::setExpireCallback(ExpireCallback expireCallback) { + Guard g(mutex_); + expireCallback_ = expireCallback; +} + +class SimpleThreadManager : public ThreadManager::Impl { + +public: + SimpleThreadManager(size_t workerCount = 4, size_t pendingTaskCountMax = 0) + : workerCount_(workerCount), pendingTaskCountMax_(pendingTaskCountMax) {} + + void start() override { + ThreadManager::Impl::pendingTaskCountMax(pendingTaskCountMax_); + ThreadManager::Impl::start(); + addWorker(workerCount_); + } + +private: + const size_t workerCount_; + const size_t pendingTaskCountMax_; +}; + +shared_ptr ThreadManager::newThreadManager() { + return shared_ptr(new ThreadManager::Impl()); +} + +shared_ptr ThreadManager::newSimpleThreadManager(size_t count, + size_t pendingTaskCountMax) { + return shared_ptr(new SimpleThreadManager(count, pendingTaskCountMax)); +} +} +} +} // apache::thrift::concurrency diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/concurrency/ThreadManager.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/concurrency/ThreadManager.h new file mode 100644 index 000000000..7b202ca6a --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/concurrency/ThreadManager.h @@ -0,0 +1,213 @@ +/* + * 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. + */ + +#ifndef _THRIFT_CONCURRENCY_THREADMANAGER_H_ +#define _THRIFT_CONCURRENCY_THREADMANAGER_H_ 1 + +#include +#include +#include + +namespace apache { +namespace thrift { +namespace concurrency { + +/** + * Thread Pool Manager and related classes + * + * @version $Id:$ + */ +class ThreadManager; + +/** + * ThreadManager class + * + * This class manages a pool of threads. It uses a ThreadFactory to create + * threads. It never actually creates or destroys worker threads, rather + * it maintains statistics on number of idle threads, number of active threads, + * task backlog, and average wait and service times and informs the PoolPolicy + * object bound to instances of this manager of interesting transitions. It is + * then up the PoolPolicy object to decide if the thread pool size needs to be + * adjusted and call this object addWorker and removeWorker methods to make + * changes. + * + * This design allows different policy implementations to use this code to + * handle basic worker thread management and worker task execution and focus on + * policy issues. The simplest policy, StaticPolicy, does nothing other than + * create a fixed number of threads. + */ +class ThreadManager { + +protected: + ThreadManager() = default; + +public: + typedef std::function)> ExpireCallback; + + virtual ~ThreadManager() = default; + + /** + * Starts the thread manager. Verifies all attributes have been properly + * initialized, then allocates necessary resources to begin operation + */ + virtual void start() = 0; + + /** + * Stops the thread manager. Aborts all remaining unprocessed task, shuts + * down all created worker threads, and releases all allocated resources. + * This method blocks for all worker threads to complete, thus it can + * potentially block forever if a worker thread is running a task that + * won't terminate. + * + * Worker threads will be joined depending on the threadFactory's detached + * disposition. + */ + virtual void stop() = 0; + + enum STATE { UNINITIALIZED, STARTING, STARTED, JOINING, STOPPING, STOPPED }; + + virtual STATE state() const = 0; + + /** + * \returns the current thread factory + */ + virtual std::shared_ptr threadFactory() const = 0; + + /** + * Set the thread factory. + * \throws InvalidArgumentException if the new thread factory has a different + * detached disposition than the one replacing it + */ + virtual void threadFactory(std::shared_ptr value) = 0; + + /** + * Adds worker thread(s). + */ + virtual void addWorker(size_t value = 1) = 0; + + /** + * Removes worker thread(s). + * Threads are joined if the thread factory detached disposition allows it. + * Blocks until the number of worker threads reaches the new limit. + * \param[in] value the number to remove + * \throws InvalidArgumentException if the value is greater than the number + * of workers + */ + virtual void removeWorker(size_t value = 1) = 0; + + /** + * Gets the current number of idle worker threads + */ + virtual size_t idleWorkerCount() const = 0; + + /** + * Gets the current number of total worker threads + */ + virtual size_t workerCount() const = 0; + + /** + * Gets the current number of pending tasks + */ + virtual size_t pendingTaskCount() const = 0; + + /** + * Gets the current number of pending and executing tasks + */ + virtual size_t totalTaskCount() const = 0; + + /** + * Gets the maximum pending task count. 0 indicates no maximum + */ + virtual size_t pendingTaskCountMax() const = 0; + + /** + * Gets the number of tasks which have been expired without being run + * since start() was called. + */ + virtual size_t expiredTaskCount() const = 0; + + /** + * Adds a task to be executed at some time in the future by a worker thread. + * + * This method will block if pendingTaskCountMax() in not zero and pendingTaskCount() + * is greater than or equalt to pendingTaskCountMax(). If this method is called in the + * context of a ThreadManager worker thread it will throw a + * TooManyPendingTasksException + * + * @param task The task to queue for execution + * + * @param timeout Time to wait in milliseconds to add a task when a pending-task-count + * is specified. Specific cases: + * timeout = 0 : Wait forever to queue task. + * timeout = -1 : Return immediately if pending task count exceeds specified max + * @param expiration when nonzero, the number of milliseconds the task is valid + * to be run; if exceeded, the task will be dropped off the queue and not run. + * + * @throws TooManyPendingTasksException Pending task count exceeds max pending task count + */ + virtual void add(std::shared_ptr task, + int64_t timeout = 0LL, + int64_t expiration = 0LL) = 0; + + /** + * Removes a pending task + */ + virtual void remove(std::shared_ptr task) = 0; + + /** + * Remove the next pending task which would be run. + * + * @return the task removed. + */ + virtual std::shared_ptr removeNextPending() = 0; + + /** + * Remove tasks from front of task queue that have expired. + */ + virtual void removeExpiredTasks() = 0; + + /** + * Set a callback to be called when a task is expired and not run. + * + * @param expireCallback a function called with the shared_ptr for + * the expired task. + */ + virtual void setExpireCallback(ExpireCallback expireCallback) = 0; + + static std::shared_ptr newThreadManager(); + + /** + * Creates a simple thread manager the uses count number of worker threads and has + * a pendingTaskCountMax maximum pending tasks. The default, 0, specified no limit + * on pending tasks + */ + static std::shared_ptr newSimpleThreadManager(size_t count = 4, + size_t pendingTaskCountMax = 0); + + class Task; + + class Worker; + + class Impl; +}; +} +} +} // apache::thrift::concurrency + +#endif // #ifndef _THRIFT_CONCURRENCY_THREADMANAGER_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/concurrency/TimerManager.cpp b/src/jaegertracing/thrift/lib/cpp/src/thrift/concurrency/TimerManager.cpp new file mode 100644 index 000000000..703c19ed1 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/concurrency/TimerManager.cpp @@ -0,0 +1,321 @@ +/* + * 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. + */ + +#include +#include + +#include +#include +#include +#include + +namespace apache { +namespace thrift { +namespace concurrency { + +using std::shared_ptr; +using std::weak_ptr; + +/** + * TimerManager class + * + * @version $Id:$ + */ +class TimerManager::Task : public Runnable { + +public: + enum STATE { WAITING, EXECUTING, CANCELLED, COMPLETE }; + + Task(shared_ptr runnable) : runnable_(runnable), state_(WAITING) {} + + ~Task() override = default; + + void run() override { + if (state_ == EXECUTING) { + runnable_->run(); + state_ = COMPLETE; + } + } + + bool operator==(const shared_ptr & runnable) const { return runnable_ == runnable; } + + task_iterator it_; + +private: + shared_ptr runnable_; + friend class TimerManager::Dispatcher; + STATE state_; +}; + +class TimerManager::Dispatcher : public Runnable { + +public: + Dispatcher(TimerManager* manager) : manager_(manager) {} + + ~Dispatcher() override = default; + + /** + * Dispatcher entry point + * + * As long as dispatcher thread is running, pull tasks off the task taskMap_ + * and execute. + */ + void run() override { + { + Synchronized s(manager_->monitor_); + if (manager_->state_ == TimerManager::STARTING) { + manager_->state_ = TimerManager::STARTED; + manager_->monitor_.notifyAll(); + } + } + + do { + std::set > expiredTasks; + { + Synchronized s(manager_->monitor_); + task_iterator expiredTaskEnd; + auto now = std::chrono::steady_clock::now(); + while (manager_->state_ == TimerManager::STARTED + && (expiredTaskEnd = manager_->taskMap_.upper_bound(now)) + == manager_->taskMap_.begin()) { + std::chrono::milliseconds timeout(0); + if (!manager_->taskMap_.empty()) { + timeout = std::chrono::duration_cast(manager_->taskMap_.begin()->first - now); + //because the unit of steady_clock is smaller than millisecond,timeout may be 0. + if (timeout.count() == 0) { + timeout = std::chrono::milliseconds(1); + } + manager_->monitor_.waitForTimeRelative(timeout); + } else { + manager_->monitor_.waitForTimeRelative(0); + } + now = std::chrono::steady_clock::now(); + } + + if (manager_->state_ == TimerManager::STARTED) { + for (auto ix = manager_->taskMap_.begin(); ix != expiredTaskEnd; ix++) { + shared_ptr task = ix->second; + expiredTasks.insert(task); + task->it_ = manager_->taskMap_.end(); + if (task->state_ == TimerManager::Task::WAITING) { + task->state_ = TimerManager::Task::EXECUTING; + } + manager_->taskCount_--; + } + manager_->taskMap_.erase(manager_->taskMap_.begin(), expiredTaskEnd); + } + } + + for (const auto & expiredTask : expiredTasks) { + expiredTask->run(); + } + + } while (manager_->state_ == TimerManager::STARTED); + + { + Synchronized s(manager_->monitor_); + if (manager_->state_ == TimerManager::STOPPING) { + manager_->state_ = TimerManager::STOPPED; + manager_->monitor_.notifyAll(); + } + } + return; + } + +private: + TimerManager* manager_; + friend class TimerManager; +}; + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable : 4355) // 'this' used in base member initializer list +#endif + +TimerManager::TimerManager() + : taskCount_(0), + state_(TimerManager::UNINITIALIZED), + dispatcher_(std::make_shared(this)) { +} + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +TimerManager::~TimerManager() { + + // If we haven't been explicitly stopped, do so now. We don't need to grab + // the monitor here, since stop already takes care of reentrancy. + + if (state_ != STOPPED) { + try { + stop(); + } catch (...) { + // We're really hosed. + } + } +} + +void TimerManager::start() { + bool doStart = false; + { + Synchronized s(monitor_); + if (!threadFactory_) { + throw InvalidArgumentException(); + } + if (state_ == TimerManager::UNINITIALIZED) { + state_ = TimerManager::STARTING; + doStart = true; + } + } + + if (doStart) { + dispatcherThread_ = threadFactory_->newThread(dispatcher_); + dispatcherThread_->start(); + } + + { + Synchronized s(monitor_); + while (state_ == TimerManager::STARTING) { + monitor_.wait(); + } + assert(state_ != TimerManager::STARTING); + } +} + +void TimerManager::stop() { + bool doStop = false; + { + Synchronized s(monitor_); + if (state_ == TimerManager::UNINITIALIZED) { + state_ = TimerManager::STOPPED; + } else if (state_ != STOPPING && state_ != STOPPED) { + doStop = true; + state_ = STOPPING; + monitor_.notifyAll(); + } + while (state_ != STOPPED) { + monitor_.wait(); + } + } + + if (doStop) { + // Clean up any outstanding tasks + taskMap_.clear(); + + // Remove dispatcher's reference to us. + dispatcher_->manager_ = nullptr; + } +} + +shared_ptr TimerManager::threadFactory() const { + Synchronized s(monitor_); + return threadFactory_; +} + +void TimerManager::threadFactory(shared_ptr value) { + Synchronized s(monitor_); + threadFactory_ = value; +} + +size_t TimerManager::taskCount() const { + return taskCount_; +} + +TimerManager::Timer TimerManager::add(shared_ptr task, const std::chrono::milliseconds &timeout) { + return add(task, std::chrono::steady_clock::now() + timeout); +} + +TimerManager::Timer TimerManager::add(shared_ptr task, + const std::chrono::time_point& abstime) { + auto now = std::chrono::steady_clock::now(); + + if (abstime < now) { + throw InvalidArgumentException(); + } + Synchronized s(monitor_); + if (state_ != TimerManager::STARTED) { + throw IllegalStateException(); + } + + // If the task map is empty, we will kick the dispatcher for sure. Otherwise, we kick him + // if the expiration time is shorter than the current value. Need to test before we insert, + // because the new task might insert at the front. + bool notifyRequired = (taskCount_ == 0) ? true : abstime < taskMap_.begin()->first; + + shared_ptr timer(new Task(task)); + taskCount_++; + timer->it_ = taskMap_.emplace(abstime, timer); + + // If the task map was empty, or if we have an expiration that is earlier + // than any previously seen, kick the dispatcher so it can update its + // timeout + if (notifyRequired) { + monitor_.notify(); + } + + return timer; +} + +void TimerManager::remove(shared_ptr task) { + Synchronized s(monitor_); + if (state_ != TimerManager::STARTED) { + throw IllegalStateException(); + } + bool found = false; + for (auto ix = taskMap_.begin(); ix != taskMap_.end();) { + if (*ix->second == task) { + found = true; + taskCount_--; + taskMap_.erase(ix++); + } else { + ++ix; + } + } + if (!found) { + throw NoSuchTaskException(); + } +} + +void TimerManager::remove(Timer handle) { + Synchronized s(monitor_); + if (state_ != TimerManager::STARTED) { + throw IllegalStateException(); + } + + shared_ptr task = handle.lock(); + if (!task) { + throw NoSuchTaskException(); + } + + if (task->it_ == taskMap_.end()) { + // Task is being executed + throw UncancellableTaskException(); + } + + taskMap_.erase(task->it_); + taskCount_--; +} + +TimerManager::STATE TimerManager::state() const { + return state_; +} +} +} +} // apache::thrift::concurrency diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/concurrency/TimerManager.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/concurrency/TimerManager.h new file mode 100644 index 000000000..44d4738d5 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/concurrency/TimerManager.h @@ -0,0 +1,137 @@ +/* + * 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. + */ + +#ifndef _THRIFT_CONCURRENCY_TIMERMANAGER_H_ +#define _THRIFT_CONCURRENCY_TIMERMANAGER_H_ 1 + +#include +#include + +#include +#include + +namespace apache { +namespace thrift { +namespace concurrency { + +/** + * Timer Manager + * + * This class dispatches timer tasks when they fall due. + * + * @version $Id:$ + */ +class TimerManager { + +public: + class Task; + typedef std::weak_ptr Timer; + + TimerManager(); + + virtual ~TimerManager(); + + virtual std::shared_ptr threadFactory() const; + + virtual void threadFactory(std::shared_ptr value); + + /** + * Starts the timer manager service + * + * @throws IllegalArgumentException Missing thread factory attribute + */ + virtual void start(); + + /** + * Stops the timer manager service + */ + virtual void stop(); + + virtual size_t taskCount() const; + + /** + * Adds a task to be executed at some time in the future by a worker thread. + * + * @param task The task to execute + * @param timeout Time in milliseconds to delay before executing task + * @return Handle of the timer, which can be used to remove the timer. + */ + virtual Timer add(std::shared_ptr task, const std::chrono::milliseconds &timeout); + Timer add(std::shared_ptr task, uint64_t timeout) { return add(task,std::chrono::milliseconds(timeout)); } + + /** + * Adds a task to be executed at some time in the future by a worker thread. + * + * @param task The task to execute + * @param abstime Absolute time in the future to execute task. + * @return Handle of the timer, which can be used to remove the timer. + */ + virtual Timer add(std::shared_ptr task, const std::chrono::time_point& abstime); + + /** + * Removes a pending task + * + * @param task The task to remove. All timers which execute this task will + * be removed. + * @throws NoSuchTaskException Specified task doesn't exist. It was either + * processed already or this call was made for a + * task that was never added to this timer + * + * @throws UncancellableTaskException Specified task is already being + * executed or has completed execution. + */ + virtual void remove(std::shared_ptr task); + + /** + * Removes a single pending task + * + * @param timer The timer to remove. The timer is returned when calling the + * add() method. + * @throws NoSuchTaskException Specified task doesn't exist. It was either + * processed already or this call was made for a + * task that was never added to this timer + * + * @throws UncancellableTaskException Specified task is already being + * executed or has completed execution. + */ + virtual void remove(Timer timer); + + enum STATE { UNINITIALIZED, STARTING, STARTED, STOPPING, STOPPED }; + + virtual STATE state() const; + +private: + std::shared_ptr threadFactory_; + friend class Task; + std::multimap, std::shared_ptr > taskMap_; + size_t taskCount_; + Monitor monitor_; + STATE state_; + class Dispatcher; + friend class Dispatcher; + std::shared_ptr dispatcher_; + std::shared_ptr dispatcherThread_; + using task_iterator = decltype(taskMap_)::iterator; + typedef std::pair task_range; +}; +} +} +} // apache::thrift::concurrency + +#endif // #ifndef _THRIFT_CONCURRENCY_TIMERMANAGER_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/processor/PeekProcessor.cpp b/src/jaegertracing/thrift/lib/cpp/src/thrift/processor/PeekProcessor.cpp new file mode 100644 index 000000000..4cd58b8db --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/processor/PeekProcessor.cpp @@ -0,0 +1,131 @@ +/* + * 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. + */ + +#include + +using namespace apache::thrift::transport; +using namespace apache::thrift::protocol; +using namespace apache::thrift; + +namespace apache { +namespace thrift { +namespace processor { + +PeekProcessor::PeekProcessor() { + memoryBuffer_.reset(new TMemoryBuffer()); + targetTransport_ = memoryBuffer_; +} +PeekProcessor::~PeekProcessor() = default; + +void PeekProcessor::initialize(std::shared_ptr actualProcessor, + std::shared_ptr protocolFactory, + std::shared_ptr transportFactory) { + actualProcessor_ = actualProcessor; + pipedProtocol_ = protocolFactory->getProtocol(targetTransport_); + transportFactory_ = transportFactory; + transportFactory_->initializeTargetTransport(targetTransport_); +} + +std::shared_ptr PeekProcessor::getPipedTransport(std::shared_ptr in) { + return transportFactory_->getTransport(in); +} + +void PeekProcessor::setTargetTransport(std::shared_ptr targetTransport) { + targetTransport_ = targetTransport; + if (std::dynamic_pointer_cast(targetTransport_)) { + memoryBuffer_ = std::dynamic_pointer_cast(targetTransport); + } else if (std::dynamic_pointer_cast(targetTransport_)) { + memoryBuffer_ = std::dynamic_pointer_cast( + std::dynamic_pointer_cast(targetTransport_)->getTargetTransport()); + } + + if (!memoryBuffer_) { + throw TException( + "Target transport must be a TMemoryBuffer or a TPipedTransport with TMemoryBuffer"); + } +} + +bool PeekProcessor::process(std::shared_ptr in, + std::shared_ptr out, + void* connectionContext) { + + std::string fname; + TMessageType mtype; + int32_t seqid; + in->readMessageBegin(fname, mtype, seqid); + + if (mtype != T_CALL && mtype != T_ONEWAY) { + throw TException("Unexpected message type"); + } + + // Peek at the name + peekName(fname); + + TType ftype; + int16_t fid; + while (true) { + in->readFieldBegin(fname, ftype, fid); + if (ftype == T_STOP) { + break; + } + + // Peek at the variable + peek(in, ftype, fid); + in->readFieldEnd(); + } + in->readMessageEnd(); + in->getTransport()->readEnd(); + + // + // All the data is now in memoryBuffer_ and ready to be processed + // + + // Let's first take a peek at the full data in memory + uint8_t* buffer; + uint32_t size; + memoryBuffer_->getBuffer(&buffer, &size); + peekBuffer(buffer, size); + + // Done peeking at variables + peekEnd(); + + bool ret = actualProcessor_->process(pipedProtocol_, out, connectionContext); + memoryBuffer_->resetBuffer(); + return ret; +} + +void PeekProcessor::peekName(const std::string& fname) { + (void)fname; +} + +void PeekProcessor::peekBuffer(uint8_t* buffer, uint32_t size) { + (void)buffer; + (void)size; +} + +void PeekProcessor::peek(std::shared_ptr in, TType ftype, int16_t fid) { + (void)fid; + in->skip(ftype); +} + +void PeekProcessor::peekEnd() { +} +} +} +} diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/processor/PeekProcessor.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/processor/PeekProcessor.h new file mode 100644 index 000000000..ae565fc4b --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/processor/PeekProcessor.h @@ -0,0 +1,83 @@ +/* + * 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. + */ + +#ifndef PEEKPROCESSOR_H +#define PEEKPROCESSOR_H + +#include +#include +#include +#include +#include +#include + +namespace apache { +namespace thrift { +namespace processor { + +/* + * Class for peeking at the raw data that is being processed by another processor + * and gives the derived class a chance to change behavior accordingly + * + */ +class PeekProcessor : public apache::thrift::TProcessor { + +public: + PeekProcessor(); + ~PeekProcessor() override; + + // Input here: actualProcessor - the underlying processor + // protocolFactory - the protocol factory used to wrap the memory buffer + // transportFactory - this TPipedTransportFactory is used to wrap the source transport + // via a call to getPipedTransport + void initialize( + std::shared_ptr actualProcessor, + std::shared_ptr protocolFactory, + std::shared_ptr transportFactory); + + std::shared_ptr getPipedTransport( + std::shared_ptr in); + + void setTargetTransport(std::shared_ptr targetTransport); + + bool process(std::shared_ptr in, + std::shared_ptr out, + void* connectionContext) override; + + // The following three functions can be overloaded by child classes to + // achieve desired peeking behavior + virtual void peekName(const std::string& fname); + virtual void peekBuffer(uint8_t* buffer, uint32_t size); + virtual void peek(std::shared_ptr in, + apache::thrift::protocol::TType ftype, + int16_t fid); + virtual void peekEnd(); + +private: + std::shared_ptr actualProcessor_; + std::shared_ptr pipedProtocol_; + std::shared_ptr transportFactory_; + std::shared_ptr memoryBuffer_; + std::shared_ptr targetTransport_; +}; +} +} +} // apache::thrift::processor + +#endif diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/processor/StatsProcessor.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/processor/StatsProcessor.h new file mode 100644 index 000000000..e98efb82c --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/processor/StatsProcessor.h @@ -0,0 +1,242 @@ +/* + * 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. + */ + +#ifndef STATSPROCESSOR_H +#define STATSPROCESSOR_H + +#include +#include +#include +#include + +namespace apache { +namespace thrift { +namespace processor { + +/* + * Class for keeping track of function call statistics and printing them if desired + * + */ +class StatsProcessor : public apache::thrift::TProcessor { +public: + StatsProcessor(bool print, bool frequency) : print_(print), frequency_(frequency) {} + virtual ~StatsProcessor(){}; + + virtual bool process(std::shared_ptr piprot, + std::shared_ptr poprot, + void* serverContext) { + + piprot_ = piprot; + + std::string fname; + apache::thrift::protocol::TMessageType mtype; + int32_t seqid; + + piprot_->readMessageBegin(fname, mtype, seqid); + if (mtype != apache::thrift::protocol::T_CALL && mtype != apache::thrift::protocol::T_ONEWAY) { + if (print_) { + printf("Unknown message type\n"); + } + throw apache::thrift::TException("Unexpected message type"); + } + if (print_) { + printf("%s (", fname.c_str()); + } + if (frequency_) { + if (frequency_map_.find(fname) != frequency_map_.end()) { + frequency_map_[fname]++; + } else { + frequency_map_[fname] = 1; + } + } + + apache::thrift::protocol::TType ftype; + int16_t fid; + + while (true) { + piprot_->readFieldBegin(fname, ftype, fid); + if (ftype == apache::thrift::protocol::T_STOP) { + break; + } + + printAndPassToBuffer(ftype); + if (print_) { + printf(", "); + } + } + + if (print_) { + printf("\b\b)\n"); + } + return true; + } + + const std::map& get_frequency_map() { return frequency_map_; } + +protected: + void printAndPassToBuffer(apache::thrift::protocol::TType ftype) { + switch (ftype) { + case apache::thrift::protocol::T_BOOL: { + bool boolv; + piprot_->readBool(boolv); + if (print_) { + printf("%d", boolv); + } + } break; + case apache::thrift::protocol::T_BYTE: { + int8_t bytev; + piprot_->readByte(bytev); + if (print_) { + printf("%d", bytev); + } + } break; + case apache::thrift::protocol::T_I16: { + int16_t i16; + piprot_->readI16(i16); + if (print_) { + printf("%d", i16); + } + } break; + case apache::thrift::protocol::T_I32: { + int32_t i32; + piprot_->readI32(i32); + if (print_) { + printf("%d", i32); + } + } break; + case apache::thrift::protocol::T_I64: { + int64_t i64; + piprot_->readI64(i64); + if (print_) { + printf("%ld", i64); + } + } break; + case apache::thrift::protocol::T_DOUBLE: { + double dub; + piprot_->readDouble(dub); + if (print_) { + printf("%f", dub); + } + } break; + case apache::thrift::protocol::T_STRING: { + std::string str; + piprot_->readString(str); + if (print_) { + printf("%s", str.c_str()); + } + } break; + case apache::thrift::protocol::T_STRUCT: { + std::string name; + int16_t fid; + apache::thrift::protocol::TType ftype; + piprot_->readStructBegin(name); + if (print_) { + printf("<"); + } + while (true) { + piprot_->readFieldBegin(name, ftype, fid); + if (ftype == apache::thrift::protocol::T_STOP) { + break; + } + printAndPassToBuffer(ftype); + if (print_) { + printf(","); + } + piprot_->readFieldEnd(); + } + piprot_->readStructEnd(); + if (print_) { + printf("\b>"); + } + } break; + case apache::thrift::protocol::T_MAP: { + apache::thrift::protocol::TType keyType; + apache::thrift::protocol::TType valType; + uint32_t i, size; + piprot_->readMapBegin(keyType, valType, size); + if (print_) { + printf("{"); + } + for (i = 0; i < size; i++) { + printAndPassToBuffer(keyType); + if (print_) { + printf("=>"); + } + printAndPassToBuffer(valType); + if (print_) { + printf(","); + } + } + piprot_->readMapEnd(); + if (print_) { + printf("\b}"); + } + } break; + case apache::thrift::protocol::T_SET: { + apache::thrift::protocol::TType elemType; + uint32_t i, size; + piprot_->readSetBegin(elemType, size); + if (print_) { + printf("{"); + } + for (i = 0; i < size; i++) { + printAndPassToBuffer(elemType); + if (print_) { + printf(","); + } + } + piprot_->readSetEnd(); + if (print_) { + printf("\b}"); + } + } break; + case apache::thrift::protocol::T_LIST: { + apache::thrift::protocol::TType elemType; + uint32_t i, size; + piprot_->readListBegin(elemType, size); + if (print_) { + printf("["); + } + for (i = 0; i < size; i++) { + printAndPassToBuffer(elemType); + if (print_) { + printf(","); + } + } + piprot_->readListEnd(); + if (print_) { + printf("\b]"); + } + } break; + default: + break; + } + } + + std::shared_ptr piprot_; + std::map frequency_map_; + + bool print_; + bool frequency_; +}; +} +} +} // apache::thrift::processor + +#endif diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/processor/TMultiplexedProcessor.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/processor/TMultiplexedProcessor.h new file mode 100644 index 000000000..85c0affc2 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/processor/TMultiplexedProcessor.h @@ -0,0 +1,224 @@ +/* + * 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. + */ + +#ifndef THRIFT_TMULTIPLEXEDPROCESSOR_H_ +#define THRIFT_TMULTIPLEXEDPROCESSOR_H_ 1 + +#include +#include +#include +#include + +namespace apache { +namespace thrift { +namespace protocol { + +/** + * To be able to work with any protocol, we needed + * to allow them to call readMessageBegin() and get a TMessage in exactly + * the standard format, without the service name prepended to TMessage.name. + */ +class StoredMessageProtocol : public TProtocolDecorator { +public: + StoredMessageProtocol(std::shared_ptr _protocol, + const std::string& _name, + const TMessageType _type, + const int32_t _seqid) + : TProtocolDecorator(_protocol), name(_name), type(_type), seqid(_seqid) {} + + uint32_t readMessageBegin_virt(std::string& _name, TMessageType& _type, int32_t& _seqid) override { + + _name = name; + _type = type; + _seqid = seqid; + + return 0; // (Normal TProtocol read functions return number of bytes read) + } + + std::string name; + TMessageType type; + int32_t seqid; +}; +} // namespace protocol + +/** + * TMultiplexedProcessor is a TProcessor allowing + * a single TServer to provide multiple services. + * + *

To do so, you instantiate the processor and then register additional + * processors with it, as shown in the following example:

+ * + *
+ * std::shared_ptr processor(new TMultiplexedProcessor()); + * + * processor->registerProcessor( + * "Calculator", + * std::shared_ptr( new CalculatorProcessor( + * std::shared_ptr( new CalculatorHandler())))); + * + * processor->registerProcessor( + * "WeatherReport", + * std::shared_ptr( new WeatherReportProcessor( + * std::shared_ptr( new WeatherReportHandler())))); + * + * std::shared_ptr transport(new TServerSocket(9090)); + * TSimpleServer server(processor, transport); + * + * server.serve(); + *
+ */ +class TMultiplexedProcessor : public TProcessor { +public: + typedef std::map > services_t; + + /** + * 'Register' a service with this TMultiplexedProcessor. This + * allows us to broker requests to individual services by using the service + * name to select them at request time. + * + * \param [in] serviceName Name of a service, has to be identical to the name + * declared in the Thrift IDL, e.g. "WeatherReport". + * \param [in] processor Implementation of a service, usually referred to + * as "handlers", e.g. WeatherReportHandler, + * implementing WeatherReportIf interface. + */ + void registerProcessor(const std::string& serviceName, std::shared_ptr processor) { + services[serviceName] = processor; + } + + /** + * Register a service to be called to process queries without service name + * \param [in] processor Implementation of a service. + */ + void registerDefault(const std::shared_ptr& processor) { + defaultProcessor = processor; + } + + /** + * Chew up invalid input and return an exception to throw. + */ + TException protocol_error(std::shared_ptr in, + std::shared_ptr out, + const std::string& name, + int32_t seqid, + const std::string& msg) const { + in->skip(::apache::thrift::protocol::T_STRUCT); + in->readMessageEnd(); + in->getTransport()->readEnd(); + ::apache::thrift::TApplicationException + x(::apache::thrift::TApplicationException::PROTOCOL_ERROR, + "TMultiplexedProcessor: " + msg); + out->writeMessageBegin(name, ::apache::thrift::protocol::T_EXCEPTION, seqid); + x.write(out.get()); + out->writeMessageEnd(); + out->getTransport()->writeEnd(); + out->getTransport()->flush(); + return TException(msg); +} + + /** + * This implementation of process performs the following steps: + * + *
    + *
  1. Read the beginning of the message.
  2. + *
  3. Extract the service name from the message.
  4. + *
  5. Using the service name to locate the appropriate processor.
  6. + *
  7. Dispatch to the processor, with a decorated instance of TProtocol + * that allows readMessageBegin() to return the original TMessage.
  8. + *
+ * + * \throws TException If the message type is not T_CALL or T_ONEWAY, if + * the service name was not found in the message, or if the service + * name was not found in the service map. + */ + bool process(std::shared_ptr in, + std::shared_ptr out, + void* connectionContext) override { + std::string name; + protocol::TMessageType type; + int32_t seqid; + + // Use the actual underlying protocol (e.g. TBinaryProtocol) to read the + // message header. This pulls the message "off the wire", which we'll + // deal with at the end of this method. + in->readMessageBegin(name, type, seqid); + + if (type != protocol::T_CALL && type != protocol::T_ONEWAY) { + // Unexpected message type. + throw protocol_error(in, out, name, seqid, "Unexpected message type"); + } + + // Extract the service name + boost::tokenizer > tok(name, boost::char_separator(":")); + + std::vector tokens; + std::copy(tok.begin(), tok.end(), std::back_inserter(tokens)); + + // A valid message should consist of two tokens: the service + // name and the name of the method to call. + if (tokens.size() == 2) { + // Search for a processor associated with this service name. + auto it = services.find(tokens[0]); + + if (it != services.end()) { + std::shared_ptr processor = it->second; + // Let the processor registered for this service name + // process the message. + return processor + ->process(std::shared_ptr( + new protocol::StoredMessageProtocol(in, tokens[1], type, seqid)), + out, + connectionContext); + } else { + // Unknown service. + throw protocol_error(in, out, name, seqid, + "Unknown service: " + tokens[0] + + ". Did you forget to call registerProcessor()?"); + } + } else if (tokens.size() == 1) { + if (defaultProcessor) { + // non-multiplexed client forwards to default processor + return defaultProcessor + ->process(std::shared_ptr( + new protocol::StoredMessageProtocol(in, tokens[0], type, seqid)), + out, + connectionContext); + } else { + throw protocol_error(in, out, name, seqid, + "Non-multiplexed client request dropped. " + "Did you forget to call defaultProcessor()?"); + } + } else { + throw protocol_error(in, out, name, seqid, + "Wrong number of tokens."); + } + } + +private: + /** Map of service processor objects, indexed by service names. */ + services_t services; + + //! If a non-multi client requests something, it goes to the + //! default processor (if one is defined) for backwards compatibility. + std::shared_ptr defaultProcessor; +}; +} +} + +#endif // THRIFT_TMULTIPLEXEDPROCESSOR_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TBase64Utils.cpp b/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TBase64Utils.cpp new file mode 100644 index 000000000..7474f5af8 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TBase64Utils.cpp @@ -0,0 +1,315 @@ +/* + * 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. + */ + +#include + +using std::string; + +namespace apache { +namespace thrift { +namespace protocol { + +static const uint8_t* kBase64EncodeTable + = (const uint8_t*)"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +void base64_encode(const uint8_t* in, uint32_t len, uint8_t* buf) { + buf[0] = kBase64EncodeTable[(in[0] >> 2) & 0x3f]; + if (len == 3) { + buf[1] = kBase64EncodeTable[((in[0] << 4) & 0x30) | ((in[1] >> 4) & 0x0f)]; + buf[2] = kBase64EncodeTable[((in[1] << 2) & 0x3c) | ((in[2] >> 6) & 0x03)]; + buf[3] = kBase64EncodeTable[in[2] & 0x3f]; + } else if (len == 2) { + buf[1] = kBase64EncodeTable[((in[0] << 4) & 0x30) | ((in[1] >> 4) & 0x0f)]; + buf[2] = kBase64EncodeTable[(in[1] << 2) & 0x3c]; + } else { // len == 1 + buf[1] = kBase64EncodeTable[(in[0] << 4) & 0x30]; + } +} + +static const uint8_t kBase64DecodeTable[256] = { + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0x3e, + 0xff, + 0xff, + 0xff, + 0x3f, + 0x34, + 0x35, + 0x36, + 0x37, + 0x38, + 0x39, + 0x3a, + 0x3b, + 0x3c, + 0x3d, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0x00, + 0x01, + 0x02, + 0x03, + 0x04, + 0x05, + 0x06, + 0x07, + 0x08, + 0x09, + 0x0a, + 0x0b, + 0x0c, + 0x0d, + 0x0e, + 0x0f, + 0x10, + 0x11, + 0x12, + 0x13, + 0x14, + 0x15, + 0x16, + 0x17, + 0x18, + 0x19, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0x1a, + 0x1b, + 0x1c, + 0x1d, + 0x1e, + 0x1f, + 0x20, + 0x21, + 0x22, + 0x23, + 0x24, + 0x25, + 0x26, + 0x27, + 0x28, + 0x29, + 0x2a, + 0x2b, + 0x2c, + 0x2d, + 0x2e, + 0x2f, + 0x30, + 0x31, + 0x32, + 0x33, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, +}; + +void base64_decode(uint8_t* buf, uint32_t len) { + buf[0] = (kBase64DecodeTable[buf[0]] << 2) | (kBase64DecodeTable[buf[1]] >> 4); + if (len > 2) { + buf[1] = ((kBase64DecodeTable[buf[1]] << 4) & 0xf0) | (kBase64DecodeTable[buf[2]] >> 2); + if (len > 3) { + buf[2] = ((kBase64DecodeTable[buf[2]] << 6) & 0xc0) | (kBase64DecodeTable[buf[3]]); + } + } +} +} +} +} // apache::thrift::protocol diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TBase64Utils.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TBase64Utils.h new file mode 100644 index 000000000..1ea67440e --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TBase64Utils.h @@ -0,0 +1,45 @@ +/* + * 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. + */ + +#ifndef _THRIFT_PROTOCOL_TBASE64UTILS_H_ +#define _THRIFT_PROTOCOL_TBASE64UTILS_H_ + +#include +#include + +namespace apache { +namespace thrift { +namespace protocol { + +// in must be at least len bytes +// len must be 1, 2, or 3 +// buf must be a buffer of at least 4 bytes and may not overlap in +// the data is not padded with '='; the caller can do this if desired +void base64_encode(const uint8_t* in, uint32_t len, uint8_t* buf); + +// buf must be a buffer of at least 4 bytes and contain base64 encoded values +// buf will be changed to contain output bytes +// len is number of bytes to consume from input (must be 2, 3, or 4) +// no '=' padding should be included in the input +void base64_decode(uint8_t* buf, uint32_t len); +} +} +} // apache::thrift::protocol + +#endif // #define _THRIFT_PROTOCOL_TBASE64UTILS_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TBinaryProtocol.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TBinaryProtocol.h new file mode 100644 index 000000000..6bd5fb830 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TBinaryProtocol.h @@ -0,0 +1,250 @@ +/* + * 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. + */ + +#ifndef _THRIFT_PROTOCOL_TBINARYPROTOCOL_H_ +#define _THRIFT_PROTOCOL_TBINARYPROTOCOL_H_ 1 + +#include +#include + +#include + +namespace apache { +namespace thrift { +namespace protocol { + +/** + * The default binary protocol for thrift. Writes all data in a very basic + * binary format, essentially just spitting out the raw bytes. + * + */ +template +class TBinaryProtocolT : public TVirtualProtocol > { +public: + static const int32_t VERSION_MASK = ((int32_t)0xffff0000); + static const int32_t VERSION_1 = ((int32_t)0x80010000); + // VERSION_2 (0x80020000) was taken by TDenseProtocol (which has since been removed) + + TBinaryProtocolT(std::shared_ptr trans) + : TVirtualProtocol >(trans), + trans_(trans.get()), + string_limit_(0), + container_limit_(0), + strict_read_(false), + strict_write_(true) {} + + TBinaryProtocolT(std::shared_ptr trans, + int32_t string_limit, + int32_t container_limit, + bool strict_read, + bool strict_write) + : TVirtualProtocol >(trans), + trans_(trans.get()), + string_limit_(string_limit), + container_limit_(container_limit), + strict_read_(strict_read), + strict_write_(strict_write) {} + + void setStringSizeLimit(int32_t string_limit) { string_limit_ = string_limit; } + + void setContainerSizeLimit(int32_t container_limit) { container_limit_ = container_limit; } + + void setStrict(bool strict_read, bool strict_write) { + strict_read_ = strict_read; + strict_write_ = strict_write; + } + + /** + * Writing functions. + */ + + /*ol*/ uint32_t writeMessageBegin(const std::string& name, + const TMessageType messageType, + const int32_t seqid); + + /*ol*/ uint32_t writeMessageEnd(); + + inline uint32_t writeStructBegin(const char* name); + + inline uint32_t writeStructEnd(); + + inline uint32_t writeFieldBegin(const char* name, const TType fieldType, const int16_t fieldId); + + inline uint32_t writeFieldEnd(); + + inline uint32_t writeFieldStop(); + + inline uint32_t writeMapBegin(const TType keyType, const TType valType, const uint32_t size); + + inline uint32_t writeMapEnd(); + + inline uint32_t writeListBegin(const TType elemType, const uint32_t size); + + inline uint32_t writeListEnd(); + + inline uint32_t writeSetBegin(const TType elemType, const uint32_t size); + + inline uint32_t writeSetEnd(); + + inline uint32_t writeBool(const bool value); + + inline uint32_t writeByte(const int8_t byte); + + inline uint32_t writeI16(const int16_t i16); + + inline uint32_t writeI32(const int32_t i32); + + inline uint32_t writeI64(const int64_t i64); + + inline uint32_t writeDouble(const double dub); + + template + inline uint32_t writeString(const StrType& str); + + inline uint32_t writeBinary(const std::string& str); + + /** + * Reading functions + */ + + /*ol*/ uint32_t readMessageBegin(std::string& name, TMessageType& messageType, int32_t& seqid); + + /*ol*/ uint32_t readMessageEnd(); + + inline uint32_t readStructBegin(std::string& name); + + inline uint32_t readStructEnd(); + + inline uint32_t readFieldBegin(std::string& name, TType& fieldType, int16_t& fieldId); + + inline uint32_t readFieldEnd(); + + inline uint32_t readMapBegin(TType& keyType, TType& valType, uint32_t& size); + + inline uint32_t readMapEnd(); + + inline uint32_t readListBegin(TType& elemType, uint32_t& size); + + inline uint32_t readListEnd(); + + inline uint32_t readSetBegin(TType& elemType, uint32_t& size); + + inline uint32_t readSetEnd(); + + inline uint32_t readBool(bool& value); + // Provide the default readBool() implementation for std::vector + using TVirtualProtocol >::readBool; + + inline uint32_t readByte(int8_t& byte); + + inline uint32_t readI16(int16_t& i16); + + inline uint32_t readI32(int32_t& i32); + + inline uint32_t readI64(int64_t& i64); + + inline uint32_t readDouble(double& dub); + + template + inline uint32_t readString(StrType& str); + + inline uint32_t readBinary(std::string& str); + +protected: + template + uint32_t readStringBody(StrType& str, int32_t sz); + + Transport_* trans_; + + int32_t string_limit_; + int32_t container_limit_; + + // Enforce presence of version identifier + bool strict_read_; + bool strict_write_; +}; + +typedef TBinaryProtocolT TBinaryProtocol; +typedef TBinaryProtocolT TLEBinaryProtocol; + +/** + * Constructs binary protocol handlers + */ +template +class TBinaryProtocolFactoryT : public TProtocolFactory { +public: + TBinaryProtocolFactoryT() + : string_limit_(0), container_limit_(0), strict_read_(false), strict_write_(true) {} + + TBinaryProtocolFactoryT(int32_t string_limit, + int32_t container_limit, + bool strict_read, + bool strict_write) + : string_limit_(string_limit), + container_limit_(container_limit), + strict_read_(strict_read), + strict_write_(strict_write) {} + + ~TBinaryProtocolFactoryT() override = default; + + void setStringSizeLimit(int32_t string_limit) { string_limit_ = string_limit; } + + void setContainerSizeLimit(int32_t container_limit) { container_limit_ = container_limit; } + + void setStrict(bool strict_read, bool strict_write) { + strict_read_ = strict_read; + strict_write_ = strict_write; + } + + std::shared_ptr getProtocol(std::shared_ptr trans) override { + std::shared_ptr specific_trans = std::dynamic_pointer_cast(trans); + TProtocol* prot; + if (specific_trans) { + prot = new TBinaryProtocolT(specific_trans, + string_limit_, + container_limit_, + strict_read_, + strict_write_); + } else { + prot = new TBinaryProtocolT(trans, + string_limit_, + container_limit_, + strict_read_, + strict_write_); + } + + return std::shared_ptr(prot); + } + +private: + int32_t string_limit_; + int32_t container_limit_; + bool strict_read_; + bool strict_write_; +}; + +typedef TBinaryProtocolFactoryT TBinaryProtocolFactory; +typedef TBinaryProtocolFactoryT TLEBinaryProtocolFactory; +} +} +} // apache::thrift::protocol + +#include + +#endif // #ifndef _THRIFT_PROTOCOL_TBINARYPROTOCOL_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TBinaryProtocol.tcc b/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TBinaryProtocol.tcc new file mode 100644 index 000000000..2964f25d0 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TBinaryProtocol.tcc @@ -0,0 +1,454 @@ +/* + * 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. + */ + +#ifndef _THRIFT_PROTOCOL_TBINARYPROTOCOL_TCC_ +#define _THRIFT_PROTOCOL_TBINARYPROTOCOL_TCC_ 1 + +#include + +#include + +namespace apache { +namespace thrift { +namespace protocol { + +template +uint32_t TBinaryProtocolT::writeMessageBegin(const std::string& name, + const TMessageType messageType, + const int32_t seqid) { + if (this->strict_write_) { + int32_t version = (VERSION_1) | ((int32_t)messageType); + uint32_t wsize = 0; + wsize += writeI32(version); + wsize += writeString(name); + wsize += writeI32(seqid); + return wsize; + } else { + uint32_t wsize = 0; + wsize += writeString(name); + wsize += writeByte((int8_t)messageType); + wsize += writeI32(seqid); + return wsize; + } +} + +template +uint32_t TBinaryProtocolT::writeMessageEnd() { + return 0; +} + +template +uint32_t TBinaryProtocolT::writeStructBegin(const char* name) { + (void)name; + return 0; +} + +template +uint32_t TBinaryProtocolT::writeStructEnd() { + return 0; +} + +template +uint32_t TBinaryProtocolT::writeFieldBegin(const char* name, + const TType fieldType, + const int16_t fieldId) { + (void)name; + uint32_t wsize = 0; + wsize += writeByte((int8_t)fieldType); + wsize += writeI16(fieldId); + return wsize; +} + +template +uint32_t TBinaryProtocolT::writeFieldEnd() { + return 0; +} + +template +uint32_t TBinaryProtocolT::writeFieldStop() { + return writeByte((int8_t)T_STOP); +} + +template +uint32_t TBinaryProtocolT::writeMapBegin(const TType keyType, + const TType valType, + const uint32_t size) { + uint32_t wsize = 0; + wsize += writeByte((int8_t)keyType); + wsize += writeByte((int8_t)valType); + wsize += writeI32((int32_t)size); + return wsize; +} + +template +uint32_t TBinaryProtocolT::writeMapEnd() { + return 0; +} + +template +uint32_t TBinaryProtocolT::writeListBegin(const TType elemType, + const uint32_t size) { + uint32_t wsize = 0; + wsize += writeByte((int8_t)elemType); + wsize += writeI32((int32_t)size); + return wsize; +} + +template +uint32_t TBinaryProtocolT::writeListEnd() { + return 0; +} + +template +uint32_t TBinaryProtocolT::writeSetBegin(const TType elemType, + const uint32_t size) { + uint32_t wsize = 0; + wsize += writeByte((int8_t)elemType); + wsize += writeI32((int32_t)size); + return wsize; +} + +template +uint32_t TBinaryProtocolT::writeSetEnd() { + return 0; +} + +template +uint32_t TBinaryProtocolT::writeBool(const bool value) { + uint8_t tmp = value ? 1 : 0; + this->trans_->write(&tmp, 1); + return 1; +} + +template +uint32_t TBinaryProtocolT::writeByte(const int8_t byte) { + this->trans_->write((uint8_t*)&byte, 1); + return 1; +} + +template +uint32_t TBinaryProtocolT::writeI16(const int16_t i16) { + auto net = (int16_t)ByteOrder_::toWire16(i16); + this->trans_->write((uint8_t*)&net, 2); + return 2; +} + +template +uint32_t TBinaryProtocolT::writeI32(const int32_t i32) { + auto net = (int32_t)ByteOrder_::toWire32(i32); + this->trans_->write((uint8_t*)&net, 4); + return 4; +} + +template +uint32_t TBinaryProtocolT::writeI64(const int64_t i64) { + auto net = (int64_t)ByteOrder_::toWire64(i64); + this->trans_->write((uint8_t*)&net, 8); + return 8; +} + +template +uint32_t TBinaryProtocolT::writeDouble(const double dub) { + static_assert(sizeof(double) == sizeof(uint64_t), "sizeof(double) == sizeof(uint64_t)"); + static_assert(std::numeric_limits::is_iec559, "std::numeric_limits::is_iec559"); + + auto bits = bitwise_cast(dub); + bits = ByteOrder_::toWire64(bits); + this->trans_->write((uint8_t*)&bits, 8); + return 8; +} + +template +template +uint32_t TBinaryProtocolT::writeString(const StrType& str) { + if (str.size() > static_cast((std::numeric_limits::max)())) + throw TProtocolException(TProtocolException::SIZE_LIMIT); + auto size = static_cast(str.size()); + uint32_t result = writeI32((int32_t)size); + if (size > 0) { + this->trans_->write((uint8_t*)str.data(), size); + } + return result + size; +} + +template +uint32_t TBinaryProtocolT::writeBinary(const std::string& str) { + return TBinaryProtocolT::writeString(str); +} + +/** + * Reading functions + */ + +template +uint32_t TBinaryProtocolT::readMessageBegin(std::string& name, + TMessageType& messageType, + int32_t& seqid) { + uint32_t result = 0; + int32_t sz; + result += readI32(sz); + + if (sz < 0) { + // Check for correct version number + int32_t version = sz & VERSION_MASK; + if (version != VERSION_1) { + throw TProtocolException(TProtocolException::BAD_VERSION, "Bad version identifier"); + } + messageType = (TMessageType)(sz & 0x000000ff); + result += readString(name); + result += readI32(seqid); + } else { + if (this->strict_read_) { + throw TProtocolException(TProtocolException::BAD_VERSION, + "No version identifier... old protocol client in strict mode?"); + } else { + // Handle pre-versioned input + int8_t type; + result += readStringBody(name, sz); + result += readByte(type); + messageType = (TMessageType)type; + result += readI32(seqid); + } + } + return result; +} + +template +uint32_t TBinaryProtocolT::readMessageEnd() { + return 0; +} + +template +uint32_t TBinaryProtocolT::readStructBegin(std::string& name) { + name = ""; + return 0; +} + +template +uint32_t TBinaryProtocolT::readStructEnd() { + return 0; +} + +template +uint32_t TBinaryProtocolT::readFieldBegin(std::string& name, + TType& fieldType, + int16_t& fieldId) { + (void)name; + uint32_t result = 0; + int8_t type; + result += readByte(type); + fieldType = (TType)type; + if (fieldType == T_STOP) { + fieldId = 0; + return result; + } + result += readI16(fieldId); + return result; +} + +template +uint32_t TBinaryProtocolT::readFieldEnd() { + return 0; +} + +template +uint32_t TBinaryProtocolT::readMapBegin(TType& keyType, + TType& valType, + uint32_t& size) { + int8_t k, v; + uint32_t result = 0; + int32_t sizei; + result += readByte(k); + keyType = (TType)k; + result += readByte(v); + valType = (TType)v; + result += readI32(sizei); + if (sizei < 0) { + throw TProtocolException(TProtocolException::NEGATIVE_SIZE); + } else if (this->container_limit_ && sizei > this->container_limit_) { + throw TProtocolException(TProtocolException::SIZE_LIMIT); + } + size = (uint32_t)sizei; + return result; +} + +template +uint32_t TBinaryProtocolT::readMapEnd() { + return 0; +} + +template +uint32_t TBinaryProtocolT::readListBegin(TType& elemType, uint32_t& size) { + int8_t e; + uint32_t result = 0; + int32_t sizei; + result += readByte(e); + elemType = (TType)e; + result += readI32(sizei); + if (sizei < 0) { + throw TProtocolException(TProtocolException::NEGATIVE_SIZE); + } else if (this->container_limit_ && sizei > this->container_limit_) { + throw TProtocolException(TProtocolException::SIZE_LIMIT); + } + size = (uint32_t)sizei; + return result; +} + +template +uint32_t TBinaryProtocolT::readListEnd() { + return 0; +} + +template +uint32_t TBinaryProtocolT::readSetBegin(TType& elemType, uint32_t& size) { + int8_t e; + uint32_t result = 0; + int32_t sizei; + result += readByte(e); + elemType = (TType)e; + result += readI32(sizei); + if (sizei < 0) { + throw TProtocolException(TProtocolException::NEGATIVE_SIZE); + } else if (this->container_limit_ && sizei > this->container_limit_) { + throw TProtocolException(TProtocolException::SIZE_LIMIT); + } + size = (uint32_t)sizei; + return result; +} + +template +uint32_t TBinaryProtocolT::readSetEnd() { + return 0; +} + +template +uint32_t TBinaryProtocolT::readBool(bool& value) { + uint8_t b[1]; + this->trans_->readAll(b, 1); + value = *(int8_t*)b != 0; + return 1; +} + +template +uint32_t TBinaryProtocolT::readByte(int8_t& byte) { + uint8_t b[1]; + this->trans_->readAll(b, 1); + byte = *(int8_t*)b; + return 1; +} + +template +uint32_t TBinaryProtocolT::readI16(int16_t& i16) { + union bytes { + uint8_t b[2]; + int16_t all; + } theBytes; + this->trans_->readAll(theBytes.b, 2); + i16 = (int16_t)ByteOrder_::fromWire16(theBytes.all); + return 2; +} + +template +uint32_t TBinaryProtocolT::readI32(int32_t& i32) { + union bytes { + uint8_t b[4]; + int32_t all; + } theBytes; + this->trans_->readAll(theBytes.b, 4); + i32 = (int32_t)ByteOrder_::fromWire32(theBytes.all); + return 4; +} + +template +uint32_t TBinaryProtocolT::readI64(int64_t& i64) { + union bytes { + uint8_t b[8]; + int64_t all; + } theBytes; + this->trans_->readAll(theBytes.b, 8); + i64 = (int64_t)ByteOrder_::fromWire64(theBytes.all); + return 8; +} + +template +uint32_t TBinaryProtocolT::readDouble(double& dub) { + static_assert(sizeof(double) == sizeof(uint64_t), "sizeof(double) == sizeof(uint64_t)"); + static_assert(std::numeric_limits::is_iec559, "std::numeric_limits::is_iec559"); + + union bytes { + uint8_t b[8]; + uint64_t all; + } theBytes; + this->trans_->readAll(theBytes.b, 8); + theBytes.all = ByteOrder_::fromWire64(theBytes.all); + dub = bitwise_cast(theBytes.all); + return 8; +} + +template +template +uint32_t TBinaryProtocolT::readString(StrType& str) { + uint32_t result; + int32_t size; + result = readI32(size); + return result + readStringBody(str, size); +} + +template +uint32_t TBinaryProtocolT::readBinary(std::string& str) { + return TBinaryProtocolT::readString(str); +} + +template +template +uint32_t TBinaryProtocolT::readStringBody(StrType& str, int32_t size) { + uint32_t result = 0; + + // Catch error cases + if (size < 0) { + throw TProtocolException(TProtocolException::NEGATIVE_SIZE); + } + if (this->string_limit_ > 0 && size > this->string_limit_) { + throw TProtocolException(TProtocolException::SIZE_LIMIT); + } + + // Catch empty string case + if (size == 0) { + str.clear(); + return result; + } + + // Try to borrow first + const uint8_t* borrow_buf; + uint32_t got = size; + if ((borrow_buf = this->trans_->borrow(nullptr, &got))) { + str.assign((const char*)borrow_buf, size); + this->trans_->consume(size); + return size; + } + + str.resize(size); + this->trans_->readAll(reinterpret_cast(&str[0]), size); + return (uint32_t)size; +} +} +} +} // apache::thrift::protocol + +#endif // #ifndef _THRIFT_PROTOCOL_TBINARYPROTOCOL_TCC_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TCompactProtocol.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TCompactProtocol.h new file mode 100644 index 000000000..2930aba29 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TCompactProtocol.h @@ -0,0 +1,266 @@ +/* + * 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. + */ + +#ifndef _THRIFT_PROTOCOL_TCOMPACTPROTOCOL_H_ +#define _THRIFT_PROTOCOL_TCOMPACTPROTOCOL_H_ 1 + +#include + +#include +#include + +namespace apache { +namespace thrift { +namespace protocol { + +/** + * C++ Implementation of the Compact Protocol as described in THRIFT-110 + */ +template +class TCompactProtocolT : public TVirtualProtocol > { +public: + static const int8_t PROTOCOL_ID = (int8_t)0x82u; + static const int8_t VERSION_N = 1; + static const int8_t VERSION_MASK = 0x1f; // 0001 1111 + +protected: + static const int8_t TYPE_MASK = (int8_t)0xE0u; // 1110 0000 + static const int8_t TYPE_BITS = 0x07; // 0000 0111 + static const int32_t TYPE_SHIFT_AMOUNT = 5; + + Transport_* trans_; + + /** + * (Writing) If we encounter a boolean field begin, save the TField here + * so it can have the value incorporated. + */ + struct { + const char* name; + TType fieldType; + int16_t fieldId; + } booleanField_; + + /** + * (Reading) If we read a field header, and it's a boolean field, save + * the boolean value here so that readBool can use it. + */ + struct { + bool hasBoolValue; + bool boolValue; + } boolValue_; + + /** + * Used to keep track of the last field for the current and previous structs, + * so we can do the delta stuff. + */ + + std::stack lastField_; + int16_t lastFieldId_; + +public: + TCompactProtocolT(std::shared_ptr trans) + : TVirtualProtocol >(trans), + trans_(trans.get()), + lastFieldId_(0), + string_limit_(0), + string_buf_(nullptr), + string_buf_size_(0), + container_limit_(0) { + booleanField_.name = nullptr; + boolValue_.hasBoolValue = false; + } + + TCompactProtocolT(std::shared_ptr trans, + int32_t string_limit, + int32_t container_limit) + : TVirtualProtocol >(trans), + trans_(trans.get()), + lastFieldId_(0), + string_limit_(string_limit), + string_buf_(nullptr), + string_buf_size_(0), + container_limit_(container_limit) { + booleanField_.name = nullptr; + boolValue_.hasBoolValue = false; + } + + ~TCompactProtocolT() override { free(string_buf_); } + + /** + * Writing functions + */ + + virtual uint32_t writeMessageBegin(const std::string& name, + const TMessageType messageType, + const int32_t seqid); + + uint32_t writeStructBegin(const char* name); + + uint32_t writeStructEnd(); + + uint32_t writeFieldBegin(const char* name, const TType fieldType, const int16_t fieldId); + + uint32_t writeFieldStop(); + + uint32_t writeListBegin(const TType elemType, const uint32_t size); + + uint32_t writeSetBegin(const TType elemType, const uint32_t size); + + virtual uint32_t writeMapBegin(const TType keyType, const TType valType, const uint32_t size); + + uint32_t writeBool(const bool value); + + uint32_t writeByte(const int8_t byte); + + uint32_t writeI16(const int16_t i16); + + uint32_t writeI32(const int32_t i32); + + uint32_t writeI64(const int64_t i64); + + uint32_t writeDouble(const double dub); + + uint32_t writeString(const std::string& str); + + uint32_t writeBinary(const std::string& str); + + /** + * These methods are called by structs, but don't actually have any wired + * output or purpose + */ + virtual uint32_t writeMessageEnd() { return 0; } + uint32_t writeMapEnd() { return 0; } + uint32_t writeListEnd() { return 0; } + uint32_t writeSetEnd() { return 0; } + uint32_t writeFieldEnd() { return 0; } + +protected: + int32_t writeFieldBeginInternal(const char* name, + const TType fieldType, + const int16_t fieldId, + int8_t typeOverride); + uint32_t writeCollectionBegin(const TType elemType, int32_t size); + uint32_t writeVarint32(uint32_t n); + uint32_t writeVarint64(uint64_t n); + uint64_t i64ToZigzag(const int64_t l); + uint32_t i32ToZigzag(const int32_t n); + inline int8_t getCompactType(const TType ttype); + +public: + uint32_t readMessageBegin(std::string& name, TMessageType& messageType, int32_t& seqid); + + uint32_t readStructBegin(std::string& name); + + uint32_t readStructEnd(); + + uint32_t readFieldBegin(std::string& name, TType& fieldType, int16_t& fieldId); + + uint32_t readMapBegin(TType& keyType, TType& valType, uint32_t& size); + + uint32_t readListBegin(TType& elemType, uint32_t& size); + + uint32_t readSetBegin(TType& elemType, uint32_t& size); + + uint32_t readBool(bool& value); + // Provide the default readBool() implementation for std::vector + using TVirtualProtocol >::readBool; + + uint32_t readByte(int8_t& byte); + + uint32_t readI16(int16_t& i16); + + uint32_t readI32(int32_t& i32); + + uint32_t readI64(int64_t& i64); + + uint32_t readDouble(double& dub); + + uint32_t readString(std::string& str); + + uint32_t readBinary(std::string& str); + + /* + *These methods are here for the struct to call, but don't have any wire + * encoding. + */ + uint32_t readMessageEnd() { return 0; } + uint32_t readFieldEnd() { return 0; } + uint32_t readMapEnd() { return 0; } + uint32_t readListEnd() { return 0; } + uint32_t readSetEnd() { return 0; } + +protected: + uint32_t readVarint32(int32_t& i32); + uint32_t readVarint64(int64_t& i64); + int32_t zigzagToI32(uint32_t n); + int64_t zigzagToI64(uint64_t n); + TType getTType(int8_t type); + + // Buffer for reading strings, save for the lifetime of the protocol to + // avoid memory churn allocating memory on every string read + int32_t string_limit_; + uint8_t* string_buf_; + int32_t string_buf_size_; + int32_t container_limit_; +}; + +typedef TCompactProtocolT TCompactProtocol; + +/** + * Constructs compact protocol handlers + */ +template +class TCompactProtocolFactoryT : public TProtocolFactory { +public: + TCompactProtocolFactoryT() : string_limit_(0), container_limit_(0) {} + + TCompactProtocolFactoryT(int32_t string_limit, int32_t container_limit) + : string_limit_(string_limit), container_limit_(container_limit) {} + + ~TCompactProtocolFactoryT() override = default; + + void setStringSizeLimit(int32_t string_limit) { string_limit_ = string_limit; } + + void setContainerSizeLimit(int32_t container_limit) { container_limit_ = container_limit; } + + std::shared_ptr getProtocol(std::shared_ptr trans) override { + std::shared_ptr specific_trans = std::dynamic_pointer_cast(trans); + TProtocol* prot; + if (specific_trans) { + prot = new TCompactProtocolT(specific_trans, string_limit_, container_limit_); + } else { + prot = new TCompactProtocol(trans, string_limit_, container_limit_); + } + + return std::shared_ptr(prot); + } + +private: + int32_t string_limit_; + int32_t container_limit_; +}; + +typedef TCompactProtocolFactoryT TCompactProtocolFactory; +} +} +} // apache::thrift::protocol + +#include + +#endif diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TCompactProtocol.tcc b/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TCompactProtocol.tcc new file mode 100644 index 000000000..d1e342efd --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TCompactProtocol.tcc @@ -0,0 +1,826 @@ +/* + * 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. + */ +#ifndef _THRIFT_PROTOCOL_TCOMPACTPROTOCOL_TCC_ +#define _THRIFT_PROTOCOL_TCOMPACTPROTOCOL_TCC_ 1 + +#include + +#include "thrift/config.h" + +/* + * TCompactProtocol::i*ToZigzag depend on the fact that the right shift + * operator on a signed integer is an arithmetic (sign-extending) shift. + * If this is not the case, the current implementation will not work. + * If anyone encounters this error, we can try to figure out the best + * way to implement an arithmetic right shift on their platform. + */ +#if !defined(SIGNED_RIGHT_SHIFT_IS) || !defined(ARITHMETIC_RIGHT_SHIFT) +# error "Unable to determine the behavior of a signed right shift" +#endif +#if SIGNED_RIGHT_SHIFT_IS != ARITHMETIC_RIGHT_SHIFT +# error "TCompactProtocol currently only works if a signed right shift is arithmetic" +#endif + +#ifdef __GNUC__ +#define UNLIKELY(val) (__builtin_expect((val), 0)) +#else +#define UNLIKELY(val) (val) +#endif + +namespace apache { namespace thrift { namespace protocol { + +namespace detail { namespace compact { + +enum Types { + CT_STOP = 0x00, + CT_BOOLEAN_TRUE = 0x01, + CT_BOOLEAN_FALSE = 0x02, + CT_BYTE = 0x03, + CT_I16 = 0x04, + CT_I32 = 0x05, + CT_I64 = 0x06, + CT_DOUBLE = 0x07, + CT_BINARY = 0x08, + CT_LIST = 0x09, + CT_SET = 0x0A, + CT_MAP = 0x0B, + CT_STRUCT = 0x0C +}; + +const int8_t TTypeToCType[16] = { + CT_STOP, // T_STOP + 0, // unused + CT_BOOLEAN_TRUE, // T_BOOL + CT_BYTE, // T_BYTE + CT_DOUBLE, // T_DOUBLE + 0, // unused + CT_I16, // T_I16 + 0, // unused + CT_I32, // T_I32 + 0, // unused + CT_I64, // T_I64 + CT_BINARY, // T_STRING + CT_STRUCT, // T_STRUCT + CT_MAP, // T_MAP + CT_SET, // T_SET + CT_LIST, // T_LIST +}; + +}} // end detail::compact namespace + + +template +uint32_t TCompactProtocolT::writeMessageBegin( + const std::string& name, + const TMessageType messageType, + const int32_t seqid) { + uint32_t wsize = 0; + wsize += writeByte(PROTOCOL_ID); + wsize += writeByte((VERSION_N & VERSION_MASK) | (((int32_t)messageType << TYPE_SHIFT_AMOUNT) & TYPE_MASK)); + wsize += writeVarint32(seqid); + wsize += writeString(name); + return wsize; +} + +/** + * Write a field header containing the field id and field type. If the + * difference between the current field id and the last one is small (< 15), + * then the field id will be encoded in the 4 MSB as a delta. Otherwise, the + * field id will follow the type header as a zigzag varint. + */ +template +uint32_t TCompactProtocolT::writeFieldBegin(const char* name, + const TType fieldType, + const int16_t fieldId) { + if (fieldType == T_BOOL) { + booleanField_.name = name; + booleanField_.fieldType = fieldType; + booleanField_.fieldId = fieldId; + } else { + return writeFieldBeginInternal(name, fieldType, fieldId, -1); + } + return 0; +} + +/** + * Write the STOP symbol so we know there are no more fields in this struct. + */ +template +uint32_t TCompactProtocolT::writeFieldStop() { + return writeByte(T_STOP); +} + +/** + * Write a struct begin. This doesn't actually put anything on the wire. We + * use it as an opportunity to put special placeholder markers on the field + * stack so we can get the field id deltas correct. + */ +template +uint32_t TCompactProtocolT::writeStructBegin(const char* name) { + (void) name; + lastField_.push(lastFieldId_); + lastFieldId_ = 0; + return 0; +} + +/** + * Write a struct end. This doesn't actually put anything on the wire. We use + * this as an opportunity to pop the last field from the current struct off + * of the field stack. + */ +template +uint32_t TCompactProtocolT::writeStructEnd() { + lastFieldId_ = lastField_.top(); + lastField_.pop(); + return 0; +} + +/** + * Write a List header. + */ +template +uint32_t TCompactProtocolT::writeListBegin(const TType elemType, + const uint32_t size) { + return writeCollectionBegin(elemType, size); +} + +/** + * Write a set header. + */ +template +uint32_t TCompactProtocolT::writeSetBegin(const TType elemType, + const uint32_t size) { + return writeCollectionBegin(elemType, size); +} + +/** + * Write a map header. If the map is empty, omit the key and value type + * headers, as we don't need any additional information to skip it. + */ +template +uint32_t TCompactProtocolT::writeMapBegin(const TType keyType, + const TType valType, + const uint32_t size) { + uint32_t wsize = 0; + + if (size == 0) { + wsize += writeByte(0); + } else { + wsize += writeVarint32(size); + wsize += writeByte(getCompactType(keyType) << 4 | getCompactType(valType)); + } + return wsize; +} + +/** + * Write a boolean value. Potentially, this could be a boolean field, in + * which case the field header info isn't written yet. If so, decide what the + * right type header is for the value and then write the field header. + * Otherwise, write a single byte. + */ +template +uint32_t TCompactProtocolT::writeBool(const bool value) { + uint32_t wsize = 0; + + if (booleanField_.name != nullptr) { + // we haven't written the field header yet + wsize + += writeFieldBeginInternal(booleanField_.name, + booleanField_.fieldType, + booleanField_.fieldId, + static_cast(value + ? detail::compact::CT_BOOLEAN_TRUE + : detail::compact::CT_BOOLEAN_FALSE)); + booleanField_.name = nullptr; + } else { + // we're not part of a field, so just write the value + wsize + += writeByte(static_cast(value + ? detail::compact::CT_BOOLEAN_TRUE + : detail::compact::CT_BOOLEAN_FALSE)); + } + return wsize; +} + +template +uint32_t TCompactProtocolT::writeByte(const int8_t byte) { + trans_->write((uint8_t*)&byte, 1); + return 1; +} + +/** + * Write an i16 as a zigzag varint. + */ +template +uint32_t TCompactProtocolT::writeI16(const int16_t i16) { + return writeVarint32(i32ToZigzag(i16)); +} + +/** + * Write an i32 as a zigzag varint. + */ +template +uint32_t TCompactProtocolT::writeI32(const int32_t i32) { + return writeVarint32(i32ToZigzag(i32)); +} + +/** + * Write an i64 as a zigzag varint. + */ +template +uint32_t TCompactProtocolT::writeI64(const int64_t i64) { + return writeVarint64(i64ToZigzag(i64)); +} + +/** + * Write a double to the wire as 8 bytes. + */ +template +uint32_t TCompactProtocolT::writeDouble(const double dub) { + static_assert(sizeof(double) == sizeof(uint64_t), "sizeof(double) == sizeof(uint64_t)"); + static_assert(std::numeric_limits::is_iec559, "std::numeric_limits::is_iec559"); + + auto bits = bitwise_cast(dub); + bits = THRIFT_htolell(bits); + trans_->write((uint8_t*)&bits, 8); + return 8; +} + +/** + * Write a string to the wire with a varint size preceding. + */ +template +uint32_t TCompactProtocolT::writeString(const std::string& str) { + return writeBinary(str); +} + +template +uint32_t TCompactProtocolT::writeBinary(const std::string& str) { + if(str.size() > (std::numeric_limits::max)()) + throw TProtocolException(TProtocolException::SIZE_LIMIT); + auto ssize = static_cast(str.size()); + uint32_t wsize = writeVarint32(ssize) ; + // checking ssize + wsize > uint_max, but we don't want to overflow while checking for overflows. + // transforming the check to ssize > uint_max - wsize + if(ssize > (std::numeric_limits::max)() - wsize) + throw TProtocolException(TProtocolException::SIZE_LIMIT); + wsize += ssize; + trans_->write((uint8_t*)str.data(), ssize); + return wsize; +} + +// +// Internal Writing methods +// + +/** + * The workhorse of writeFieldBegin. It has the option of doing a + * 'type override' of the type header. This is used specifically in the + * boolean field case. + */ +template +int32_t TCompactProtocolT::writeFieldBeginInternal( + const char* name, + const TType fieldType, + const int16_t fieldId, + int8_t typeOverride) { + (void) name; + uint32_t wsize = 0; + + // if there's a type override, use that. + int8_t typeToWrite = (typeOverride == -1 ? getCompactType(fieldType) : typeOverride); + + // check if we can use delta encoding for the field id + if (fieldId > lastFieldId_ && fieldId - lastFieldId_ <= 15) { + // write them together + wsize += writeByte(static_cast((fieldId - lastFieldId_) + << 4 | typeToWrite)); + } else { + // write them separate + wsize += writeByte(typeToWrite); + wsize += writeI16(fieldId); + } + + lastFieldId_ = fieldId; + return wsize; +} + +/** + * Abstract method for writing the start of lists and sets. List and sets on + * the wire differ only by the type indicator. + */ +template +uint32_t TCompactProtocolT::writeCollectionBegin(const TType elemType, + int32_t size) { + uint32_t wsize = 0; + if (size <= 14) { + wsize += writeByte(static_cast(size + << 4 | getCompactType(elemType))); + } else { + wsize += writeByte(0xf0 | getCompactType(elemType)); + wsize += writeVarint32(size); + } + return wsize; +} + +/** + * Write an i32 as a varint. Results in 1-5 bytes on the wire. + */ +template +uint32_t TCompactProtocolT::writeVarint32(uint32_t n) { + uint8_t buf[5]; + uint32_t wsize = 0; + + while (true) { + if ((n & ~0x7F) == 0) { + buf[wsize++] = (int8_t)n; + break; + } else { + buf[wsize++] = (int8_t)((n & 0x7F) | 0x80); + n >>= 7; + } + } + trans_->write(buf, wsize); + return wsize; +} + +/** + * Write an i64 as a varint. Results in 1-10 bytes on the wire. + */ +template +uint32_t TCompactProtocolT::writeVarint64(uint64_t n) { + uint8_t buf[10]; + uint32_t wsize = 0; + + while (true) { + if ((n & ~0x7FL) == 0) { + buf[wsize++] = (int8_t)n; + break; + } else { + buf[wsize++] = (int8_t)((n & 0x7F) | 0x80); + n >>= 7; + } + } + trans_->write(buf, wsize); + return wsize; +} + +/** + * Convert l into a zigzag long. This allows negative numbers to be + * represented compactly as a varint. + */ +template +uint64_t TCompactProtocolT::i64ToZigzag(const int64_t l) { + return (static_cast(l) << 1) ^ (l >> 63); +} + +/** + * Convert n into a zigzag int. This allows negative numbers to be + * represented compactly as a varint. + */ +template +uint32_t TCompactProtocolT::i32ToZigzag(const int32_t n) { + return (static_cast(n) << 1) ^ (n >> 31); +} + +/** + * Given a TType value, find the appropriate detail::compact::Types value + */ +template +int8_t TCompactProtocolT::getCompactType(const TType ttype) { + return detail::compact::TTypeToCType[ttype]; +} + +// +// Reading Methods +// + +/** + * Read a message header. + */ +template +uint32_t TCompactProtocolT::readMessageBegin( + std::string& name, + TMessageType& messageType, + int32_t& seqid) { + uint32_t rsize = 0; + int8_t protocolId; + int8_t versionAndType; + int8_t version; + + rsize += readByte(protocolId); + if (protocolId != PROTOCOL_ID) { + throw TProtocolException(TProtocolException::BAD_VERSION, "Bad protocol identifier"); + } + + rsize += readByte(versionAndType); + version = (int8_t)(versionAndType & VERSION_MASK); + if (version != VERSION_N) { + throw TProtocolException(TProtocolException::BAD_VERSION, "Bad protocol version"); + } + + messageType = (TMessageType)((versionAndType >> TYPE_SHIFT_AMOUNT) & TYPE_BITS); + rsize += readVarint32(seqid); + rsize += readString(name); + + return rsize; +} + +/** + * Read a struct begin. There's nothing on the wire for this, but it is our + * opportunity to push a new struct begin marker on the field stack. + */ +template +uint32_t TCompactProtocolT::readStructBegin(std::string& name) { + name = ""; + lastField_.push(lastFieldId_); + lastFieldId_ = 0; + return 0; +} + +/** + * Doesn't actually consume any wire data, just removes the last field for + * this struct from the field stack. + */ +template +uint32_t TCompactProtocolT::readStructEnd() { + lastFieldId_ = lastField_.top(); + lastField_.pop(); + return 0; +} + +/** + * Read a field header off the wire. + */ +template +uint32_t TCompactProtocolT::readFieldBegin(std::string& name, + TType& fieldType, + int16_t& fieldId) { + (void) name; + uint32_t rsize = 0; + int8_t byte; + int8_t type; + + rsize += readByte(byte); + type = (byte & 0x0f); + + // if it's a stop, then we can return immediately, as the struct is over. + if (type == T_STOP) { + fieldType = T_STOP; + fieldId = 0; + return rsize; + } + + // mask off the 4 MSB of the type header. it could contain a field id delta. + auto modifier = (int16_t)(((uint8_t)byte & 0xf0) >> 4); + if (modifier == 0) { + // not a delta, look ahead for the zigzag varint field id. + rsize += readI16(fieldId); + } else { + fieldId = (int16_t)(lastFieldId_ + modifier); + } + fieldType = getTType(type); + + // if this happens to be a boolean field, the value is encoded in the type + if (type == detail::compact::CT_BOOLEAN_TRUE || + type == detail::compact::CT_BOOLEAN_FALSE) { + // save the boolean value in a special instance variable. + boolValue_.hasBoolValue = true; + boolValue_.boolValue = + (type == detail::compact::CT_BOOLEAN_TRUE ? true : false); + } + + // push the new field onto the field stack so we can keep the deltas going. + lastFieldId_ = fieldId; + return rsize; +} + +/** + * Read a map header off the wire. If the size is zero, skip reading the key + * and value type. This means that 0-length maps will yield TMaps without the + * "correct" types. + */ +template +uint32_t TCompactProtocolT::readMapBegin(TType& keyType, + TType& valType, + uint32_t& size) { + uint32_t rsize = 0; + int8_t kvType = 0; + int32_t msize = 0; + + rsize += readVarint32(msize); + if (msize != 0) + rsize += readByte(kvType); + + if (msize < 0) { + throw TProtocolException(TProtocolException::NEGATIVE_SIZE); + } else if (container_limit_ && msize > container_limit_) { + throw TProtocolException(TProtocolException::SIZE_LIMIT); + } + + keyType = getTType((int8_t)((uint8_t)kvType >> 4)); + valType = getTType((int8_t)((uint8_t)kvType & 0xf)); + size = (uint32_t)msize; + + return rsize; +} + +/** + * Read a list header off the wire. If the list size is 0-14, the size will + * be packed into the element type header. If it's a longer list, the 4 MSB + * of the element type header will be 0xF, and a varint will follow with the + * true size. + */ +template +uint32_t TCompactProtocolT::readListBegin(TType& elemType, + uint32_t& size) { + int8_t size_and_type; + uint32_t rsize = 0; + int32_t lsize; + + rsize += readByte(size_and_type); + + lsize = ((uint8_t)size_and_type >> 4) & 0x0f; + if (lsize == 15) { + rsize += readVarint32(lsize); + } + + if (lsize < 0) { + throw TProtocolException(TProtocolException::NEGATIVE_SIZE); + } else if (container_limit_ && lsize > container_limit_) { + throw TProtocolException(TProtocolException::SIZE_LIMIT); + } + + elemType = getTType((int8_t)(size_and_type & 0x0f)); + size = (uint32_t)lsize; + + return rsize; +} + +/** + * Read a set header off the wire. If the set size is 0-14, the size will + * be packed into the element type header. If it's a longer set, the 4 MSB + * of the element type header will be 0xF, and a varint will follow with the + * true size. + */ +template +uint32_t TCompactProtocolT::readSetBegin(TType& elemType, + uint32_t& size) { + return readListBegin(elemType, size); +} + +/** + * Read a boolean off the wire. If this is a boolean field, the value should + * already have been read during readFieldBegin, so we'll just consume the + * pre-stored value. Otherwise, read a byte. + */ +template +uint32_t TCompactProtocolT::readBool(bool& value) { + if (boolValue_.hasBoolValue == true) { + value = boolValue_.boolValue; + boolValue_.hasBoolValue = false; + return 0; + } else { + int8_t val; + readByte(val); + value = (val == detail::compact::CT_BOOLEAN_TRUE); + return 1; + } +} + +/** + * Read a single byte off the wire. Nothing interesting here. + */ +template +uint32_t TCompactProtocolT::readByte(int8_t& byte) { + uint8_t b[1]; + trans_->readAll(b, 1); + byte = *(int8_t*)b; + return 1; +} + +/** + * Read an i16 from the wire as a zigzag varint. + */ +template +uint32_t TCompactProtocolT::readI16(int16_t& i16) { + int32_t value; + uint32_t rsize = readVarint32(value); + i16 = (int16_t)zigzagToI32(value); + return rsize; +} + +/** + * Read an i32 from the wire as a zigzag varint. + */ +template +uint32_t TCompactProtocolT::readI32(int32_t& i32) { + int32_t value; + uint32_t rsize = readVarint32(value); + i32 = zigzagToI32(value); + return rsize; +} + +/** + * Read an i64 from the wire as a zigzag varint. + */ +template +uint32_t TCompactProtocolT::readI64(int64_t& i64) { + int64_t value; + uint32_t rsize = readVarint64(value); + i64 = zigzagToI64(value); + return rsize; +} + +/** + * No magic here - just read a double off the wire. + */ +template +uint32_t TCompactProtocolT::readDouble(double& dub) { + static_assert(sizeof(double) == sizeof(uint64_t), "sizeof(double) == sizeof(uint64_t)"); + static_assert(std::numeric_limits::is_iec559, "std::numeric_limits::is_iec559"); + + union { + uint64_t bits; + uint8_t b[8]; + } u; + trans_->readAll(u.b, 8); + u.bits = THRIFT_letohll(u.bits); + dub = bitwise_cast(u.bits); + return 8; +} + +template +uint32_t TCompactProtocolT::readString(std::string& str) { + return readBinary(str); +} + +/** + * Read a byte[] from the wire. + */ +template +uint32_t TCompactProtocolT::readBinary(std::string& str) { + int32_t rsize = 0; + int32_t size; + + rsize += readVarint32(size); + // Catch empty string case + if (size == 0) { + str = ""; + return rsize; + } + + // Catch error cases + if (size < 0) { + throw TProtocolException(TProtocolException::NEGATIVE_SIZE); + } + if (string_limit_ > 0 && size > string_limit_) { + throw TProtocolException(TProtocolException::SIZE_LIMIT); + } + + // Use the heap here to prevent stack overflow for v. large strings + if (size > string_buf_size_ || string_buf_ == nullptr) { + void* new_string_buf = std::realloc(string_buf_, (uint32_t)size); + if (new_string_buf == nullptr) { + throw std::bad_alloc(); + } + string_buf_ = (uint8_t*)new_string_buf; + string_buf_size_ = size; + } + trans_->readAll(string_buf_, size); + str.assign((char*)string_buf_, size); + + return rsize + (uint32_t)size; +} + +/** + * Read an i32 from the wire as a varint. The MSB of each byte is set + * if there is another byte to follow. This can read up to 5 bytes. + */ +template +uint32_t TCompactProtocolT::readVarint32(int32_t& i32) { + int64_t val; + uint32_t rsize = readVarint64(val); + i32 = (int32_t)val; + return rsize; +} + +/** + * Read an i64 from the wire as a proper varint. The MSB of each byte is set + * if there is another byte to follow. This can read up to 10 bytes. + */ +template +uint32_t TCompactProtocolT::readVarint64(int64_t& i64) { + uint32_t rsize = 0; + uint64_t val = 0; + int shift = 0; + uint8_t buf[10]; // 64 bits / (7 bits/byte) = 10 bytes. + uint32_t buf_size = sizeof(buf); + const uint8_t* borrowed = trans_->borrow(buf, &buf_size); + + // Fast path. + if (borrowed != nullptr) { + while (true) { + uint8_t byte = borrowed[rsize]; + rsize++; + val |= (uint64_t)(byte & 0x7f) << shift; + shift += 7; + if (!(byte & 0x80)) { + i64 = val; + trans_->consume(rsize); + return rsize; + } + // Have to check for invalid data so we don't crash. + if (UNLIKELY(rsize == sizeof(buf))) { + throw TProtocolException(TProtocolException::INVALID_DATA, "Variable-length int over 10 bytes."); + } + } + } + + // Slow path. + else { + while (true) { + uint8_t byte; + rsize += trans_->readAll(&byte, 1); + val |= (uint64_t)(byte & 0x7f) << shift; + shift += 7; + if (!(byte & 0x80)) { + i64 = val; + return rsize; + } + // Might as well check for invalid data on the slow path too. + if (UNLIKELY(rsize >= sizeof(buf))) { + throw TProtocolException(TProtocolException::INVALID_DATA, "Variable-length int over 10 bytes."); + } + } + } +} + +/** + * Convert from zigzag int to int. + */ +template +int32_t TCompactProtocolT::zigzagToI32(uint32_t n) { + return (n >> 1) ^ static_cast(-static_cast(n & 1)); +} + +/** + * Convert from zigzag long to long. + */ +template +int64_t TCompactProtocolT::zigzagToI64(uint64_t n) { + return (n >> 1) ^ static_cast(-static_cast(n & 1)); +} + +template +TType TCompactProtocolT::getTType(int8_t type) { + switch (type) { + case T_STOP: + return T_STOP; + case detail::compact::CT_BOOLEAN_FALSE: + case detail::compact::CT_BOOLEAN_TRUE: + return T_BOOL; + case detail::compact::CT_BYTE: + return T_BYTE; + case detail::compact::CT_I16: + return T_I16; + case detail::compact::CT_I32: + return T_I32; + case detail::compact::CT_I64: + return T_I64; + case detail::compact::CT_DOUBLE: + return T_DOUBLE; + case detail::compact::CT_BINARY: + return T_STRING; + case detail::compact::CT_LIST: + return T_LIST; + case detail::compact::CT_SET: + return T_SET; + case detail::compact::CT_MAP: + return T_MAP; + case detail::compact::CT_STRUCT: + return T_STRUCT; + default: + throw TException(std::string("don't know what type: ") + (char)type); + } +} + +}}} // apache::thrift::protocol + +#endif // _THRIFT_PROTOCOL_TCOMPACTPROTOCOL_TCC_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TDebugProtocol.cpp b/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TDebugProtocol.cpp new file mode 100644 index 000000000..0e6d4a2aa --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TDebugProtocol.cpp @@ -0,0 +1,393 @@ +/* + * 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. + */ + +#include + +#include +#include +#include +#include +#include + +using std::string; + +static string byte_to_hex(const uint8_t byte) { + char buf[3]; + int ret = std::sprintf(buf, "%02x", (int)byte); + THRIFT_UNUSED_VARIABLE(ret); + assert(ret == 2); + assert(buf[2] == '\0'); + return buf; +} + +namespace apache { +namespace thrift { +namespace protocol { + +string TDebugProtocol::fieldTypeName(TType type) { + switch (type) { + case T_STOP: + return "stop"; + case T_VOID: + return "void"; + case T_BOOL: + return "bool"; + case T_BYTE: + return "byte"; + case T_I16: + return "i16"; + case T_I32: + return "i32"; + case T_U64: + return "u64"; + case T_I64: + return "i64"; + case T_DOUBLE: + return "double"; + case T_STRING: + return "string"; + case T_STRUCT: + return "struct"; + case T_MAP: + return "map"; + case T_SET: + return "set"; + case T_LIST: + return "list"; + case T_UTF8: + return "utf8"; + case T_UTF16: + return "utf16"; + default: + return "unknown"; + } +} + +void TDebugProtocol::indentUp() { + indent_str_ += string(indent_inc, ' '); +} + +void TDebugProtocol::indentDown() { + if (indent_str_.length() < (string::size_type)indent_inc) { + throw TProtocolException(TProtocolException::INVALID_DATA); + } + indent_str_.erase(indent_str_.length() - indent_inc); +} + +uint32_t TDebugProtocol::writePlain(const string& str) { + if (str.length() > (std::numeric_limits::max)()) + throw TProtocolException(TProtocolException::SIZE_LIMIT); + trans_->write((uint8_t*)str.data(), static_cast(str.length())); + return static_cast(str.length()); +} + +uint32_t TDebugProtocol::writeIndented(const string& str) { + if (str.length() > (std::numeric_limits::max)()) + throw TProtocolException(TProtocolException::SIZE_LIMIT); + if (indent_str_.length() > (std::numeric_limits::max)()) + throw TProtocolException(TProtocolException::SIZE_LIMIT); + uint64_t total_len = indent_str_.length() + str.length(); + if (total_len > (std::numeric_limits::max)()) + throw TProtocolException(TProtocolException::SIZE_LIMIT); + trans_->write((uint8_t*)indent_str_.data(), static_cast(indent_str_.length())); + trans_->write((uint8_t*)str.data(), static_cast(str.length())); + return static_cast(indent_str_.length() + str.length()); +} + +uint32_t TDebugProtocol::startItem() { + uint32_t size; + + switch (write_state_.back()) { + case UNINIT: + // XXX figure out what to do here. + // throw TProtocolException(TProtocolException::INVALID_DATA); + // return writeIndented(str); + return 0; + case STRUCT: + return 0; + case SET: + return writeIndented(""); + case MAP_KEY: + return writeIndented(""); + case MAP_VALUE: + return writePlain(" -> "); + case LIST: + size = writeIndented("[" + to_string(list_idx_.back()) + "] = "); + list_idx_.back()++; + return size; + default: + throw std::logic_error("Invalid enum value."); + } +} + +uint32_t TDebugProtocol::endItem() { + // uint32_t size; + + switch (write_state_.back()) { + case UNINIT: + // XXX figure out what to do here. + // throw TProtocolException(TProtocolException::INVALID_DATA); + // return writeIndented(str); + return 0; + case STRUCT: + return writePlain(",\n"); + case SET: + return writePlain(",\n"); + case MAP_KEY: + write_state_.back() = MAP_VALUE; + return 0; + case MAP_VALUE: + write_state_.back() = MAP_KEY; + return writePlain(",\n"); + case LIST: + return writePlain(",\n"); + default: + throw std::logic_error("Invalid enum value."); + } +} + +uint32_t TDebugProtocol::writeItem(const std::string& str) { + uint32_t size = 0; + size += startItem(); + size += writePlain(str); + size += endItem(); + return size; +} + +uint32_t TDebugProtocol::writeMessageBegin(const std::string& name, + const TMessageType messageType, + const int32_t seqid) { + (void)seqid; + string mtype; + switch (messageType) { + case T_CALL: + mtype = "call"; + break; + case T_REPLY: + mtype = "reply"; + break; + case T_EXCEPTION: + mtype = "exn"; + break; + case T_ONEWAY: + mtype = "oneway"; + break; + } + + uint32_t size = writeIndented("(" + mtype + ") " + name + "("); + indentUp(); + return size; +} + +uint32_t TDebugProtocol::writeMessageEnd() { + indentDown(); + return writeIndented(")\n"); +} + +uint32_t TDebugProtocol::writeStructBegin(const char* name) { + uint32_t size = 0; + size += startItem(); + size += writePlain(string(name) + " {\n"); + indentUp(); + write_state_.push_back(STRUCT); + return size; +} + +uint32_t TDebugProtocol::writeStructEnd() { + indentDown(); + write_state_.pop_back(); + uint32_t size = 0; + size += writeIndented("}"); + size += endItem(); + return size; +} + +uint32_t TDebugProtocol::writeFieldBegin(const char* name, + const TType fieldType, + const int16_t fieldId) { + // sprintf(id_str, "%02d", fieldId); + string id_str = to_string(fieldId); + if (id_str.length() == 1) + id_str = '0' + id_str; + + return writeIndented(id_str + ": " + name + " (" + fieldTypeName(fieldType) + ") = "); +} + +uint32_t TDebugProtocol::writeFieldEnd() { + assert(write_state_.back() == STRUCT); + return 0; +} + +uint32_t TDebugProtocol::writeFieldStop() { + return 0; + // writeIndented("***STOP***\n"); +} + +uint32_t TDebugProtocol::writeMapBegin(const TType keyType, + const TType valType, + const uint32_t size) { + // TODO(dreiss): Optimize short maps? + uint32_t bsize = 0; + bsize += startItem(); + bsize += writePlain( + "map<" + fieldTypeName(keyType) + "," + fieldTypeName(valType) + ">" + "[" + to_string(size) + "] {\n"); + indentUp(); + write_state_.push_back(MAP_KEY); + return bsize; +} + +uint32_t TDebugProtocol::writeMapEnd() { + indentDown(); + write_state_.pop_back(); + uint32_t size = 0; + size += writeIndented("}"); + size += endItem(); + return size; +} + +uint32_t TDebugProtocol::writeListBegin(const TType elemType, const uint32_t size) { + // TODO(dreiss): Optimize short arrays. + uint32_t bsize = 0; + bsize += startItem(); + bsize += writePlain( + "list<" + fieldTypeName(elemType) + ">" + "[" + to_string(size) + "] {\n"); + indentUp(); + write_state_.push_back(LIST); + list_idx_.push_back(0); + return bsize; +} + +uint32_t TDebugProtocol::writeListEnd() { + indentDown(); + write_state_.pop_back(); + list_idx_.pop_back(); + uint32_t size = 0; + size += writeIndented("}"); + size += endItem(); + return size; +} + +uint32_t TDebugProtocol::writeSetBegin(const TType elemType, const uint32_t size) { + // TODO(dreiss): Optimize short sets. + uint32_t bsize = 0; + bsize += startItem(); + bsize += writePlain( + "set<" + fieldTypeName(elemType) + ">" + "[" + to_string(size) + "] {\n"); + indentUp(); + write_state_.push_back(SET); + return bsize; +} + +uint32_t TDebugProtocol::writeSetEnd() { + indentDown(); + write_state_.pop_back(); + uint32_t size = 0; + size += writeIndented("}"); + size += endItem(); + return size; +} + +uint32_t TDebugProtocol::writeBool(const bool value) { + return writeItem(value ? "true" : "false"); +} + +uint32_t TDebugProtocol::writeByte(const int8_t byte) { + return writeItem("0x" + byte_to_hex(byte)); +} + +uint32_t TDebugProtocol::writeI16(const int16_t i16) { + return writeItem(to_string(i16)); +} + +uint32_t TDebugProtocol::writeI32(const int32_t i32) { + return writeItem(to_string(i32)); +} + +uint32_t TDebugProtocol::writeI64(const int64_t i64) { + return writeItem(to_string(i64)); +} + +uint32_t TDebugProtocol::writeDouble(const double dub) { + return writeItem(to_string(dub)); +} + +uint32_t TDebugProtocol::writeString(const string& str) { + // XXX Raw/UTF-8? + + string to_show = str; + if (to_show.length() > (string::size_type)string_limit_) { + to_show = str.substr(0, string_prefix_size_); + to_show += "[...](" + to_string(str.length()) + ")"; + } + + string output = "\""; + + for (string::const_iterator it = to_show.begin(); it != to_show.end(); ++it) { + if (*it == '\\') { + output += "\\\\"; + } else if (*it == '"') { + output += "\\\""; + // passing characters <0 to std::isprint causes asserts. isprint takes an + // int, so we need to be careful of sign extension + } else if (std::isprint((unsigned char)*it)) { + output += *it; + } else { + switch (*it) { + case '\a': + output += "\\a"; + break; + case '\b': + output += "\\b"; + break; + case '\f': + output += "\\f"; + break; + case '\n': + output += "\\n"; + break; + case '\r': + output += "\\r"; + break; + case '\t': + output += "\\t"; + break; + case '\v': + output += "\\v"; + break; + default: + output += "\\x"; + output += byte_to_hex(*it); + } + } + } + + output += '\"'; + return writeItem(output); +} + +uint32_t TDebugProtocol::writeBinary(const string& str) { + // XXX Hex? + return TDebugProtocol::writeString(str); +} +} +} +} // apache::thrift::protocol diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TDebugProtocol.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TDebugProtocol.h new file mode 100644 index 000000000..41bb0d4ec --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TDebugProtocol.h @@ -0,0 +1,204 @@ +/* + * 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. + */ + +#ifndef _THRIFT_PROTOCOL_TDEBUGPROTOCOL_H_ +#define _THRIFT_PROTOCOL_TDEBUGPROTOCOL_H_ 1 + +#include + +#include + +namespace apache { +namespace thrift { +namespace protocol { + +/* + +!!! EXPERIMENTAL CODE !!! + +This protocol is very much a work in progress. +It doesn't handle many cases properly. +It throws exceptions in many cases. +It probably segfaults in many cases. +Bug reports and feature requests are welcome. +Complaints are not. :R + +*/ + +/** + * Protocol that prints the payload in a nice human-readable format. + * Reading from this protocol is not supported. + * + */ +class TDebugProtocol : public TVirtualProtocol { +private: + enum write_state_t { UNINIT, STRUCT, LIST, SET, MAP_KEY, MAP_VALUE }; + +public: + TDebugProtocol(std::shared_ptr trans) + : TVirtualProtocol(trans), + trans_(trans.get()), + string_limit_(DEFAULT_STRING_LIMIT), + string_prefix_size_(DEFAULT_STRING_PREFIX_SIZE) { + write_state_.push_back(UNINIT); + } + + static const int32_t DEFAULT_STRING_LIMIT = 256; + static const int32_t DEFAULT_STRING_PREFIX_SIZE = 16; + + void setStringSizeLimit(int32_t string_limit) { string_limit_ = string_limit; } + + void setStringPrefixSize(int32_t string_prefix_size) { string_prefix_size_ = string_prefix_size; } + + uint32_t writeMessageBegin(const std::string& name, + const TMessageType messageType, + const int32_t seqid); + + uint32_t writeMessageEnd(); + + uint32_t writeStructBegin(const char* name); + + uint32_t writeStructEnd(); + + uint32_t writeFieldBegin(const char* name, const TType fieldType, const int16_t fieldId); + + uint32_t writeFieldEnd(); + + uint32_t writeFieldStop(); + + uint32_t writeMapBegin(const TType keyType, const TType valType, const uint32_t size); + + uint32_t writeMapEnd(); + + uint32_t writeListBegin(const TType elemType, const uint32_t size); + + uint32_t writeListEnd(); + + uint32_t writeSetBegin(const TType elemType, const uint32_t size); + + uint32_t writeSetEnd(); + + uint32_t writeBool(const bool value); + + uint32_t writeByte(const int8_t byte); + + uint32_t writeI16(const int16_t i16); + + uint32_t writeI32(const int32_t i32); + + uint32_t writeI64(const int64_t i64); + + uint32_t writeDouble(const double dub); + + uint32_t writeString(const std::string& str); + + uint32_t writeBinary(const std::string& str); + +private: + void indentUp(); + void indentDown(); + uint32_t writePlain(const std::string& str); + uint32_t writeIndented(const std::string& str); + uint32_t startItem(); + uint32_t endItem(); + uint32_t writeItem(const std::string& str); + + static std::string fieldTypeName(TType type); + + TTransport* trans_; + + int32_t string_limit_; + int32_t string_prefix_size_; + + std::string indent_str_; + static const int indent_inc = 2; + + std::vector write_state_; + std::vector list_idx_; +}; + +/** + * Constructs debug protocol handlers + */ +class TDebugProtocolFactory : public TProtocolFactory { +public: + TDebugProtocolFactory() = default; + ~TDebugProtocolFactory() override = default; + + std::shared_ptr getProtocol(std::shared_ptr trans) override { + return std::shared_ptr(new TDebugProtocol(trans)); + } +}; +} +} +} // apache::thrift::protocol + +// TODO(dreiss): Move (part of) ThriftDebugString into a .cpp file and remove this. +#include + +namespace apache { +namespace thrift { + +template +std::string ThriftDebugString(const ThriftStruct& ts) { + using namespace apache::thrift::transport; + using namespace apache::thrift::protocol; + auto* buffer = new TMemoryBuffer; + std::shared_ptr trans(buffer); + TDebugProtocol protocol(trans); + + ts.write(&protocol); + + uint8_t* buf; + uint32_t size; + buffer->getBuffer(&buf, &size); + return std::string((char*)buf, (unsigned int)size); +} + +// TODO(dreiss): This is badly broken. Don't use it unless you are me. +#if 0 +template +std::string DebugString(const std::vector& vec) { + using namespace apache::thrift::transport; + using namespace apache::thrift::protocol; + TMemoryBuffer* buffer = new TMemoryBuffer; + std::shared_ptr trans(buffer); + TDebugProtocol protocol(trans); + + // I am gross! + protocol.writeStructBegin("SomeRandomVector"); + + // TODO: Fix this with a trait. + protocol.writeListBegin((TType)99, vec.size()); + typename std::vector::const_iterator it; + for (it = vec.begin(); it != vec.end(); ++it) { + it->write(&protocol); + } + protocol.writeListEnd(); + + uint8_t* buf; + uint32_t size; + buffer->getBuffer(&buf, &size); + return std::string((char*)buf, (unsigned int)size); +} +#endif // 0 +} +} // apache::thrift + +#endif // #ifndef _THRIFT_PROTOCOL_TDEBUGPROTOCOL_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/THeaderProtocol.cpp b/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/THeaderProtocol.cpp new file mode 100644 index 000000000..6242e30b8 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/THeaderProtocol.cpp @@ -0,0 +1,253 @@ +/* + * 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. + */ +#ifndef THRIFT_PROTOCOL_THEADERPROTOCOL_CPP_ +#define THRIFT_PROTOCOL_THEADERPROTOCOL_CPP_ 1 + +#include +#include +#include +#include + +#include + +#include + +namespace apache { +namespace thrift { +namespace protocol { + +void THeaderProtocol::resetProtocol() { + if (proto_ && protoId_ == trans_->getProtocolId()) { + return; + } + + protoId_ = trans_->getProtocolId(); + + switch (protoId_) { + case T_BINARY_PROTOCOL: + proto_ = std::make_shared >(trans_); + break; + + case T_COMPACT_PROTOCOL: + proto_ = std::make_shared >(trans_); + break; + + default: + throw TApplicationException(TApplicationException::INVALID_PROTOCOL, + "Unknown protocol requested"); + } +} + +uint32_t THeaderProtocol::writeMessageBegin(const std::string& name, + const TMessageType messageType, + const int32_t seqId) { + resetProtocol(); // Reset in case we changed protocols + trans_->setSequenceNumber(seqId); + return proto_->writeMessageBegin(name, messageType, seqId); +} + +uint32_t THeaderProtocol::writeMessageEnd() { + return proto_->writeMessageEnd(); +} + +uint32_t THeaderProtocol::writeStructBegin(const char* name) { + return proto_->writeStructBegin(name); +} + +uint32_t THeaderProtocol::writeStructEnd() { + return proto_->writeStructEnd(); +} + +uint32_t THeaderProtocol::writeFieldBegin(const char* name, + const TType fieldType, + const int16_t fieldId) { + return proto_->writeFieldBegin(name, fieldType, fieldId); +} + +uint32_t THeaderProtocol::writeFieldEnd() { + return proto_->writeFieldEnd(); +} + +uint32_t THeaderProtocol::writeFieldStop() { + return proto_->writeFieldStop(); +} + +uint32_t THeaderProtocol::writeMapBegin(const TType keyType, + const TType valType, + const uint32_t size) { + return proto_->writeMapBegin(keyType, valType, size); +} + +uint32_t THeaderProtocol::writeMapEnd() { + return proto_->writeMapEnd(); +} + +uint32_t THeaderProtocol::writeListBegin(const TType elemType, const uint32_t size) { + return proto_->writeListBegin(elemType, size); +} + +uint32_t THeaderProtocol::writeListEnd() { + return proto_->writeListEnd(); +} + +uint32_t THeaderProtocol::writeSetBegin(const TType elemType, const uint32_t size) { + return proto_->writeSetBegin(elemType, size); +} + +uint32_t THeaderProtocol::writeSetEnd() { + return proto_->writeSetEnd(); +} + +uint32_t THeaderProtocol::writeBool(const bool value) { + return proto_->writeBool(value); +} + +uint32_t THeaderProtocol::writeByte(const int8_t byte) { + return proto_->writeByte(byte); +} + +uint32_t THeaderProtocol::writeI16(const int16_t i16) { + return proto_->writeI16(i16); +} + +uint32_t THeaderProtocol::writeI32(const int32_t i32) { + return proto_->writeI32(i32); +} + +uint32_t THeaderProtocol::writeI64(const int64_t i64) { + return proto_->writeI64(i64); +} + +uint32_t THeaderProtocol::writeDouble(const double dub) { + return proto_->writeDouble(dub); +} + +uint32_t THeaderProtocol::writeString(const std::string& str) { + return proto_->writeString(str); +} + +uint32_t THeaderProtocol::writeBinary(const std::string& str) { + return proto_->writeBinary(str); +} + +/** + * Reading functions + */ + +uint32_t THeaderProtocol::readMessageBegin(std::string& name, + TMessageType& messageType, + int32_t& seqId) { + // Read the next frame, and change protocols if needed + try { + trans_->resetProtocol(); + resetProtocol(); + } catch (const TApplicationException& ex) { + writeMessageBegin("", T_EXCEPTION, 0); + ex.write((TProtocol*)this); + writeMessageEnd(); + trans_->flush(); + + // The framing is still good, but we don't know about this protocol. + // In the future, this could be made a client-side only error if + // connection pooling is used. + throw ex; + } + return proto_->readMessageBegin(name, messageType, seqId); +} + +uint32_t THeaderProtocol::readMessageEnd() { + return proto_->readMessageEnd(); +} + +uint32_t THeaderProtocol::readStructBegin(std::string& name) { + return proto_->readStructBegin(name); +} + +uint32_t THeaderProtocol::readStructEnd() { + return proto_->readStructEnd(); +} + +uint32_t THeaderProtocol::readFieldBegin(std::string& name, TType& fieldType, int16_t& fieldId) { + return proto_->readFieldBegin(name, fieldType, fieldId); +} + +uint32_t THeaderProtocol::readFieldEnd() { + return proto_->readFieldEnd(); +} + +uint32_t THeaderProtocol::readMapBegin(TType& keyType, TType& valType, uint32_t& size) { + return proto_->readMapBegin(keyType, valType, size); +} + +uint32_t THeaderProtocol::readMapEnd() { + return proto_->readMapEnd(); +} + +uint32_t THeaderProtocol::readListBegin(TType& elemType, uint32_t& size) { + return proto_->readListBegin(elemType, size); +} + +uint32_t THeaderProtocol::readListEnd() { + return proto_->readListEnd(); +} + +uint32_t THeaderProtocol::readSetBegin(TType& elemType, uint32_t& size) { + return proto_->readSetBegin(elemType, size); +} + +uint32_t THeaderProtocol::readSetEnd() { + return proto_->readSetEnd(); +} + +uint32_t THeaderProtocol::readBool(bool& value) { + return proto_->readBool(value); +} + +uint32_t THeaderProtocol::readByte(int8_t& byte) { + return proto_->readByte(byte); +} + +uint32_t THeaderProtocol::readI16(int16_t& i16) { + return proto_->readI16(i16); +} + +uint32_t THeaderProtocol::readI32(int32_t& i32) { + return proto_->readI32(i32); +} + +uint32_t THeaderProtocol::readI64(int64_t& i64) { + return proto_->readI64(i64); +} + +uint32_t THeaderProtocol::readDouble(double& dub) { + return proto_->readDouble(dub); +} + +uint32_t THeaderProtocol::readString(std::string& str) { + return proto_->readString(str); +} + +uint32_t THeaderProtocol::readBinary(std::string& binary) { + return proto_->readBinary(binary); +} +} +} +} // apache::thrift::protocol + +#endif // #ifndef THRIFT_PROTOCOL_THEADERPROTOCOL_CPP_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/THeaderProtocol.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/THeaderProtocol.h new file mode 100644 index 000000000..0d5018596 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/THeaderProtocol.h @@ -0,0 +1,210 @@ +/* + * 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. + */ + +#ifndef THRIFT_PROTOCOL_THEADERPROTOCOL_H_ +#define THRIFT_PROTOCOL_THEADERPROTOCOL_H_ 1 + +#include +#include +#include +#include + +#include + +using apache::thrift::transport::THeaderTransport; + +namespace apache { +namespace thrift { +namespace protocol { + +/** + * The header protocol for thrift. Reads unframed, framed, header format, + * and http + * + */ +class THeaderProtocol : public TVirtualProtocol { +protected: +public: + void resetProtocol(); + + explicit THeaderProtocol(const std::shared_ptr& trans, + uint16_t protoId = T_COMPACT_PROTOCOL) + : TVirtualProtocol(std::shared_ptr(new THeaderTransport(trans))), + trans_(std::dynamic_pointer_cast(getTransport())), + protoId_(protoId) { + trans_->setProtocolId(protoId); + resetProtocol(); + } + + THeaderProtocol(const std::shared_ptr& inTrans, + const std::shared_ptr& outTrans, + uint16_t protoId = T_COMPACT_PROTOCOL) + : TVirtualProtocol( + std::shared_ptr(new THeaderTransport(inTrans, outTrans))), + trans_(std::dynamic_pointer_cast(getTransport())), + protoId_(protoId) { + trans_->setProtocolId(protoId); + resetProtocol(); + } + + ~THeaderProtocol() override = default; + + /** + * Functions to work with headers by calling into THeaderTransport + */ + void setProtocolId(uint16_t protoId) { + trans_->setProtocolId(protoId); + resetProtocol(); + } + + typedef THeaderTransport::StringToStringMap StringToStringMap; + + // these work with write headers + void setHeader(const std::string& key, const std::string& value) { + trans_->setHeader(key, value); + } + + void clearHeaders() { trans_->clearHeaders(); } + + StringToStringMap& getWriteHeaders() { return trans_->getWriteHeaders(); } + + // these work with read headers + const StringToStringMap& getHeaders() const { return trans_->getHeaders(); } + + /** + * Writing functions. + */ + + /*ol*/ uint32_t writeMessageBegin(const std::string& name, + const TMessageType messageType, + const int32_t seqId); + + /*ol*/ uint32_t writeMessageEnd(); + + uint32_t writeStructBegin(const char* name); + + uint32_t writeStructEnd(); + + uint32_t writeFieldBegin(const char* name, const TType fieldType, const int16_t fieldId); + + uint32_t writeFieldEnd(); + + uint32_t writeFieldStop(); + + uint32_t writeMapBegin(const TType keyType, const TType valType, const uint32_t size); + + uint32_t writeMapEnd(); + + uint32_t writeListBegin(const TType elemType, const uint32_t size); + + uint32_t writeListEnd(); + + uint32_t writeSetBegin(const TType elemType, const uint32_t size); + + uint32_t writeSetEnd(); + + uint32_t writeBool(const bool value); + + uint32_t writeByte(const int8_t byte); + + uint32_t writeI16(const int16_t i16); + + uint32_t writeI32(const int32_t i32); + + uint32_t writeI64(const int64_t i64); + + uint32_t writeDouble(const double dub); + + uint32_t writeString(const std::string& str); + + uint32_t writeBinary(const std::string& str); + + /** + * Reading functions + */ + + /*ol*/ uint32_t readMessageBegin(std::string& name, TMessageType& messageType, int32_t& seqId); + + /*ol*/ uint32_t readMessageEnd(); + + uint32_t readStructBegin(std::string& name); + + uint32_t readStructEnd(); + + uint32_t readFieldBegin(std::string& name, TType& fieldType, int16_t& fieldId); + + uint32_t readFieldEnd(); + + uint32_t readMapBegin(TType& keyType, TType& valType, uint32_t& size); + + uint32_t readMapEnd(); + + uint32_t readListBegin(TType& elemType, uint32_t& size); + + uint32_t readListEnd(); + + uint32_t readSetBegin(TType& elemType, uint32_t& size); + + uint32_t readSetEnd(); + + uint32_t readBool(bool& value); + // Provide the default readBool() implementation for std::vector + using TVirtualProtocol::readBool; + + uint32_t readByte(int8_t& byte); + + uint32_t readI16(int16_t& i16); + + uint32_t readI32(int32_t& i32); + + uint32_t readI64(int64_t& i64); + + uint32_t readDouble(double& dub); + + uint32_t readString(std::string& str); + + uint32_t readBinary(std::string& binary); + +protected: + std::shared_ptr trans_; + + std::shared_ptr proto_; + uint32_t protoId_; +}; + +class THeaderProtocolFactory : public TProtocolFactory { +public: + std::shared_ptr getProtocol(std::shared_ptr trans) override { + auto* headerProtocol + = new THeaderProtocol(trans, trans, T_BINARY_PROTOCOL); + return std::shared_ptr(headerProtocol); + } + + std::shared_ptr getProtocol( + std::shared_ptr inTrans, + std::shared_ptr outTrans) override { + auto* headerProtocol = new THeaderProtocol(inTrans, outTrans, T_BINARY_PROTOCOL); + return std::shared_ptr(headerProtocol); + } +}; +} +} +} // apache::thrift::protocol + +#endif // #ifndef THRIFT_PROTOCOL_THEADERPROTOCOL_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TJSONProtocol.cpp b/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TJSONProtocol.cpp new file mode 100644 index 000000000..28d0da299 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TJSONProtocol.cpp @@ -0,0 +1,1098 @@ +/* + * 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. + */ + +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +using namespace apache::thrift::transport; + +namespace apache { +namespace thrift { +namespace protocol { + +// Static data + +static const uint8_t kJSONObjectStart = '{'; +static const uint8_t kJSONObjectEnd = '}'; +static const uint8_t kJSONArrayStart = '['; +static const uint8_t kJSONArrayEnd = ']'; +static const uint8_t kJSONPairSeparator = ':'; +static const uint8_t kJSONElemSeparator = ','; +static const uint8_t kJSONBackslash = '\\'; +static const uint8_t kJSONStringDelimiter = '"'; +static const uint8_t kJSONEscapeChar = 'u'; + +static const std::string kJSONEscapePrefix("\\u00"); + +static const uint32_t kThriftVersion1 = 1; + +static const std::string kThriftNan("NaN"); +static const std::string kThriftInfinity("Infinity"); +static const std::string kThriftNegativeInfinity("-Infinity"); + +static const std::string kTypeNameBool("tf"); +static const std::string kTypeNameByte("i8"); +static const std::string kTypeNameI16("i16"); +static const std::string kTypeNameI32("i32"); +static const std::string kTypeNameI64("i64"); +static const std::string kTypeNameDouble("dbl"); +static const std::string kTypeNameStruct("rec"); +static const std::string kTypeNameString("str"); +static const std::string kTypeNameMap("map"); +static const std::string kTypeNameList("lst"); +static const std::string kTypeNameSet("set"); + +static const std::string& getTypeNameForTypeID(TType typeID) { + switch (typeID) { + case T_BOOL: + return kTypeNameBool; + case T_BYTE: + return kTypeNameByte; + case T_I16: + return kTypeNameI16; + case T_I32: + return kTypeNameI32; + case T_I64: + return kTypeNameI64; + case T_DOUBLE: + return kTypeNameDouble; + case T_STRING: + return kTypeNameString; + case T_STRUCT: + return kTypeNameStruct; + case T_MAP: + return kTypeNameMap; + case T_SET: + return kTypeNameSet; + case T_LIST: + return kTypeNameList; + default: + throw TProtocolException(TProtocolException::NOT_IMPLEMENTED, "Unrecognized type"); + } +} + +static TType getTypeIDForTypeName(const std::string& name) { + TType result = T_STOP; // Sentinel value + if (name.length() > 1) { + switch (name[0]) { + case 'd': + result = T_DOUBLE; + break; + case 'i': + switch (name[1]) { + case '8': + result = T_BYTE; + break; + case '1': + result = T_I16; + break; + case '3': + result = T_I32; + break; + case '6': + result = T_I64; + break; + } + break; + case 'l': + result = T_LIST; + break; + case 'm': + result = T_MAP; + break; + case 'r': + result = T_STRUCT; + break; + case 's': + if (name[1] == 't') { + result = T_STRING; + } else if (name[1] == 'e') { + result = T_SET; + } + break; + case 't': + result = T_BOOL; + break; + } + } + if (result == T_STOP) { + throw TProtocolException(TProtocolException::NOT_IMPLEMENTED, "Unrecognized type"); + } + return result; +} + +// This table describes the handling for the first 0x30 characters +// 0 : escape using "\u00xx" notation +// 1 : just output index +// : escape using "\" notation +static const uint8_t kJSONCharTable[0x30] = { + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 'b', + 't', + 'n', + 0, + 'f', + 'r', + 0, + 0, // 0 + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, // 1 + 1, + 1, + '"', + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, // 2 +}; + +// This string's characters must match up with the elements in kEscapeCharVals. +// I don't have '/' on this list even though it appears on www.json.org -- +// it is not in the RFC +const static std::string kEscapeChars("\"\\bfnrt"); + +// The elements of this array must match up with the sequence of characters in +// kEscapeChars +const static uint8_t kEscapeCharVals[7] = { + '"', + '\\', + '\b', + '\f', + '\n', + '\r', + '\t', +}; + +// Static helper functions + +// Read 1 character from the transport trans and verify that it is the +// expected character ch. +// Throw a protocol exception if it is not. +static uint32_t readSyntaxChar(TJSONProtocol::LookaheadReader& reader, uint8_t ch) { + uint8_t ch2 = reader.read(); + if (ch2 != ch) { + throw TProtocolException(TProtocolException::INVALID_DATA, + "Expected \'" + std::string((char*)&ch, 1) + "\'; got \'" + + std::string((char*)&ch2, 1) + "\'."); + } + return 1; +} + +// Return the integer value of a hex character ch. +// Throw a protocol exception if the character is not [0-9a-f]. +static uint8_t hexVal(uint8_t ch) { + if ((ch >= '0') && (ch <= '9')) { + return ch - '0'; + } else if ((ch >= 'a') && (ch <= 'f')) { + return ch - 'a' + 10; + } else { + throw TProtocolException(TProtocolException::INVALID_DATA, + "Expected hex val ([0-9a-f]); got \'" + std::string((char*)&ch, 1) + + "\'."); + } +} + +// Return the hex character representing the integer val. The value is masked +// to make sure it is in the correct range. +static uint8_t hexChar(uint8_t val) { + val &= 0x0F; + if (val < 10) { + return val + '0'; + } else { + return val - 10 + 'a'; + } +} + +// Return true if the character ch is in [-+0-9.Ee]; false otherwise +static bool isJSONNumeric(uint8_t ch) { + switch (ch) { + case '+': + case '-': + case '.': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case 'E': + case 'e': + return true; + } + return false; +} + +// Return true if the code unit is high surrogate +static bool isHighSurrogate(uint16_t val) { + return val >= 0xD800 && val <= 0xDBFF; +} + +// Return true if the code unit is low surrogate +static bool isLowSurrogate(uint16_t val) { + return val >= 0xDC00 && val <= 0xDFFF; +} + +/** + * Class to serve as base JSON context and as base class for other context + * implementations + */ +class TJSONContext { + +public: + TJSONContext() = default; + + virtual ~TJSONContext() = default; + + /** + * Write context data to the transport. Default is to do nothing. + */ + virtual uint32_t write(TTransport& trans) { + (void)trans; + return 0; + }; + + /** + * Read context data from the transport. Default is to do nothing. + */ + virtual uint32_t read(TJSONProtocol::LookaheadReader& reader) { + (void)reader; + return 0; + }; + + /** + * Return true if numbers need to be escaped as strings in this context. + * Default behavior is to return false. + */ + virtual bool escapeNum() { return false; } +}; + +// Context class for object member key-value pairs +class JSONPairContext : public TJSONContext { + +public: + JSONPairContext() : first_(true), colon_(true) {} + + uint32_t write(TTransport& trans) override { + if (first_) { + first_ = false; + colon_ = true; + return 0; + } else { + trans.write(colon_ ? &kJSONPairSeparator : &kJSONElemSeparator, 1); + colon_ = !colon_; + return 1; + } + } + + uint32_t read(TJSONProtocol::LookaheadReader& reader) override { + if (first_) { + first_ = false; + colon_ = true; + return 0; + } else { + uint8_t ch = (colon_ ? kJSONPairSeparator : kJSONElemSeparator); + colon_ = !colon_; + return readSyntaxChar(reader, ch); + } + } + + // Numbers must be turned into strings if they are the key part of a pair + bool escapeNum() override { return colon_; } + +private: + bool first_; + bool colon_; +}; + +// Context class for lists +class JSONListContext : public TJSONContext { + +public: + JSONListContext() : first_(true) {} + + uint32_t write(TTransport& trans) override { + if (first_) { + first_ = false; + return 0; + } else { + trans.write(&kJSONElemSeparator, 1); + return 1; + } + } + + uint32_t read(TJSONProtocol::LookaheadReader& reader) override { + if (first_) { + first_ = false; + return 0; + } else { + return readSyntaxChar(reader, kJSONElemSeparator); + } + } + +private: + bool first_; +}; + +TJSONProtocol::TJSONProtocol(std::shared_ptr ptrans) + : TVirtualProtocol(ptrans), + trans_(ptrans.get()), + context_(new TJSONContext()), + reader_(*ptrans) { +} + +TJSONProtocol::~TJSONProtocol() = default; + +void TJSONProtocol::pushContext(std::shared_ptr c) { + contexts_.push(context_); + context_ = c; +} + +void TJSONProtocol::popContext() { + context_ = contexts_.top(); + contexts_.pop(); +} + +// Write the character ch as a JSON escape sequence ("\u00xx") +uint32_t TJSONProtocol::writeJSONEscapeChar(uint8_t ch) { + trans_->write((const uint8_t*)kJSONEscapePrefix.c_str(), + static_cast(kJSONEscapePrefix.length())); + uint8_t outCh = hexChar(ch >> 4); + trans_->write(&outCh, 1); + outCh = hexChar(ch); + trans_->write(&outCh, 1); + return 6; +} + +// Write the character ch as part of a JSON string, escaping as appropriate. +uint32_t TJSONProtocol::writeJSONChar(uint8_t ch) { + if (ch >= 0x30) { + if (ch == kJSONBackslash) { // Only special character >= 0x30 is '\' + trans_->write(&kJSONBackslash, 1); + trans_->write(&kJSONBackslash, 1); + return 2; + } else { + trans_->write(&ch, 1); + return 1; + } + } else { + uint8_t outCh = kJSONCharTable[ch]; + // Check if regular character, backslash escaped, or JSON escaped + if (outCh == 1) { + trans_->write(&ch, 1); + return 1; + } else if (outCh > 1) { + trans_->write(&kJSONBackslash, 1); + trans_->write(&outCh, 1); + return 2; + } else { + return writeJSONEscapeChar(ch); + } + } +} + +// Write out the contents of the string str as a JSON string, escaping +// characters as appropriate. +uint32_t TJSONProtocol::writeJSONString(const std::string& str) { + uint32_t result = context_->write(*trans_); + result += 2; // For quotes + trans_->write(&kJSONStringDelimiter, 1); + std::string::const_iterator iter(str.begin()); + std::string::const_iterator end(str.end()); + while (iter != end) { + result += writeJSONChar(*iter++); + } + trans_->write(&kJSONStringDelimiter, 1); + return result; +} + +// Write out the contents of the string as JSON string, base64-encoding +// the string's contents, and escaping as appropriate +uint32_t TJSONProtocol::writeJSONBase64(const std::string& str) { + uint32_t result = context_->write(*trans_); + result += 2; // For quotes + trans_->write(&kJSONStringDelimiter, 1); + uint8_t b[4]; + const auto* bytes = (const uint8_t*)str.c_str(); + if (str.length() > (std::numeric_limits::max)()) + throw TProtocolException(TProtocolException::SIZE_LIMIT); + auto len = static_cast(str.length()); + while (len >= 3) { + // Encode 3 bytes at a time + base64_encode(bytes, 3, b); + trans_->write(b, 4); + result += 4; + bytes += 3; + len -= 3; + } + if (len) { // Handle remainder + base64_encode(bytes, len, b); + trans_->write(b, len + 1); + result += len + 1; + } + trans_->write(&kJSONStringDelimiter, 1); + return result; +} + +// Convert the given integer type to a JSON number, or a string +// if the context requires it (eg: key in a map pair). +template +uint32_t TJSONProtocol::writeJSONInteger(NumberType num) { + uint32_t result = context_->write(*trans_); + std::string val(to_string(num)); + bool escapeNum = context_->escapeNum(); + if (escapeNum) { + trans_->write(&kJSONStringDelimiter, 1); + result += 1; + } + if (val.length() > (std::numeric_limits::max)()) + throw TProtocolException(TProtocolException::SIZE_LIMIT); + trans_->write((const uint8_t*)val.c_str(), static_cast(val.length())); + result += static_cast(val.length()); + if (escapeNum) { + trans_->write(&kJSONStringDelimiter, 1); + result += 1; + } + return result; +} + +namespace { +std::string doubleToString(double d) { + std::ostringstream str; + str.imbue(std::locale::classic()); + const std::streamsize max_digits10 = 2 + std::numeric_limits::digits10; + str.precision(max_digits10); + str << d; + return str.str(); +} +} + +// Convert the given double to a JSON string, which is either the number, +// "NaN" or "Infinity" or "-Infinity". +uint32_t TJSONProtocol::writeJSONDouble(double num) { + uint32_t result = context_->write(*trans_); + std::string val; + + bool special = false; + switch (std::fpclassify(num)) { + case FP_INFINITE: + if (std::signbit(num)) { + val = kThriftNegativeInfinity; + } else { + val = kThriftInfinity; + } + special = true; + break; + case FP_NAN: + val = kThriftNan; + special = true; + break; + default: + val = doubleToString(num); + break; + } + + bool escapeNum = special || context_->escapeNum(); + if (escapeNum) { + trans_->write(&kJSONStringDelimiter, 1); + result += 1; + } + if (val.length() > (std::numeric_limits::max)()) + throw TProtocolException(TProtocolException::SIZE_LIMIT); + trans_->write((const uint8_t*)val.c_str(), static_cast(val.length())); + result += static_cast(val.length()); + if (escapeNum) { + trans_->write(&kJSONStringDelimiter, 1); + result += 1; + } + return result; +} + +uint32_t TJSONProtocol::writeJSONObjectStart() { + uint32_t result = context_->write(*trans_); + trans_->write(&kJSONObjectStart, 1); + pushContext(std::shared_ptr(new JSONPairContext())); + return result + 1; +} + +uint32_t TJSONProtocol::writeJSONObjectEnd() { + popContext(); + trans_->write(&kJSONObjectEnd, 1); + return 1; +} + +uint32_t TJSONProtocol::writeJSONArrayStart() { + uint32_t result = context_->write(*trans_); + trans_->write(&kJSONArrayStart, 1); + pushContext(std::shared_ptr(new JSONListContext())); + return result + 1; +} + +uint32_t TJSONProtocol::writeJSONArrayEnd() { + popContext(); + trans_->write(&kJSONArrayEnd, 1); + return 1; +} + +uint32_t TJSONProtocol::writeMessageBegin(const std::string& name, + const TMessageType messageType, + const int32_t seqid) { + uint32_t result = writeJSONArrayStart(); + result += writeJSONInteger(kThriftVersion1); + result += writeJSONString(name); + result += writeJSONInteger(messageType); + result += writeJSONInteger(seqid); + return result; +} + +uint32_t TJSONProtocol::writeMessageEnd() { + return writeJSONArrayEnd(); +} + +uint32_t TJSONProtocol::writeStructBegin(const char* name) { + (void)name; + return writeJSONObjectStart(); +} + +uint32_t TJSONProtocol::writeStructEnd() { + return writeJSONObjectEnd(); +} + +uint32_t TJSONProtocol::writeFieldBegin(const char* name, + const TType fieldType, + const int16_t fieldId) { + (void)name; + uint32_t result = writeJSONInteger(fieldId); + result += writeJSONObjectStart(); + result += writeJSONString(getTypeNameForTypeID(fieldType)); + return result; +} + +uint32_t TJSONProtocol::writeFieldEnd() { + return writeJSONObjectEnd(); +} + +uint32_t TJSONProtocol::writeFieldStop() { + return 0; +} + +uint32_t TJSONProtocol::writeMapBegin(const TType keyType, + const TType valType, + const uint32_t size) { + uint32_t result = writeJSONArrayStart(); + result += writeJSONString(getTypeNameForTypeID(keyType)); + result += writeJSONString(getTypeNameForTypeID(valType)); + result += writeJSONInteger((int64_t)size); + result += writeJSONObjectStart(); + return result; +} + +uint32_t TJSONProtocol::writeMapEnd() { + uint32_t result = writeJSONObjectEnd(); + result += writeJSONArrayEnd(); + return result; +} + +uint32_t TJSONProtocol::writeListBegin(const TType elemType, const uint32_t size) { + uint32_t result = writeJSONArrayStart(); + result += writeJSONString(getTypeNameForTypeID(elemType)); + result += writeJSONInteger((int64_t)size); + return result; +} + +uint32_t TJSONProtocol::writeListEnd() { + return writeJSONArrayEnd(); +} + +uint32_t TJSONProtocol::writeSetBegin(const TType elemType, const uint32_t size) { + uint32_t result = writeJSONArrayStart(); + result += writeJSONString(getTypeNameForTypeID(elemType)); + result += writeJSONInteger((int64_t)size); + return result; +} + +uint32_t TJSONProtocol::writeSetEnd() { + return writeJSONArrayEnd(); +} + +uint32_t TJSONProtocol::writeBool(const bool value) { + return writeJSONInteger(value); +} + +uint32_t TJSONProtocol::writeByte(const int8_t byte) { + // writeByte() must be handled specially because to_string sees + // int8_t as a text type instead of an integer type + return writeJSONInteger((int16_t)byte); +} + +uint32_t TJSONProtocol::writeI16(const int16_t i16) { + return writeJSONInteger(i16); +} + +uint32_t TJSONProtocol::writeI32(const int32_t i32) { + return writeJSONInteger(i32); +} + +uint32_t TJSONProtocol::writeI64(const int64_t i64) { + return writeJSONInteger(i64); +} + +uint32_t TJSONProtocol::writeDouble(const double dub) { + return writeJSONDouble(dub); +} + +uint32_t TJSONProtocol::writeString(const std::string& str) { + return writeJSONString(str); +} + +uint32_t TJSONProtocol::writeBinary(const std::string& str) { + return writeJSONBase64(str); +} + +/** + * Reading functions + */ + +// Reads 1 byte and verifies that it matches ch. +uint32_t TJSONProtocol::readJSONSyntaxChar(uint8_t ch) { + return readSyntaxChar(reader_, ch); +} + +// Decodes the four hex parts of a JSON escaped string character and returns +// the UTF-16 code unit via out. +uint32_t TJSONProtocol::readJSONEscapeChar(uint16_t* out) { + uint8_t b[4]; + b[0] = reader_.read(); + b[1] = reader_.read(); + b[2] = reader_.read(); + b[3] = reader_.read(); + + *out = (hexVal(b[0]) << 12) + + (hexVal(b[1]) << 8) + (hexVal(b[2]) << 4) + hexVal(b[3]); + + return 4; +} + +// Decodes a JSON string, including unescaping, and returns the string via str +uint32_t TJSONProtocol::readJSONString(std::string& str, bool skipContext) { + uint32_t result = (skipContext ? 0 : context_->read(reader_)); + result += readJSONSyntaxChar(kJSONStringDelimiter); + std::vector codeunits; + uint8_t ch; + str.clear(); + while (true) { + ch = reader_.read(); + ++result; + if (ch == kJSONStringDelimiter) { + break; + } + if (ch == kJSONBackslash) { + ch = reader_.read(); + ++result; + if (ch == kJSONEscapeChar) { + uint16_t cp; + result += readJSONEscapeChar(&cp); + if (isHighSurrogate(cp)) { + codeunits.push_back(cp); + } else { + if (isLowSurrogate(cp) + && codeunits.empty()) { + throw TProtocolException(TProtocolException::INVALID_DATA, + "Missing UTF-16 high surrogate pair."); + } + codeunits.push_back(cp); + codeunits.push_back(0); + str += boost::locale::conv::utf_to_utf(codeunits.data()); + codeunits.clear(); + } + continue; + } else { + size_t pos = kEscapeChars.find(ch); + if (pos == kEscapeChars.npos) { + throw TProtocolException(TProtocolException::INVALID_DATA, + "Expected control char, got '" + std::string((const char*)&ch, 1) + + "'."); + } + ch = kEscapeCharVals[pos]; + } + } + if (!codeunits.empty()) { + throw TProtocolException(TProtocolException::INVALID_DATA, + "Missing UTF-16 low surrogate pair."); + } + str += ch; + } + + if (!codeunits.empty()) { + throw TProtocolException(TProtocolException::INVALID_DATA, + "Missing UTF-16 low surrogate pair."); + } + return result; +} + +// Reads a block of base64 characters, decoding it, and returns via str +uint32_t TJSONProtocol::readJSONBase64(std::string& str) { + std::string tmp; + uint32_t result = readJSONString(tmp); + auto* b = (uint8_t*)tmp.c_str(); + if (tmp.length() > (std::numeric_limits::max)()) + throw TProtocolException(TProtocolException::SIZE_LIMIT); + auto len = static_cast(tmp.length()); + str.clear(); + // Ignore padding + if (len >= 2) { + uint32_t bound = len - 2; + for (uint32_t i = len - 1; i >= bound && b[i] == '='; --i) { + --len; + } + } + while (len >= 4) { + base64_decode(b, 4); + str.append((const char*)b, 3); + b += 4; + len -= 4; + } + // Don't decode if we hit the end or got a single leftover byte (invalid + // base64 but legal for skip of regular string type) + if (len > 1) { + base64_decode(b, len); + str.append((const char*)b, len - 1); + } + return result; +} + +// Reads a sequence of characters, stopping at the first one that is not +// a valid JSON numeric character. +uint32_t TJSONProtocol::readJSONNumericChars(std::string& str) { + uint32_t result = 0; + str.clear(); + while (true) { + uint8_t ch = reader_.peek(); + if (!isJSONNumeric(ch)) { + break; + } + reader_.read(); + str += ch; + ++result; + } + return result; +} + +namespace { +template +T fromString(const std::string& s) { + T t; + std::istringstream str(s); + str.imbue(std::locale::classic()); + str >> t; + if (str.bad() || !str.eof()) + throw std::runtime_error(s); + return t; +} +} + +// Reads a sequence of characters and assembles them into a number, +// returning them via num +template +uint32_t TJSONProtocol::readJSONInteger(NumberType& num) { + uint32_t result = context_->read(reader_); + if (context_->escapeNum()) { + result += readJSONSyntaxChar(kJSONStringDelimiter); + } + std::string str; + result += readJSONNumericChars(str); + try { + num = fromString(str); + } catch (const std::runtime_error&) { + throw TProtocolException(TProtocolException::INVALID_DATA, + "Expected numeric value; got \"" + str + "\""); + } + if (context_->escapeNum()) { + result += readJSONSyntaxChar(kJSONStringDelimiter); + } + return result; +} + +// Reads a JSON number or string and interprets it as a double. +uint32_t TJSONProtocol::readJSONDouble(double& num) { + uint32_t result = context_->read(reader_); + std::string str; + if (reader_.peek() == kJSONStringDelimiter) { + result += readJSONString(str, true); + // Check for NaN, Infinity and -Infinity + if (str == kThriftNan) { + num = HUGE_VAL / HUGE_VAL; // generates NaN + } else if (str == kThriftInfinity) { + num = HUGE_VAL; + } else if (str == kThriftNegativeInfinity) { + num = -HUGE_VAL; + } else { + if (!context_->escapeNum()) { + // Throw exception -- we should not be in a string in this case + throw TProtocolException(TProtocolException::INVALID_DATA, + "Numeric data unexpectedly quoted"); + } + try { + num = fromString(str); + } catch (const std::runtime_error&) { + throw TProtocolException(TProtocolException::INVALID_DATA, + "Expected numeric value; got \"" + str + "\""); + } + } + } else { + if (context_->escapeNum()) { + // This will throw - we should have had a quote if escapeNum == true + readJSONSyntaxChar(kJSONStringDelimiter); + } + result += readJSONNumericChars(str); + try { + num = fromString(str); + } catch (const std::runtime_error&) { + throw TProtocolException(TProtocolException::INVALID_DATA, + "Expected numeric value; got \"" + str + "\""); + } + } + return result; +} + +uint32_t TJSONProtocol::readJSONObjectStart() { + uint32_t result = context_->read(reader_); + result += readJSONSyntaxChar(kJSONObjectStart); + pushContext(std::shared_ptr(new JSONPairContext())); + return result; +} + +uint32_t TJSONProtocol::readJSONObjectEnd() { + uint32_t result = readJSONSyntaxChar(kJSONObjectEnd); + popContext(); + return result; +} + +uint32_t TJSONProtocol::readJSONArrayStart() { + uint32_t result = context_->read(reader_); + result += readJSONSyntaxChar(kJSONArrayStart); + pushContext(std::shared_ptr(new JSONListContext())); + return result; +} + +uint32_t TJSONProtocol::readJSONArrayEnd() { + uint32_t result = readJSONSyntaxChar(kJSONArrayEnd); + popContext(); + return result; +} + +uint32_t TJSONProtocol::readMessageBegin(std::string& name, + TMessageType& messageType, + int32_t& seqid) { + uint32_t result = readJSONArrayStart(); + int64_t tmpVal = 0; + result += readJSONInteger(tmpVal); + if (tmpVal != kThriftVersion1) { + throw TProtocolException(TProtocolException::BAD_VERSION, "Message contained bad version."); + } + result += readJSONString(name); + result += readJSONInteger(tmpVal); + messageType = (TMessageType)tmpVal; + result += readJSONInteger(tmpVal); + if (tmpVal > (std::numeric_limits::max)() || + tmpVal < (std::numeric_limits::min)()) + throw TProtocolException(TProtocolException::INVALID_DATA, "sequence id is not int32_t"); + seqid = static_cast(tmpVal); + return result; +} + +uint32_t TJSONProtocol::readMessageEnd() { + return readJSONArrayEnd(); +} + +uint32_t TJSONProtocol::readStructBegin(std::string& name) { + (void)name; + return readJSONObjectStart(); +} + +uint32_t TJSONProtocol::readStructEnd() { + return readJSONObjectEnd(); +} + +uint32_t TJSONProtocol::readFieldBegin(std::string& name, TType& fieldType, int16_t& fieldId) { + (void)name; + uint32_t result = 0; + // Check if we hit the end of the list + uint8_t ch = reader_.peek(); + if (ch == kJSONObjectEnd) { + fieldType = apache::thrift::protocol::T_STOP; + } else { + uint64_t tmpVal = 0; + std::string tmpStr; + result += readJSONInteger(tmpVal); + if (tmpVal > static_cast((std::numeric_limits::max)())) + throw TProtocolException(TProtocolException::SIZE_LIMIT); + fieldId = static_cast(tmpVal); + result += readJSONObjectStart(); + result += readJSONString(tmpStr); + fieldType = getTypeIDForTypeName(tmpStr); + } + return result; +} + +uint32_t TJSONProtocol::readFieldEnd() { + return readJSONObjectEnd(); +} + +uint32_t TJSONProtocol::readMapBegin(TType& keyType, TType& valType, uint32_t& size) { + uint64_t tmpVal = 0; + std::string tmpStr; + uint32_t result = readJSONArrayStart(); + result += readJSONString(tmpStr); + keyType = getTypeIDForTypeName(tmpStr); + result += readJSONString(tmpStr); + valType = getTypeIDForTypeName(tmpStr); + result += readJSONInteger(tmpVal); + if (tmpVal > (std::numeric_limits::max)()) + throw TProtocolException(TProtocolException::SIZE_LIMIT); + size = static_cast(tmpVal); + result += readJSONObjectStart(); + return result; +} + +uint32_t TJSONProtocol::readMapEnd() { + uint32_t result = readJSONObjectEnd(); + result += readJSONArrayEnd(); + return result; +} + +uint32_t TJSONProtocol::readListBegin(TType& elemType, uint32_t& size) { + uint64_t tmpVal = 0; + std::string tmpStr; + uint32_t result = readJSONArrayStart(); + result += readJSONString(tmpStr); + elemType = getTypeIDForTypeName(tmpStr); + result += readJSONInteger(tmpVal); + if (tmpVal > (std::numeric_limits::max)()) + throw TProtocolException(TProtocolException::SIZE_LIMIT); + size = static_cast(tmpVal); + return result; +} + +uint32_t TJSONProtocol::readListEnd() { + return readJSONArrayEnd(); +} + +uint32_t TJSONProtocol::readSetBegin(TType& elemType, uint32_t& size) { + uint64_t tmpVal = 0; + std::string tmpStr; + uint32_t result = readJSONArrayStart(); + result += readJSONString(tmpStr); + elemType = getTypeIDForTypeName(tmpStr); + result += readJSONInteger(tmpVal); + if (tmpVal > (std::numeric_limits::max)()) + throw TProtocolException(TProtocolException::SIZE_LIMIT); + size = static_cast(tmpVal); + return result; +} + +uint32_t TJSONProtocol::readSetEnd() { + return readJSONArrayEnd(); +} + +uint32_t TJSONProtocol::readBool(bool& value) { + return readJSONInteger(value); +} + +// readByte() must be handled properly because boost::lexical cast sees int8_t +// as a text type instead of an integer type +uint32_t TJSONProtocol::readByte(int8_t& byte) { + auto tmp = (int16_t)byte; + uint32_t result = readJSONInteger(tmp); + assert(tmp < 256); + byte = (int8_t)tmp; + return result; +} + +uint32_t TJSONProtocol::readI16(int16_t& i16) { + return readJSONInteger(i16); +} + +uint32_t TJSONProtocol::readI32(int32_t& i32) { + return readJSONInteger(i32); +} + +uint32_t TJSONProtocol::readI64(int64_t& i64) { + return readJSONInteger(i64); +} + +uint32_t TJSONProtocol::readDouble(double& dub) { + return readJSONDouble(dub); +} + +uint32_t TJSONProtocol::readString(std::string& str) { + return readJSONString(str); +} + +uint32_t TJSONProtocol::readBinary(std::string& str) { + return readJSONBase64(str); +} +} +} +} // apache::thrift::protocol diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TJSONProtocol.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TJSONProtocol.h new file mode 100644 index 000000000..420995ef3 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TJSONProtocol.h @@ -0,0 +1,325 @@ +/* + * 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. + */ + +#ifndef _THRIFT_PROTOCOL_TJSONPROTOCOL_H_ +#define _THRIFT_PROTOCOL_TJSONPROTOCOL_H_ 1 + +#include + +#include + +namespace apache { +namespace thrift { +namespace protocol { + +// Forward declaration +class TJSONContext; + +/** + * JSON protocol for Thrift. + * + * Implements a protocol which uses JSON as the wire-format. + * + * Thrift types are represented as described below: + * + * 1. Every Thrift integer type is represented as a JSON number. + * + * 2. Thrift doubles are represented as JSON numbers. Some special values are + * represented as strings: + * a. "NaN" for not-a-number values + * b. "Infinity" for positive infinity + * c. "-Infinity" for negative infinity + * + * 3. Thrift string values are emitted as JSON strings, with appropriate + * escaping. + * + * 4. Thrift binary values are encoded into Base64 and emitted as JSON strings. + * The readBinary() method is written such that it will properly skip if + * called on a Thrift string (although it will decode garbage data). + * + * NOTE: Base64 padding is optional for Thrift binary value encoding. So + * the readBinary() method needs to decode both input strings with padding + * and those without one. + * + * 5. Thrift structs are represented as JSON objects, with the field ID as the + * key, and the field value represented as a JSON object with a single + * key-value pair. The key is a short string identifier for that type, + * followed by the value. The valid type identifiers are: "tf" for bool, + * "i8" for byte, "i16" for 16-bit integer, "i32" for 32-bit integer, "i64" + * for 64-bit integer, "dbl" for double-precision loating point, "str" for + * string (including binary), "rec" for struct ("records"), "map" for map, + * "lst" for list, "set" for set. + * + * 6. Thrift lists and sets are represented as JSON arrays, with the first + * element of the JSON array being the string identifier for the Thrift + * element type and the second element of the JSON array being the count of + * the Thrift elements. The Thrift elements then follow. + * + * 7. Thrift maps are represented as JSON arrays, with the first two elements + * of the JSON array being the string identifiers for the Thrift key type + * and value type, followed by the count of the Thrift pairs, followed by a + * JSON object containing the key-value pairs. Note that JSON keys can only + * be strings, which means that the key type of the Thrift map should be + * restricted to numeric or string types -- in the case of numerics, they + * are serialized as strings. + * + * 8. Thrift messages are represented as JSON arrays, with the protocol + * version #, the message name, the message type, and the sequence ID as + * the first 4 elements. + * + * More discussion of the double handling is probably warranted. The aim of + * the current implementation is to match as closely as possible the behavior + * of Java's Double.toString(), which has no precision loss. Implementors in + * other languages should strive to achieve that where possible. I have not + * yet verified whether std::istringstream::operator>>, which is doing that + * work for me in C++, loses any precision, but I am leaving this as a future + * improvement. I may try to provide a C component for this, so that other + * languages could bind to the same underlying implementation for maximum + * consistency. + * + */ +class TJSONProtocol : public TVirtualProtocol { +public: + TJSONProtocol(std::shared_ptr ptrans); + + ~TJSONProtocol() override; + +private: + void pushContext(std::shared_ptr c); + + void popContext(); + + uint32_t writeJSONEscapeChar(uint8_t ch); + + uint32_t writeJSONChar(uint8_t ch); + + uint32_t writeJSONString(const std::string& str); + + uint32_t writeJSONBase64(const std::string& str); + + template + uint32_t writeJSONInteger(NumberType num); + + uint32_t writeJSONDouble(double num); + + uint32_t writeJSONObjectStart(); + + uint32_t writeJSONObjectEnd(); + + uint32_t writeJSONArrayStart(); + + uint32_t writeJSONArrayEnd(); + + uint32_t readJSONSyntaxChar(uint8_t ch); + + uint32_t readJSONEscapeChar(uint16_t* out); + + uint32_t readJSONString(std::string& str, bool skipContext = false); + + uint32_t readJSONBase64(std::string& str); + + uint32_t readJSONNumericChars(std::string& str); + + template + uint32_t readJSONInteger(NumberType& num); + + uint32_t readJSONDouble(double& num); + + uint32_t readJSONObjectStart(); + + uint32_t readJSONObjectEnd(); + + uint32_t readJSONArrayStart(); + + uint32_t readJSONArrayEnd(); + +public: + /** + * Writing functions. + */ + + uint32_t writeMessageBegin(const std::string& name, + const TMessageType messageType, + const int32_t seqid); + + uint32_t writeMessageEnd(); + + uint32_t writeStructBegin(const char* name); + + uint32_t writeStructEnd(); + + uint32_t writeFieldBegin(const char* name, const TType fieldType, const int16_t fieldId); + + uint32_t writeFieldEnd(); + + uint32_t writeFieldStop(); + + uint32_t writeMapBegin(const TType keyType, const TType valType, const uint32_t size); + + uint32_t writeMapEnd(); + + uint32_t writeListBegin(const TType elemType, const uint32_t size); + + uint32_t writeListEnd(); + + uint32_t writeSetBegin(const TType elemType, const uint32_t size); + + uint32_t writeSetEnd(); + + uint32_t writeBool(const bool value); + + uint32_t writeByte(const int8_t byte); + + uint32_t writeI16(const int16_t i16); + + uint32_t writeI32(const int32_t i32); + + uint32_t writeI64(const int64_t i64); + + uint32_t writeDouble(const double dub); + + uint32_t writeString(const std::string& str); + + uint32_t writeBinary(const std::string& str); + + /** + * Reading functions + */ + + uint32_t readMessageBegin(std::string& name, TMessageType& messageType, int32_t& seqid); + + uint32_t readMessageEnd(); + + uint32_t readStructBegin(std::string& name); + + uint32_t readStructEnd(); + + uint32_t readFieldBegin(std::string& name, TType& fieldType, int16_t& fieldId); + + uint32_t readFieldEnd(); + + uint32_t readMapBegin(TType& keyType, TType& valType, uint32_t& size); + + uint32_t readMapEnd(); + + uint32_t readListBegin(TType& elemType, uint32_t& size); + + uint32_t readListEnd(); + + uint32_t readSetBegin(TType& elemType, uint32_t& size); + + uint32_t readSetEnd(); + + uint32_t readBool(bool& value); + + // Provide the default readBool() implementation for std::vector + using TVirtualProtocol::readBool; + + uint32_t readByte(int8_t& byte); + + uint32_t readI16(int16_t& i16); + + uint32_t readI32(int32_t& i32); + + uint32_t readI64(int64_t& i64); + + uint32_t readDouble(double& dub); + + uint32_t readString(std::string& str); + + uint32_t readBinary(std::string& str); + + class LookaheadReader { + + public: + LookaheadReader(TTransport& trans) : trans_(&trans), hasData_(false), data_(0) {} + + uint8_t read() { + if (hasData_) { + hasData_ = false; + } else { + trans_->readAll(&data_, 1); + } + return data_; + } + + uint8_t peek() { + if (!hasData_) { + trans_->readAll(&data_, 1); + } + hasData_ = true; + return data_; + } + + private: + TTransport* trans_; + bool hasData_; + uint8_t data_; + }; + +private: + TTransport* trans_; + + std::stack > contexts_; + std::shared_ptr context_; + LookaheadReader reader_; +}; + +/** + * Constructs input and output protocol objects given transports. + */ +class TJSONProtocolFactory : public TProtocolFactory { +public: + TJSONProtocolFactory() = default; + + ~TJSONProtocolFactory() override = default; + + std::shared_ptr getProtocol(std::shared_ptr trans) override { + return std::shared_ptr(new TJSONProtocol(trans)); + } +}; +} +} +} // apache::thrift::protocol + +// TODO(dreiss): Move part of ThriftJSONString into a .cpp file and remove this. +#include + +namespace apache { +namespace thrift { + +template +std::string ThriftJSONString(const ThriftStruct& ts) { + using namespace apache::thrift::transport; + using namespace apache::thrift::protocol; + auto* buffer = new TMemoryBuffer; + std::shared_ptr trans(buffer); + TJSONProtocol protocol(trans); + + ts.write(&protocol); + + uint8_t* buf; + uint32_t size; + buffer->getBuffer(&buf, &size); + return std::string((char*)buf, (unsigned int)size); +} +} +} // apache::thrift + +#endif // #define _THRIFT_PROTOCOL_TJSONPROTOCOL_H_ 1 diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TMultiplexedProtocol.cpp b/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TMultiplexedProtocol.cpp new file mode 100644 index 000000000..f0dc69e0d --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TMultiplexedProtocol.cpp @@ -0,0 +1,40 @@ +/* + * 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. + */ + +#include +#include +#include + +namespace apache { +namespace thrift { +namespace protocol { +uint32_t TMultiplexedProtocol::writeMessageBegin_virt(const std::string& _name, + const TMessageType _type, + const int32_t _seqid) { + if (_type == T_CALL || _type == T_ONEWAY) { + return TProtocolDecorator::writeMessageBegin_virt(serviceName + separator + _name, + _type, + _seqid); + } else { + return TProtocolDecorator::writeMessageBegin_virt(_name, _type, _seqid); + } +} +} +} +} diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TMultiplexedProtocol.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TMultiplexedProtocol.h new file mode 100644 index 000000000..0dc960584 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TMultiplexedProtocol.h @@ -0,0 +1,95 @@ +/* + * 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. + */ + +#ifndef THRIFT_TMULTIPLEXEDPROTOCOL_H_ +#define THRIFT_TMULTIPLEXEDPROTOCOL_H_ 1 + +#include + +namespace apache { +namespace thrift { +namespace protocol { +using std::shared_ptr; + +/** + * TMultiplexedProtocol is a protocol-independent concrete decorator + * that allows a Thrift client to communicate with a multiplexing Thrift server, + * by prepending the service name to the function name during function calls. + * + * \note THIS IS NOT USED BY SERVERS. On the server, use + * {@link apache::thrift::TMultiplexedProcessor TMultiplexedProcessor} to handle requests + * from a multiplexing client. + * + * This example uses a single socket transport to invoke two services: + * + *
+ * shared_ptr transport(new TSocket("localhost", 9090)); + * transport->open(); + * + * shared_ptr protocol(new TBinaryProtocol(transport)); + * + * shared_ptr mp1(new TMultiplexedProtocol(protocol, "Calculator")); + * shared_ptr service1(new CalculatorClient(mp1)); + * + * shared_ptr mp2(new TMultiplexedProtocol(protocol, "WeatherReport")); + * shared_ptr service2(new WeatherReportClient(mp2)); + * + * service1->add(2,2); + * int temp = service2->getTemperature(); + *
+ * + * @see apache::thrift::protocol::TProtocolDecorator + */ +class TMultiplexedProtocol : public TProtocolDecorator { +public: + /** + * Wrap the specified protocol, allowing it to be used to communicate with a + * multiplexing server. The serviceName is required as it is + * prepended to the message header so that the multiplexing server can broker + * the function call to the proper service. + * + * \param _protocol Your communication protocol of choice, e.g. TBinaryProtocol. + * \param _serviceName The service name of the service communicating via this protocol. + */ + TMultiplexedProtocol(shared_ptr _protocol, const std::string& _serviceName) + : TProtocolDecorator(_protocol), serviceName(_serviceName), separator(":") {} + ~TMultiplexedProtocol() override = default; + + /** + * Prepends the service name to the function name, separated by TMultiplexedProtocol::SEPARATOR. + * + * \param [in] _name The name of the method to be called in the service. + * \param [in] _type The type of message + * \param [in] _name The sequential id of the message + * + * \throws TException Passed through from wrapped TProtocol instance. + */ + uint32_t writeMessageBegin_virt(const std::string& _name, + const TMessageType _type, + const int32_t _seqid) override; + +private: + const std::string serviceName; + const std::string separator; +}; +} +} +} + +#endif // THRIFT_TMULTIPLEXEDPROTOCOL_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TProtocol.cpp b/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TProtocol.cpp new file mode 100644 index 000000000..b460455ff --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TProtocol.cpp @@ -0,0 +1,33 @@ +/* + * 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. + */ + +#include + +namespace apache { +namespace thrift { +namespace protocol { + +TProtocol::~TProtocol() = default; +uint32_t TProtocol::skip_virt(TType type) { + return ::apache::thrift::protocol::skip(*this, type); +} + +TProtocolFactory::~TProtocolFactory() = default; + +}}} // apache::thrift::protocol diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TProtocol.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TProtocol.h new file mode 100644 index 000000000..df9c5c39b --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TProtocol.h @@ -0,0 +1,762 @@ +/* + * 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. + */ + +#ifndef _THRIFT_PROTOCOL_TPROTOCOL_H_ +#define _THRIFT_PROTOCOL_TPROTOCOL_H_ 1 + +#ifdef _WIN32 +// Need to come before any Windows.h includes +#include +#endif + +#include +#include + +#include + +#ifdef HAVE_NETINET_IN_H +#include +#endif +#include +#include +#include +#include +#include + +// Use this to get around strict aliasing rules. +// For example, uint64_t i = bitwise_cast(returns_double()); +// The most obvious implementation is to just cast a pointer, +// but that doesn't work. +// For a pretty in-depth explanation of the problem, see +// http://cellperformance.beyond3d.com/articles/2006/06/understanding-strict-aliasing.html +template +static inline To bitwise_cast(From from) { + static_assert(sizeof(From) == sizeof(To), "sizeof(From) == sizeof(To)"); + + // BAD!!! These are all broken with -O2. + //return *reinterpret_cast(&from); // BAD!!! + //return *static_cast(static_cast(&from)); // BAD!!! + //return *(To*)(void*)&from; // BAD!!! + + // Super clean and paritally blessed by section 3.9 of the standard. + //unsigned char c[sizeof(from)]; + //memcpy(c, &from, sizeof(from)); + //To to; + //memcpy(&to, c, sizeof(c)); + //return to; + + // Slightly more questionable. + // Same code emitted by GCC. + //To to; + //memcpy(&to, &from, sizeof(from)); + //return to; + + // Technically undefined, but almost universally supported, + // and the most efficient implementation. + union { + From f; + To t; + } u; + u.f = from; + return u.t; +} + + +#ifdef HAVE_SYS_PARAM_H +#include +#endif + +#ifndef __THRIFT_BYTE_ORDER +# if defined(BYTE_ORDER) && defined(LITTLE_ENDIAN) && defined(BIG_ENDIAN) +# define __THRIFT_BYTE_ORDER BYTE_ORDER +# define __THRIFT_LITTLE_ENDIAN LITTLE_ENDIAN +# define __THRIFT_BIG_ENDIAN BIG_ENDIAN +# else +# include +# if BOOST_ENDIAN_BIG_BYTE +# define __THRIFT_BYTE_ORDER 4321 +# define __THRIFT_LITTLE_ENDIAN 0 +# define __THRIFT_BIG_ENDIAN __THRIFT_BYTE_ORDER +# elif BOOST_ENDIAN_LITTLE_BYTE +# define __THRIFT_BYTE_ORDER 1234 +# define __THRIFT_LITTLE_ENDIAN __THRIFT_BYTE_ORDER +# define __THRIFT_BIG_ENDIAN 0 +# endif +# ifdef BOOST_LITTLE_ENDIAN +# else +# endif +# endif +#endif + +#if __THRIFT_BYTE_ORDER == __THRIFT_BIG_ENDIAN +# if !defined(THRIFT_ntohll) +# define THRIFT_ntohll(n) (n) +# define THRIFT_htonll(n) (n) +# endif +# if defined(__GNUC__) && defined(__GLIBC__) +# include +# define THRIFT_htolell(n) bswap_64(n) +# define THRIFT_letohll(n) bswap_64(n) +# define THRIFT_htolel(n) bswap_32(n) +# define THRIFT_letohl(n) bswap_32(n) +# define THRIFT_htoles(n) bswap_16(n) +# define THRIFT_letohs(n) bswap_16(n) +# else /* GNUC & GLIBC */ +# define bswap_64(n) \ + ( (((n) & 0xff00000000000000ull) >> 56) \ + | (((n) & 0x00ff000000000000ull) >> 40) \ + | (((n) & 0x0000ff0000000000ull) >> 24) \ + | (((n) & 0x000000ff00000000ull) >> 8) \ + | (((n) & 0x00000000ff000000ull) << 8) \ + | (((n) & 0x0000000000ff0000ull) << 24) \ + | (((n) & 0x000000000000ff00ull) << 40) \ + | (((n) & 0x00000000000000ffull) << 56) ) +# define bswap_32(n) \ + ( (((n) & 0xff000000ul) >> 24) \ + | (((n) & 0x00ff0000ul) >> 8) \ + | (((n) & 0x0000ff00ul) << 8) \ + | (((n) & 0x000000fful) << 24) ) +# define bswap_16(n) \ + ( (((n) & ((unsigned short)0xff00ul)) >> 8) \ + | (((n) & ((unsigned short)0x00fful)) << 8) ) +# define THRIFT_htolell(n) bswap_64(n) +# define THRIFT_letohll(n) bswap_64(n) +# define THRIFT_htolel(n) bswap_32(n) +# define THRIFT_letohl(n) bswap_32(n) +# define THRIFT_htoles(n) bswap_16(n) +# define THRIFT_letohs(n) bswap_16(n) +# endif /* GNUC & GLIBC */ +#elif __THRIFT_BYTE_ORDER == __THRIFT_LITTLE_ENDIAN +# define THRIFT_htolell(n) (n) +# define THRIFT_letohll(n) (n) +# define THRIFT_htolel(n) (n) +# define THRIFT_letohl(n) (n) +# define THRIFT_htoles(n) (n) +# define THRIFT_letohs(n) (n) +# if defined(__GNUC__) && defined(__GLIBC__) +# include +# define THRIFT_ntohll(n) bswap_64(n) +# define THRIFT_htonll(n) bswap_64(n) +# elif defined(_MSC_VER) /* Microsoft Visual C++ */ +# define THRIFT_ntohll(n) ( _byteswap_uint64((uint64_t)n) ) +# define THRIFT_htonll(n) ( _byteswap_uint64((uint64_t)n) ) +# elif !defined(THRIFT_ntohll) /* Not GNUC/GLIBC or MSVC */ +# define THRIFT_ntohll(n) ( (((uint64_t)ntohl((uint32_t)n)) << 32) + ntohl((uint32_t)(n >> 32)) ) +# define THRIFT_htonll(n) ( (((uint64_t)htonl((uint32_t)n)) << 32) + htonl((uint32_t)(n >> 32)) ) +# endif /* GNUC/GLIBC or MSVC or something else */ +#else /* __THRIFT_BYTE_ORDER */ +# error "Can't define THRIFT_htonll or THRIFT_ntohll!" +#endif + +namespace apache { +namespace thrift { +namespace protocol { + +using apache::thrift::transport::TTransport; + +/** + * Enumerated definition of the types that the Thrift protocol supports. + * Take special note of the T_END type which is used specifically to mark + * the end of a sequence of fields. + */ +enum TType { + T_STOP = 0, + T_VOID = 1, + T_BOOL = 2, + T_BYTE = 3, + T_I08 = 3, + T_I16 = 6, + T_I32 = 8, + T_U64 = 9, + T_I64 = 10, + T_DOUBLE = 4, + T_STRING = 11, + T_UTF7 = 11, + T_STRUCT = 12, + T_MAP = 13, + T_SET = 14, + T_LIST = 15, + T_UTF8 = 16, + T_UTF16 = 17 +}; + +/** + * Enumerated definition of the message types that the Thrift protocol + * supports. + */ +enum TMessageType { + T_CALL = 1, + T_REPLY = 2, + T_EXCEPTION = 3, + T_ONEWAY = 4 +}; + +static const uint32_t DEFAULT_RECURSION_LIMIT = 64; + +/** + * Abstract class for a thrift protocol driver. These are all the methods that + * a protocol must implement. Essentially, there must be some way of reading + * and writing all the base types, plus a mechanism for writing out structs + * with indexed fields. + * + * TProtocol objects should not be shared across multiple encoding contexts, + * as they may need to maintain internal state in some protocols (i.e. XML). + * Note that is is acceptable for the TProtocol module to do its own internal + * buffered reads/writes to the underlying TTransport where appropriate (i.e. + * when parsing an input XML stream, reading should be batched rather than + * looking ahead character by character for a close tag). + * + */ +class TProtocol { +public: + virtual ~TProtocol(); + + /** + * Writing functions. + */ + + virtual uint32_t writeMessageBegin_virt(const std::string& name, + const TMessageType messageType, + const int32_t seqid) = 0; + + virtual uint32_t writeMessageEnd_virt() = 0; + + virtual uint32_t writeStructBegin_virt(const char* name) = 0; + + virtual uint32_t writeStructEnd_virt() = 0; + + virtual uint32_t writeFieldBegin_virt(const char* name, + const TType fieldType, + const int16_t fieldId) = 0; + + virtual uint32_t writeFieldEnd_virt() = 0; + + virtual uint32_t writeFieldStop_virt() = 0; + + virtual uint32_t writeMapBegin_virt(const TType keyType, const TType valType, const uint32_t size) + = 0; + + virtual uint32_t writeMapEnd_virt() = 0; + + virtual uint32_t writeListBegin_virt(const TType elemType, const uint32_t size) = 0; + + virtual uint32_t writeListEnd_virt() = 0; + + virtual uint32_t writeSetBegin_virt(const TType elemType, const uint32_t size) = 0; + + virtual uint32_t writeSetEnd_virt() = 0; + + virtual uint32_t writeBool_virt(const bool value) = 0; + + virtual uint32_t writeByte_virt(const int8_t byte) = 0; + + virtual uint32_t writeI16_virt(const int16_t i16) = 0; + + virtual uint32_t writeI32_virt(const int32_t i32) = 0; + + virtual uint32_t writeI64_virt(const int64_t i64) = 0; + + virtual uint32_t writeDouble_virt(const double dub) = 0; + + virtual uint32_t writeString_virt(const std::string& str) = 0; + + virtual uint32_t writeBinary_virt(const std::string& str) = 0; + + uint32_t writeMessageBegin(const std::string& name, + const TMessageType messageType, + const int32_t seqid) { + T_VIRTUAL_CALL(); + return writeMessageBegin_virt(name, messageType, seqid); + } + + uint32_t writeMessageEnd() { + T_VIRTUAL_CALL(); + return writeMessageEnd_virt(); + } + + uint32_t writeStructBegin(const char* name) { + T_VIRTUAL_CALL(); + return writeStructBegin_virt(name); + } + + uint32_t writeStructEnd() { + T_VIRTUAL_CALL(); + return writeStructEnd_virt(); + } + + uint32_t writeFieldBegin(const char* name, const TType fieldType, const int16_t fieldId) { + T_VIRTUAL_CALL(); + return writeFieldBegin_virt(name, fieldType, fieldId); + } + + uint32_t writeFieldEnd() { + T_VIRTUAL_CALL(); + return writeFieldEnd_virt(); + } + + uint32_t writeFieldStop() { + T_VIRTUAL_CALL(); + return writeFieldStop_virt(); + } + + uint32_t writeMapBegin(const TType keyType, const TType valType, const uint32_t size) { + T_VIRTUAL_CALL(); + return writeMapBegin_virt(keyType, valType, size); + } + + uint32_t writeMapEnd() { + T_VIRTUAL_CALL(); + return writeMapEnd_virt(); + } + + uint32_t writeListBegin(const TType elemType, const uint32_t size) { + T_VIRTUAL_CALL(); + return writeListBegin_virt(elemType, size); + } + + uint32_t writeListEnd() { + T_VIRTUAL_CALL(); + return writeListEnd_virt(); + } + + uint32_t writeSetBegin(const TType elemType, const uint32_t size) { + T_VIRTUAL_CALL(); + return writeSetBegin_virt(elemType, size); + } + + uint32_t writeSetEnd() { + T_VIRTUAL_CALL(); + return writeSetEnd_virt(); + } + + uint32_t writeBool(const bool value) { + T_VIRTUAL_CALL(); + return writeBool_virt(value); + } + + uint32_t writeByte(const int8_t byte) { + T_VIRTUAL_CALL(); + return writeByte_virt(byte); + } + + uint32_t writeI16(const int16_t i16) { + T_VIRTUAL_CALL(); + return writeI16_virt(i16); + } + + uint32_t writeI32(const int32_t i32) { + T_VIRTUAL_CALL(); + return writeI32_virt(i32); + } + + uint32_t writeI64(const int64_t i64) { + T_VIRTUAL_CALL(); + return writeI64_virt(i64); + } + + uint32_t writeDouble(const double dub) { + T_VIRTUAL_CALL(); + return writeDouble_virt(dub); + } + + uint32_t writeString(const std::string& str) { + T_VIRTUAL_CALL(); + return writeString_virt(str); + } + + uint32_t writeBinary(const std::string& str) { + T_VIRTUAL_CALL(); + return writeBinary_virt(str); + } + + /** + * Reading functions + */ + + virtual uint32_t readMessageBegin_virt(std::string& name, + TMessageType& messageType, + int32_t& seqid) = 0; + + virtual uint32_t readMessageEnd_virt() = 0; + + virtual uint32_t readStructBegin_virt(std::string& name) = 0; + + virtual uint32_t readStructEnd_virt() = 0; + + virtual uint32_t readFieldBegin_virt(std::string& name, TType& fieldType, int16_t& fieldId) = 0; + + virtual uint32_t readFieldEnd_virt() = 0; + + virtual uint32_t readMapBegin_virt(TType& keyType, TType& valType, uint32_t& size) = 0; + + virtual uint32_t readMapEnd_virt() = 0; + + virtual uint32_t readListBegin_virt(TType& elemType, uint32_t& size) = 0; + + virtual uint32_t readListEnd_virt() = 0; + + virtual uint32_t readSetBegin_virt(TType& elemType, uint32_t& size) = 0; + + virtual uint32_t readSetEnd_virt() = 0; + + virtual uint32_t readBool_virt(bool& value) = 0; + + virtual uint32_t readBool_virt(std::vector::reference value) = 0; + + virtual uint32_t readByte_virt(int8_t& byte) = 0; + + virtual uint32_t readI16_virt(int16_t& i16) = 0; + + virtual uint32_t readI32_virt(int32_t& i32) = 0; + + virtual uint32_t readI64_virt(int64_t& i64) = 0; + + virtual uint32_t readDouble_virt(double& dub) = 0; + + virtual uint32_t readString_virt(std::string& str) = 0; + + virtual uint32_t readBinary_virt(std::string& str) = 0; + + uint32_t readMessageBegin(std::string& name, TMessageType& messageType, int32_t& seqid) { + T_VIRTUAL_CALL(); + return readMessageBegin_virt(name, messageType, seqid); + } + + uint32_t readMessageEnd() { + T_VIRTUAL_CALL(); + return readMessageEnd_virt(); + } + + uint32_t readStructBegin(std::string& name) { + T_VIRTUAL_CALL(); + return readStructBegin_virt(name); + } + + uint32_t readStructEnd() { + T_VIRTUAL_CALL(); + return readStructEnd_virt(); + } + + uint32_t readFieldBegin(std::string& name, TType& fieldType, int16_t& fieldId) { + T_VIRTUAL_CALL(); + return readFieldBegin_virt(name, fieldType, fieldId); + } + + uint32_t readFieldEnd() { + T_VIRTUAL_CALL(); + return readFieldEnd_virt(); + } + + uint32_t readMapBegin(TType& keyType, TType& valType, uint32_t& size) { + T_VIRTUAL_CALL(); + return readMapBegin_virt(keyType, valType, size); + } + + uint32_t readMapEnd() { + T_VIRTUAL_CALL(); + return readMapEnd_virt(); + } + + uint32_t readListBegin(TType& elemType, uint32_t& size) { + T_VIRTUAL_CALL(); + return readListBegin_virt(elemType, size); + } + + uint32_t readListEnd() { + T_VIRTUAL_CALL(); + return readListEnd_virt(); + } + + uint32_t readSetBegin(TType& elemType, uint32_t& size) { + T_VIRTUAL_CALL(); + return readSetBegin_virt(elemType, size); + } + + uint32_t readSetEnd() { + T_VIRTUAL_CALL(); + return readSetEnd_virt(); + } + + uint32_t readBool(bool& value) { + T_VIRTUAL_CALL(); + return readBool_virt(value); + } + + uint32_t readByte(int8_t& byte) { + T_VIRTUAL_CALL(); + return readByte_virt(byte); + } + + uint32_t readI16(int16_t& i16) { + T_VIRTUAL_CALL(); + return readI16_virt(i16); + } + + uint32_t readI32(int32_t& i32) { + T_VIRTUAL_CALL(); + return readI32_virt(i32); + } + + uint32_t readI64(int64_t& i64) { + T_VIRTUAL_CALL(); + return readI64_virt(i64); + } + + uint32_t readDouble(double& dub) { + T_VIRTUAL_CALL(); + return readDouble_virt(dub); + } + + uint32_t readString(std::string& str) { + T_VIRTUAL_CALL(); + return readString_virt(str); + } + + uint32_t readBinary(std::string& str) { + T_VIRTUAL_CALL(); + return readBinary_virt(str); + } + + /* + * std::vector is specialized for bool, and its elements are individual bits + * rather than bools. We need to define a different version of readBool() + * to work with std::vector. + */ + uint32_t readBool(std::vector::reference value) { + T_VIRTUAL_CALL(); + return readBool_virt(value); + } + + /** + * Method to arbitrarily skip over data. + */ + uint32_t skip(TType type) { + T_VIRTUAL_CALL(); + return skip_virt(type); + } + virtual uint32_t skip_virt(TType type); + + inline std::shared_ptr getTransport() { return ptrans_; } + + // TODO: remove these two calls, they are for backwards + // compatibility + inline std::shared_ptr getInputTransport() { return ptrans_; } + inline std::shared_ptr getOutputTransport() { return ptrans_; } + + // input and output recursion depth are kept separate so that one protocol + // can be used concurrently for both input and output. + void incrementInputRecursionDepth() { + if (recursion_limit_ < ++input_recursion_depth_) { + throw TProtocolException(TProtocolException::DEPTH_LIMIT); + } + } + void decrementInputRecursionDepth() { --input_recursion_depth_; } + + void incrementOutputRecursionDepth() { + if (recursion_limit_ < ++output_recursion_depth_) { + throw TProtocolException(TProtocolException::DEPTH_LIMIT); + } + } + void decrementOutputRecursionDepth() { --output_recursion_depth_; } + + uint32_t getRecursionLimit() const {return recursion_limit_;} + void setRecurisionLimit(uint32_t depth) {recursion_limit_ = depth;} + +protected: + TProtocol(std::shared_ptr ptrans) + : ptrans_(ptrans), input_recursion_depth_(0), output_recursion_depth_(0), recursion_limit_(DEFAULT_RECURSION_LIMIT) + {} + + std::shared_ptr ptrans_; + +private: + TProtocol() = default; + uint32_t input_recursion_depth_; + uint32_t output_recursion_depth_; + uint32_t recursion_limit_; +}; + +/** + * Constructs input and output protocol objects given transports. + */ +class TProtocolFactory { +public: + TProtocolFactory() = default; + + virtual ~TProtocolFactory(); + + virtual std::shared_ptr getProtocol(std::shared_ptr trans) = 0; + virtual std::shared_ptr getProtocol(std::shared_ptr inTrans, + std::shared_ptr outTrans) { + (void)outTrans; + return getProtocol(inTrans); + } +}; + +/** + * Dummy protocol class. + * + * This class does nothing, and should never be instantiated. + * It is used only by the generator code. + */ +class TDummyProtocol : public TProtocol {}; + +// This is the default / legacy choice +struct TNetworkBigEndian +{ + static uint16_t toWire16(uint16_t x) {return htons(x);} + static uint32_t toWire32(uint32_t x) {return htonl(x);} + static uint64_t toWire64(uint64_t x) {return THRIFT_htonll(x);} + static uint16_t fromWire16(uint16_t x) {return ntohs(x);} + static uint32_t fromWire32(uint32_t x) {return ntohl(x);} + static uint64_t fromWire64(uint64_t x) {return THRIFT_ntohll(x);} +}; + +// On most systems, this will be a bit faster than TNetworkBigEndian +struct TNetworkLittleEndian +{ + static uint16_t toWire16(uint16_t x) {return THRIFT_htoles(x);} + static uint32_t toWire32(uint32_t x) {return THRIFT_htolel(x);} + static uint64_t toWire64(uint64_t x) {return THRIFT_htolell(x);} + static uint16_t fromWire16(uint16_t x) {return THRIFT_letohs(x);} + static uint32_t fromWire32(uint32_t x) {return THRIFT_letohl(x);} + static uint64_t fromWire64(uint64_t x) {return THRIFT_letohll(x);} +}; + +struct TOutputRecursionTracker { + TProtocol &prot_; + TOutputRecursionTracker(TProtocol &prot) : prot_(prot) { + prot_.incrementOutputRecursionDepth(); + } + ~TOutputRecursionTracker() { + prot_.decrementOutputRecursionDepth(); + } +}; + +struct TInputRecursionTracker { + TProtocol &prot_; + TInputRecursionTracker(TProtocol &prot) : prot_(prot) { + prot_.incrementInputRecursionDepth(); + } + ~TInputRecursionTracker() { + prot_.decrementInputRecursionDepth(); + } +}; + +/** + * Helper template for implementing TProtocol::skip(). + * + * Templatized to avoid having to make virtual function calls. + */ +template +uint32_t skip(Protocol_& prot, TType type) { + TInputRecursionTracker tracker(prot); + + switch (type) { + case T_BOOL: { + bool boolv; + return prot.readBool(boolv); + } + case T_BYTE: { + int8_t bytev = 0; + return prot.readByte(bytev); + } + case T_I16: { + int16_t i16; + return prot.readI16(i16); + } + case T_I32: { + int32_t i32; + return prot.readI32(i32); + } + case T_I64: { + int64_t i64; + return prot.readI64(i64); + } + case T_DOUBLE: { + double dub; + return prot.readDouble(dub); + } + case T_STRING: { + std::string str; + return prot.readBinary(str); + } + case T_STRUCT: { + uint32_t result = 0; + std::string name; + int16_t fid; + TType ftype; + result += prot.readStructBegin(name); + while (true) { + result += prot.readFieldBegin(name, ftype, fid); + if (ftype == T_STOP) { + break; + } + result += skip(prot, ftype); + result += prot.readFieldEnd(); + } + result += prot.readStructEnd(); + return result; + } + case T_MAP: { + uint32_t result = 0; + TType keyType; + TType valType; + uint32_t i, size; + result += prot.readMapBegin(keyType, valType, size); + for (i = 0; i < size; i++) { + result += skip(prot, keyType); + result += skip(prot, valType); + } + result += prot.readMapEnd(); + return result; + } + case T_SET: { + uint32_t result = 0; + TType elemType; + uint32_t i, size; + result += prot.readSetBegin(elemType, size); + for (i = 0; i < size; i++) { + result += skip(prot, elemType); + } + result += prot.readSetEnd(); + return result; + } + case T_LIST: { + uint32_t result = 0; + TType elemType; + uint32_t i, size; + result += prot.readListBegin(elemType, size); + for (i = 0; i < size; i++) { + result += skip(prot, elemType); + } + result += prot.readListEnd(); + return result; + } + default: + break; + } + + throw TProtocolException(TProtocolException::INVALID_DATA, + "invalid TType"); +} + +}}} // apache::thrift::protocol + +#endif // #define _THRIFT_PROTOCOL_TPROTOCOL_H_ 1 diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TProtocolDecorator.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TProtocolDecorator.h new file mode 100644 index 000000000..5258159f1 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TProtocolDecorator.h @@ -0,0 +1,151 @@ +/* + * 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. + */ + +#ifndef THRIFT_TPROTOCOLDECORATOR_H_ +#define THRIFT_TPROTOCOLDECORATOR_H_ 1 + +#include +#include + +namespace apache { +namespace thrift { +namespace protocol { +using std::shared_ptr; + +/** + * TProtocolDecorator forwards all requests to an enclosed + * TProtocol instance, providing a way to author concise + * concrete decorator subclasses. + * + *

See p.175 of Design Patterns (by Gamma et al.)

+ * + * @see apache::thrift::protocol::TMultiplexedProtocol + */ +class TProtocolDecorator : public TProtocol { +public: + ~TProtocolDecorator() override = default; + + // Desc: Initializes the protocol decorator object. + TProtocolDecorator(shared_ptr proto) + : TProtocol(proto->getTransport()), protocol(proto) {} + + uint32_t writeMessageBegin_virt(const std::string& name, + const TMessageType messageType, + const int32_t seqid) override { + return protocol->writeMessageBegin(name, messageType, seqid); + } + uint32_t writeMessageEnd_virt() override { return protocol->writeMessageEnd(); } + uint32_t writeStructBegin_virt(const char* name) override { + return protocol->writeStructBegin(name); + } + uint32_t writeStructEnd_virt() override { return protocol->writeStructEnd(); } + + uint32_t writeFieldBegin_virt(const char* name, + const TType fieldType, + const int16_t fieldId) override { + return protocol->writeFieldBegin(name, fieldType, fieldId); + } + + uint32_t writeFieldEnd_virt() override { return protocol->writeFieldEnd(); } + uint32_t writeFieldStop_virt() override { return protocol->writeFieldStop(); } + + uint32_t writeMapBegin_virt(const TType keyType, + const TType valType, + const uint32_t size) override { + return protocol->writeMapBegin(keyType, valType, size); + } + + uint32_t writeMapEnd_virt() override { return protocol->writeMapEnd(); } + + uint32_t writeListBegin_virt(const TType elemType, const uint32_t size) override { + return protocol->writeListBegin(elemType, size); + } + uint32_t writeListEnd_virt() override { return protocol->writeListEnd(); } + + uint32_t writeSetBegin_virt(const TType elemType, const uint32_t size) override { + return protocol->writeSetBegin(elemType, size); + } + uint32_t writeSetEnd_virt() override { return protocol->writeSetEnd(); } + + uint32_t writeBool_virt(const bool value) override { return protocol->writeBool(value); } + uint32_t writeByte_virt(const int8_t byte) override { return protocol->writeByte(byte); } + uint32_t writeI16_virt(const int16_t i16) override { return protocol->writeI16(i16); } + uint32_t writeI32_virt(const int32_t i32) override { return protocol->writeI32(i32); } + uint32_t writeI64_virt(const int64_t i64) override { return protocol->writeI64(i64); } + + uint32_t writeDouble_virt(const double dub) override { return protocol->writeDouble(dub); } + uint32_t writeString_virt(const std::string& str) override { return protocol->writeString(str); } + uint32_t writeBinary_virt(const std::string& str) override { return protocol->writeBinary(str); } + + uint32_t readMessageBegin_virt(std::string& name, + TMessageType& messageType, + int32_t& seqid) override { + return protocol->readMessageBegin(name, messageType, seqid); + } + uint32_t readMessageEnd_virt() override { return protocol->readMessageEnd(); } + + uint32_t readStructBegin_virt(std::string& name) override { + return protocol->readStructBegin(name); + } + uint32_t readStructEnd_virt() override { return protocol->readStructEnd(); } + + uint32_t readFieldBegin_virt(std::string& name, TType& fieldType, int16_t& fieldId) override { + return protocol->readFieldBegin(name, fieldType, fieldId); + } + uint32_t readFieldEnd_virt() override { return protocol->readFieldEnd(); } + + uint32_t readMapBegin_virt(TType& keyType, TType& valType, uint32_t& size) override { + return protocol->readMapBegin(keyType, valType, size); + } + uint32_t readMapEnd_virt() override { return protocol->readMapEnd(); } + + uint32_t readListBegin_virt(TType& elemType, uint32_t& size) override { + return protocol->readListBegin(elemType, size); + } + uint32_t readListEnd_virt() override { return protocol->readListEnd(); } + + uint32_t readSetBegin_virt(TType& elemType, uint32_t& size) override { + return protocol->readSetBegin(elemType, size); + } + uint32_t readSetEnd_virt() override { return protocol->readSetEnd(); } + + uint32_t readBool_virt(bool& value) override { return protocol->readBool(value); } + uint32_t readBool_virt(std::vector::reference value) override { + return protocol->readBool(value); + } + + uint32_t readByte_virt(int8_t& byte) override { return protocol->readByte(byte); } + + uint32_t readI16_virt(int16_t& i16) override { return protocol->readI16(i16); } + uint32_t readI32_virt(int32_t& i32) override { return protocol->readI32(i32); } + uint32_t readI64_virt(int64_t& i64) override { return protocol->readI64(i64); } + + uint32_t readDouble_virt(double& dub) override { return protocol->readDouble(dub); } + + uint32_t readString_virt(std::string& str) override { return protocol->readString(str); } + uint32_t readBinary_virt(std::string& str) override { return protocol->readBinary(str); } + +private: + shared_ptr protocol; +}; +} +} +} + +#endif // THRIFT_TPROTOCOLDECORATOR_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TProtocolException.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TProtocolException.h new file mode 100644 index 000000000..4ace9046e --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TProtocolException.h @@ -0,0 +1,107 @@ +/* + * 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. + */ + +#ifndef _THRIFT_PROTOCOL_TPROTOCOLEXCEPTION_H_ +#define _THRIFT_PROTOCOL_TPROTOCOLEXCEPTION_H_ 1 + +#include + +namespace apache { +namespace thrift { +namespace protocol { + +/** + * Class to encapsulate all the possible types of protocol errors that may + * occur in various protocol systems. This provides a sort of generic + * wrapper around the vague UNIX E_ error codes that lets a common code + * base of error handling to be used for various types of protocols, i.e. + * pipes etc. + * + */ +class TProtocolException : public apache::thrift::TException { +public: + /** + * Error codes for the various types of exceptions. + */ + enum TProtocolExceptionType { + UNKNOWN = 0, + INVALID_DATA = 1, + NEGATIVE_SIZE = 2, + SIZE_LIMIT = 3, + BAD_VERSION = 4, + NOT_IMPLEMENTED = 5, + DEPTH_LIMIT = 6 + }; + + TProtocolException() : apache::thrift::TException(), type_(UNKNOWN) {} + + TProtocolException(TProtocolExceptionType type) : apache::thrift::TException(), type_(type) {} + + TProtocolException(const std::string& message) + : apache::thrift::TException(message), type_(UNKNOWN) {} + + TProtocolException(TProtocolExceptionType type, const std::string& message) + : apache::thrift::TException(message), type_(type) {} + + ~TProtocolException() noexcept override = default; + + /** + * Returns an error code that provides information about the type of error + * that has occurred. + * + * @return Error code + */ + TProtocolExceptionType getType() const { return type_; } + + const char* what() const noexcept override { + if (message_.empty()) { + switch (type_) { + case UNKNOWN: + return "TProtocolException: Unknown protocol exception"; + case INVALID_DATA: + return "TProtocolException: Invalid data"; + case NEGATIVE_SIZE: + return "TProtocolException: Negative size"; + case SIZE_LIMIT: + return "TProtocolException: Exceeded size limit"; + case BAD_VERSION: + return "TProtocolException: Invalid version"; + case NOT_IMPLEMENTED: + return "TProtocolException: Not implemented"; + case DEPTH_LIMIT: + return "TProtocolException: Exceeded depth limit"; + default: + return "TProtocolException: (Invalid exception type)"; + } + } else { + return message_.c_str(); + } + } + +protected: + /** + * Error code + */ + TProtocolExceptionType type_; +}; +} +} +} // apache::thrift::protocol + +#endif // #ifndef _THRIFT_PROTOCOL_TPROTOCOLEXCEPTION_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TProtocolTap.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TProtocolTap.h new file mode 100644 index 000000000..d000ba61a --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TProtocolTap.h @@ -0,0 +1,177 @@ +/* + * 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. + */ + +#ifndef _THRIFT_PROTOCOL_TPROTOCOLTAP_H_ +#define _THRIFT_PROTOCOL_TPROTOCOLTAP_H_ 1 + +#include + +namespace apache { +namespace thrift { +namespace protocol { + +using apache::thrift::transport::TTransport; + +/** + * Puts a wiretap on a protocol object. Any reads to this class are passed + * through to an enclosed protocol object, but also mirrored as write to a + * second protocol object. + * + */ +class TProtocolTap : public TVirtualProtocol { +public: + TProtocolTap(std::shared_ptr source, std::shared_ptr sink) + : TVirtualProtocol(source->getTransport()), source_(source), sink_(sink) {} + + uint32_t readMessageBegin(std::string& name, TMessageType& messageType, int32_t& seqid) { + uint32_t rv = source_->readMessageBegin(name, messageType, seqid); + sink_->writeMessageBegin(name, messageType, seqid); + return rv; + } + + uint32_t readMessageEnd() { + uint32_t rv = source_->readMessageEnd(); + sink_->writeMessageEnd(); + return rv; + } + + uint32_t readStructBegin(std::string& name) { + uint32_t rv = source_->readStructBegin(name); + sink_->writeStructBegin(name.c_str()); + return rv; + } + + uint32_t readStructEnd() { + uint32_t rv = source_->readStructEnd(); + sink_->writeStructEnd(); + return rv; + } + + uint32_t readFieldBegin(std::string& name, TType& fieldType, int16_t& fieldId) { + uint32_t rv = source_->readFieldBegin(name, fieldType, fieldId); + if (fieldType == T_STOP) { + sink_->writeFieldStop(); + } else { + sink_->writeFieldBegin(name.c_str(), fieldType, fieldId); + } + return rv; + } + + uint32_t readFieldEnd() { + uint32_t rv = source_->readFieldEnd(); + sink_->writeFieldEnd(); + return rv; + } + + uint32_t readMapBegin(TType& keyType, TType& valType, uint32_t& size) { + uint32_t rv = source_->readMapBegin(keyType, valType, size); + sink_->writeMapBegin(keyType, valType, size); + return rv; + } + + uint32_t readMapEnd() { + uint32_t rv = source_->readMapEnd(); + sink_->writeMapEnd(); + return rv; + } + + uint32_t readListBegin(TType& elemType, uint32_t& size) { + uint32_t rv = source_->readListBegin(elemType, size); + sink_->writeListBegin(elemType, size); + return rv; + } + + uint32_t readListEnd() { + uint32_t rv = source_->readListEnd(); + sink_->writeListEnd(); + return rv; + } + + uint32_t readSetBegin(TType& elemType, uint32_t& size) { + uint32_t rv = source_->readSetBegin(elemType, size); + sink_->writeSetBegin(elemType, size); + return rv; + } + + uint32_t readSetEnd() { + uint32_t rv = source_->readSetEnd(); + sink_->writeSetEnd(); + return rv; + } + + uint32_t readBool(bool& value) { + uint32_t rv = source_->readBool(value); + sink_->writeBool(value); + return rv; + } + + // Provide the default readBool() implementation for std::vector + using TVirtualProtocol::readBool; + + uint32_t readByte(int8_t& byte) { + uint32_t rv = source_->readByte(byte); + sink_->writeByte(byte); + return rv; + } + + uint32_t readI16(int16_t& i16) { + uint32_t rv = source_->readI16(i16); + sink_->writeI16(i16); + return rv; + } + + uint32_t readI32(int32_t& i32) { + uint32_t rv = source_->readI32(i32); + sink_->writeI32(i32); + return rv; + } + + uint32_t readI64(int64_t& i64) { + uint32_t rv = source_->readI64(i64); + sink_->writeI64(i64); + return rv; + } + + uint32_t readDouble(double& dub) { + uint32_t rv = source_->readDouble(dub); + sink_->writeDouble(dub); + return rv; + } + + uint32_t readString(std::string& str) { + uint32_t rv = source_->readString(str); + sink_->writeString(str); + return rv; + } + + uint32_t readBinary(std::string& str) { + uint32_t rv = source_->readBinary(str); + sink_->writeBinary(str); + return rv; + } + +private: + std::shared_ptr source_; + std::shared_ptr sink_; +}; +} +} +} // apache::thrift::protocol + +#endif // #define _THRIFT_PROTOCOL_TPROTOCOLTAP_H_ 1 diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TProtocolTypes.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TProtocolTypes.h new file mode 100644 index 000000000..6898b24eb --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TProtocolTypes.h @@ -0,0 +1,36 @@ +/* + * 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. + */ + +#ifndef THRIFT_PROTOCOL_TPROTOCOLTYPES_H_ +#define THRIFT_PROTOCOL_TPROTOCOLTYPES_H_ 1 + +namespace apache { +namespace thrift { +namespace protocol { + +enum PROTOCOL_TYPES { + T_BINARY_PROTOCOL = 0, + T_JSON_PROTOCOL = 1, + T_COMPACT_PROTOCOL = 2, +}; +} +} +} // apache::thrift::protocol + +#endif // #define _THRIFT_PROTOCOL_TPROTOCOLTYPES_H_ 1 diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TVirtualProtocol.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TVirtualProtocol.h new file mode 100644 index 000000000..b7fe929af --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/protocol/TVirtualProtocol.h @@ -0,0 +1,513 @@ +/* + * 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. + */ + +#ifndef _THRIFT_PROTOCOL_TVIRTUALPROTOCOL_H_ +#define _THRIFT_PROTOCOL_TVIRTUALPROTOCOL_H_ 1 + +#include + +namespace apache { +namespace thrift { +namespace protocol { + +using apache::thrift::transport::TTransport; + +/** + * Helper class that provides default implementations of TProtocol methods. + * + * This class provides default implementations of the non-virtual TProtocol + * methods. It exists primarily so TVirtualProtocol can derive from it. It + * prevents TVirtualProtocol methods from causing infinite recursion if the + * non-virtual methods are not overridden by the TVirtualProtocol subclass. + * + * You probably don't want to use this class directly. Use TVirtualProtocol + * instead. + */ +class TProtocolDefaults : public TProtocol { +public: + uint32_t readMessageBegin(std::string& name, TMessageType& messageType, int32_t& seqid) { + (void)name; + (void)messageType; + (void)seqid; + throw TProtocolException(TProtocolException::NOT_IMPLEMENTED, + "this protocol does not support reading (yet)."); + } + + uint32_t readMessageEnd() { + throw TProtocolException(TProtocolException::NOT_IMPLEMENTED, + "this protocol does not support reading (yet)."); + } + + uint32_t readStructBegin(std::string& name) { + (void)name; + throw TProtocolException(TProtocolException::NOT_IMPLEMENTED, + "this protocol does not support reading (yet)."); + } + + uint32_t readStructEnd() { + throw TProtocolException(TProtocolException::NOT_IMPLEMENTED, + "this protocol does not support reading (yet)."); + } + + uint32_t readFieldBegin(std::string& name, TType& fieldType, int16_t& fieldId) { + (void)name; + (void)fieldType; + (void)fieldId; + throw TProtocolException(TProtocolException::NOT_IMPLEMENTED, + "this protocol does not support reading (yet)."); + } + + uint32_t readFieldEnd() { + throw TProtocolException(TProtocolException::NOT_IMPLEMENTED, + "this protocol does not support reading (yet)."); + } + + uint32_t readMapBegin(TType& keyType, TType& valType, uint32_t& size) { + (void)keyType; + (void)valType; + (void)size; + throw TProtocolException(TProtocolException::NOT_IMPLEMENTED, + "this protocol does not support reading (yet)."); + } + + uint32_t readMapEnd() { + throw TProtocolException(TProtocolException::NOT_IMPLEMENTED, + "this protocol does not support reading (yet)."); + } + + uint32_t readListBegin(TType& elemType, uint32_t& size) { + (void)elemType; + (void)size; + throw TProtocolException(TProtocolException::NOT_IMPLEMENTED, + "this protocol does not support reading (yet)."); + } + + uint32_t readListEnd() { + throw TProtocolException(TProtocolException::NOT_IMPLEMENTED, + "this protocol does not support reading (yet)."); + } + + uint32_t readSetBegin(TType& elemType, uint32_t& size) { + (void)elemType; + (void)size; + throw TProtocolException(TProtocolException::NOT_IMPLEMENTED, + "this protocol does not support reading (yet)."); + } + + uint32_t readSetEnd() { + throw TProtocolException(TProtocolException::NOT_IMPLEMENTED, + "this protocol does not support reading (yet)."); + } + + uint32_t readBool(bool& value) { + (void)value; + throw TProtocolException(TProtocolException::NOT_IMPLEMENTED, + "this protocol does not support reading (yet)."); + } + + uint32_t readBool(std::vector::reference value) { + (void)value; + throw TProtocolException(TProtocolException::NOT_IMPLEMENTED, + "this protocol does not support reading (yet)."); + } + + uint32_t readByte(int8_t& byte) { + (void)byte; + throw TProtocolException(TProtocolException::NOT_IMPLEMENTED, + "this protocol does not support reading (yet)."); + } + + uint32_t readI16(int16_t& i16) { + (void)i16; + throw TProtocolException(TProtocolException::NOT_IMPLEMENTED, + "this protocol does not support reading (yet)."); + } + + uint32_t readI32(int32_t& i32) { + (void)i32; + throw TProtocolException(TProtocolException::NOT_IMPLEMENTED, + "this protocol does not support reading (yet)."); + } + + uint32_t readI64(int64_t& i64) { + (void)i64; + throw TProtocolException(TProtocolException::NOT_IMPLEMENTED, + "this protocol does not support reading (yet)."); + } + + uint32_t readDouble(double& dub) { + (void)dub; + throw TProtocolException(TProtocolException::NOT_IMPLEMENTED, + "this protocol does not support reading (yet)."); + } + + uint32_t readString(std::string& str) { + (void)str; + throw TProtocolException(TProtocolException::NOT_IMPLEMENTED, + "this protocol does not support reading (yet)."); + } + + uint32_t readBinary(std::string& str) { + (void)str; + throw TProtocolException(TProtocolException::NOT_IMPLEMENTED, + "this protocol does not support reading (yet)."); + } + + uint32_t writeMessageBegin(const std::string& name, + const TMessageType messageType, + const int32_t seqid) { + (void)name; + (void)messageType; + (void)seqid; + throw TProtocolException(TProtocolException::NOT_IMPLEMENTED, + "this protocol does not support writing (yet)."); + } + + uint32_t writeMessageEnd() { + throw TProtocolException(TProtocolException::NOT_IMPLEMENTED, + "this protocol does not support writing (yet)."); + } + + uint32_t writeStructBegin(const char* name) { + (void)name; + throw TProtocolException(TProtocolException::NOT_IMPLEMENTED, + "this protocol does not support writing (yet)."); + } + + uint32_t writeStructEnd() { + throw TProtocolException(TProtocolException::NOT_IMPLEMENTED, + "this protocol does not support writing (yet)."); + } + + uint32_t writeFieldBegin(const char* name, const TType fieldType, const int16_t fieldId) { + (void)name; + (void)fieldType; + (void)fieldId; + throw TProtocolException(TProtocolException::NOT_IMPLEMENTED, + "this protocol does not support writing (yet)."); + } + + uint32_t writeFieldEnd() { + throw TProtocolException(TProtocolException::NOT_IMPLEMENTED, + "this protocol does not support writing (yet)."); + } + + uint32_t writeFieldStop() { + throw TProtocolException(TProtocolException::NOT_IMPLEMENTED, + "this protocol does not support writing (yet)."); + } + + uint32_t writeMapBegin(const TType keyType, const TType valType, const uint32_t size) { + (void)keyType; + (void)valType; + (void)size; + throw TProtocolException(TProtocolException::NOT_IMPLEMENTED, + "this protocol does not support writing (yet)."); + } + + uint32_t writeMapEnd() { + throw TProtocolException(TProtocolException::NOT_IMPLEMENTED, + "this protocol does not support writing (yet)."); + } + + uint32_t writeListBegin(const TType elemType, const uint32_t size) { + (void)elemType; + (void)size; + throw TProtocolException(TProtocolException::NOT_IMPLEMENTED, + "this protocol does not support writing (yet)."); + } + + uint32_t writeListEnd() { + throw TProtocolException(TProtocolException::NOT_IMPLEMENTED, + "this protocol does not support writing (yet)."); + } + + uint32_t writeSetBegin(const TType elemType, const uint32_t size) { + (void)elemType; + (void)size; + throw TProtocolException(TProtocolException::NOT_IMPLEMENTED, + "this protocol does not support writing (yet)."); + } + + uint32_t writeSetEnd() { + throw TProtocolException(TProtocolException::NOT_IMPLEMENTED, + "this protocol does not support writing (yet)."); + } + + uint32_t writeBool(const bool value) { + (void)value; + throw TProtocolException(TProtocolException::NOT_IMPLEMENTED, + "this protocol does not support writing (yet)."); + } + + uint32_t writeByte(const int8_t byte) { + (void)byte; + throw TProtocolException(TProtocolException::NOT_IMPLEMENTED, + "this protocol does not support writing (yet)."); + } + + uint32_t writeI16(const int16_t i16) { + (void)i16; + throw TProtocolException(TProtocolException::NOT_IMPLEMENTED, + "this protocol does not support writing (yet)."); + } + + uint32_t writeI32(const int32_t i32) { + (void)i32; + throw TProtocolException(TProtocolException::NOT_IMPLEMENTED, + "this protocol does not support writing (yet)."); + } + + uint32_t writeI64(const int64_t i64) { + (void)i64; + throw TProtocolException(TProtocolException::NOT_IMPLEMENTED, + "this protocol does not support writing (yet)."); + } + + uint32_t writeDouble(const double dub) { + (void)dub; + throw TProtocolException(TProtocolException::NOT_IMPLEMENTED, + "this protocol does not support writing (yet)."); + } + + uint32_t writeString(const std::string& str) { + (void)str; + throw TProtocolException(TProtocolException::NOT_IMPLEMENTED, + "this protocol does not support writing (yet)."); + } + + uint32_t writeBinary(const std::string& str) { + (void)str; + throw TProtocolException(TProtocolException::NOT_IMPLEMENTED, + "this protocol does not support writing (yet)."); + } + + uint32_t skip(TType type) { return ::apache::thrift::protocol::skip(*this, type); } + +protected: + TProtocolDefaults(std::shared_ptr ptrans) : TProtocol(ptrans) {} +}; + +/** + * Concrete TProtocol classes should inherit from TVirtualProtocol + * so they don't have to manually override virtual methods. + */ +template +class TVirtualProtocol : public Super_ { +public: + /** + * Writing functions. + */ + + uint32_t writeMessageBegin_virt(const std::string& name, + const TMessageType messageType, + const int32_t seqid) override { + return static_cast(this)->writeMessageBegin(name, messageType, seqid); + } + + uint32_t writeMessageEnd_virt() override { + return static_cast(this)->writeMessageEnd(); + } + + uint32_t writeStructBegin_virt(const char* name) override { + return static_cast(this)->writeStructBegin(name); + } + + uint32_t writeStructEnd_virt() override { return static_cast(this)->writeStructEnd(); } + + uint32_t writeFieldBegin_virt(const char* name, + const TType fieldType, + const int16_t fieldId) override { + return static_cast(this)->writeFieldBegin(name, fieldType, fieldId); + } + + uint32_t writeFieldEnd_virt() override { return static_cast(this)->writeFieldEnd(); } + + uint32_t writeFieldStop_virt() override { return static_cast(this)->writeFieldStop(); } + + uint32_t writeMapBegin_virt(const TType keyType, + const TType valType, + const uint32_t size) override { + return static_cast(this)->writeMapBegin(keyType, valType, size); + } + + uint32_t writeMapEnd_virt() override { return static_cast(this)->writeMapEnd(); } + + uint32_t writeListBegin_virt(const TType elemType, const uint32_t size) override { + return static_cast(this)->writeListBegin(elemType, size); + } + + uint32_t writeListEnd_virt() override { return static_cast(this)->writeListEnd(); } + + uint32_t writeSetBegin_virt(const TType elemType, const uint32_t size) override { + return static_cast(this)->writeSetBegin(elemType, size); + } + + uint32_t writeSetEnd_virt() override { return static_cast(this)->writeSetEnd(); } + + uint32_t writeBool_virt(const bool value) override { + return static_cast(this)->writeBool(value); + } + + uint32_t writeByte_virt(const int8_t byte) override { + return static_cast(this)->writeByte(byte); + } + + uint32_t writeI16_virt(const int16_t i16) override { + return static_cast(this)->writeI16(i16); + } + + uint32_t writeI32_virt(const int32_t i32) override { + return static_cast(this)->writeI32(i32); + } + + uint32_t writeI64_virt(const int64_t i64) override { + return static_cast(this)->writeI64(i64); + } + + uint32_t writeDouble_virt(const double dub) override { + return static_cast(this)->writeDouble(dub); + } + + uint32_t writeString_virt(const std::string& str) override { + return static_cast(this)->writeString(str); + } + + uint32_t writeBinary_virt(const std::string& str) override { + return static_cast(this)->writeBinary(str); + } + + /** + * Reading functions + */ + + uint32_t readMessageBegin_virt(std::string& name, + TMessageType& messageType, + int32_t& seqid) override { + return static_cast(this)->readMessageBegin(name, messageType, seqid); + } + + uint32_t readMessageEnd_virt() override { return static_cast(this)->readMessageEnd(); } + + uint32_t readStructBegin_virt(std::string& name) override { + return static_cast(this)->readStructBegin(name); + } + + uint32_t readStructEnd_virt() override { return static_cast(this)->readStructEnd(); } + + uint32_t readFieldBegin_virt(std::string& name, TType& fieldType, int16_t& fieldId) override { + return static_cast(this)->readFieldBegin(name, fieldType, fieldId); + } + + uint32_t readFieldEnd_virt() override { return static_cast(this)->readFieldEnd(); } + + uint32_t readMapBegin_virt(TType& keyType, TType& valType, uint32_t& size) override { + return static_cast(this)->readMapBegin(keyType, valType, size); + } + + uint32_t readMapEnd_virt() override { return static_cast(this)->readMapEnd(); } + + uint32_t readListBegin_virt(TType& elemType, uint32_t& size) override { + return static_cast(this)->readListBegin(elemType, size); + } + + uint32_t readListEnd_virt() override { return static_cast(this)->readListEnd(); } + + uint32_t readSetBegin_virt(TType& elemType, uint32_t& size) override { + return static_cast(this)->readSetBegin(elemType, size); + } + + uint32_t readSetEnd_virt() override { return static_cast(this)->readSetEnd(); } + + uint32_t readBool_virt(bool& value) override { + return static_cast(this)->readBool(value); + } + + uint32_t readBool_virt(std::vector::reference value) override { + return static_cast(this)->readBool(value); + } + + uint32_t readByte_virt(int8_t& byte) override { + return static_cast(this)->readByte(byte); + } + + uint32_t readI16_virt(int16_t& i16) override { + return static_cast(this)->readI16(i16); + } + + uint32_t readI32_virt(int32_t& i32) override { + return static_cast(this)->readI32(i32); + } + + uint32_t readI64_virt(int64_t& i64) override { + return static_cast(this)->readI64(i64); + } + + uint32_t readDouble_virt(double& dub) override { + return static_cast(this)->readDouble(dub); + } + + uint32_t readString_virt(std::string& str) override { + return static_cast(this)->readString(str); + } + + uint32_t readBinary_virt(std::string& str) override { + return static_cast(this)->readBinary(str); + } + + uint32_t skip_virt(TType type) override { return static_cast(this)->skip(type); } + + /* + * Provide a default skip() implementation that uses non-virtual read + * methods. + * + * Note: subclasses that use TVirtualProtocol to derive from another protocol + * implementation (i.e., not TProtocolDefaults) should beware that this may + * override any non-default skip() implementation provided by the parent + * transport class. They may need to explicitly redefine skip() to call the + * correct parent implementation, if desired. + */ + uint32_t skip(TType type) { + auto* const prot = static_cast(this); + return ::apache::thrift::protocol::skip(*prot, type); + } + + /* + * Provide a default readBool() implementation for use with + * std::vector, that behaves the same as reading into a normal bool. + * + * Subclasses can override this if desired, but there normally shouldn't + * be a need to. + */ + uint32_t readBool(std::vector::reference value) { + bool b = false; + uint32_t ret = static_cast(this)->readBool(b); + value = b; + return ret; + } + using Super_::readBool; // so we don't hide readBool(bool&) + +protected: + TVirtualProtocol(std::shared_ptr ptrans) : Super_(ptrans) {} +}; +} +} +} // apache::thrift::protocol + +#endif // #define _THRIFT_PROTOCOL_TVIRTUALPROTOCOL_H_ 1 diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/qt/CMakeLists.txt b/src/jaegertracing/thrift/lib/cpp/src/thrift/qt/CMakeLists.txt new file mode 100644 index 000000000..04a9a316e --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/qt/CMakeLists.txt @@ -0,0 +1,28 @@ +# +# 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. +# + +set( thriftcppqt5_SOURCES + TQIODeviceTransport.cpp + TQTcpServer.cpp +) +set(CMAKE_AUTOMOC ON) +find_package(Qt5 REQUIRED COMPONENTS Core Network) +ADD_LIBRARY_THRIFT(thriftqt5 ${thriftcppqt5_SOURCES}) +TARGET_LINK_LIBRARIES_THRIFT(thriftqt5 Qt5::Core Qt5::Network) +TARGET_LINK_LIBRARIES_THRIFT_AGAINST_THRIFT_LIBRARY(thriftqt5 thrift) diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/qt/TQIODeviceTransport.cpp b/src/jaegertracing/thrift/lib/cpp/src/thrift/qt/TQIODeviceTransport.cpp new file mode 100644 index 000000000..1537fc60d --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/qt/TQIODeviceTransport.cpp @@ -0,0 +1,169 @@ +/* + * 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. + */ + +#include + +#include +#include + +#include +#include + +namespace apache { +namespace thrift { + +using std::shared_ptr; + +namespace transport { + +TQIODeviceTransport::TQIODeviceTransport(shared_ptr dev) : dev_(dev) { +} + +TQIODeviceTransport::~TQIODeviceTransport() { + dev_->close(); +} + +void TQIODeviceTransport::open() { + if (!isOpen()) { + throw TTransportException(TTransportException::NOT_OPEN, + "open(): underlying QIODevice isn't open"); + } +} + +bool TQIODeviceTransport::isOpen() const { + return dev_->isOpen(); +} + +bool TQIODeviceTransport::peek() { + return dev_->bytesAvailable() > 0; +} + +void TQIODeviceTransport::close() { + dev_->close(); +} + +uint32_t TQIODeviceTransport::readAll(uint8_t* buf, uint32_t len) { + uint32_t requestLen = len; + while (len) { + uint32_t readSize; + try { + readSize = read(buf, len); + } catch (...) { + if (len != requestLen) { + // something read already + return requestLen - len; + } + // error but nothing read yet + throw; + } + if (readSize == 0) { + dev_->waitForReadyRead(50); + } else { + buf += readSize; + len -= readSize; + } + } + return requestLen; +} + +uint32_t TQIODeviceTransport::read(uint8_t* buf, uint32_t len) { + uint32_t actualSize; + qint64 readSize; + + if (!dev_->isOpen()) { + throw TTransportException(TTransportException::NOT_OPEN, + "read(): underlying QIODevice is not open"); + } + + actualSize = (uint32_t)(std::min)((qint64)len, dev_->bytesAvailable()); + readSize = dev_->read(reinterpret_cast(buf), actualSize); + + if (readSize < 0) { + QAbstractSocket* socket; + if ((socket = qobject_cast(dev_.get()))) { + throw TTransportException(TTransportException::UNKNOWN, + "Failed to read() from QAbstractSocket", + socket->error()); + } + throw TTransportException(TTransportException::UNKNOWN, "Failed to read from from QIODevice"); + } + + return (uint32_t)readSize; +} + +void TQIODeviceTransport::write(const uint8_t* buf, uint32_t len) { + while (len) { + uint32_t written = write_partial(buf, len); + len -= written; + dev_->waitForBytesWritten(50); + } +} + +uint32_t TQIODeviceTransport::write_partial(const uint8_t* buf, uint32_t len) { + qint64 written; + + if (!dev_->isOpen()) { + throw TTransportException(TTransportException::NOT_OPEN, + "write_partial(): underlying QIODevice is not open"); + } + + written = dev_->write(reinterpret_cast(buf), len); + if (written < 0) { + QAbstractSocket* socket; + if ((socket = qobject_cast(dev_.get()))) { + throw TTransportException(TTransportException::UNKNOWN, + "write_partial(): failed to write to QAbstractSocket", + socket->error()); + } + + throw TTransportException(TTransportException::UNKNOWN, + "write_partial(): failed to write to underlying QIODevice"); + } + + return (uint32_t)written; +} + +void TQIODeviceTransport::flush() { + if (!dev_->isOpen()) { + throw TTransportException(TTransportException::NOT_OPEN, + "flush(): underlying QIODevice is not open"); + } + + QAbstractSocket* socket; + + if ((socket = qobject_cast(dev_.get()))) { + socket->flush(); + } else { + dev_->waitForBytesWritten(1); + } +} + +uint8_t* TQIODeviceTransport::borrow(uint8_t* buf, uint32_t* len) { + (void)buf; + (void)len; + return nullptr; +} + +void TQIODeviceTransport::consume(uint32_t len) { + (void)len; + throw TTransportException(TTransportException::UNKNOWN); +} +} +} +} // apache::thrift::transport diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/qt/TQIODeviceTransport.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/qt/TQIODeviceTransport.h new file mode 100644 index 000000000..a3b511def --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/qt/TQIODeviceTransport.h @@ -0,0 +1,68 @@ +/* + * 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. + */ + +#ifndef _THRIFT_ASYNC_TQIODEVICE_TRANSPORT_H_ +#define _THRIFT_ASYNC_TQIODEVICE_TRANSPORT_H_ 1 + +#include + +#include + +class QIODevice; + +namespace apache { +namespace thrift { +namespace transport { + +/** + * Transport that operates on a QIODevice (socket, file, etc). + */ +class TQIODeviceTransport + : public apache::thrift::transport::TVirtualTransport { +public: + explicit TQIODeviceTransport(std::shared_ptr dev); + ~TQIODeviceTransport() override; + + void open() override; + bool isOpen() const override; + bool peek() override; + void close() override; + + uint32_t readAll(uint8_t* buf, uint32_t len); + uint32_t read(uint8_t* buf, uint32_t len); + + void write(const uint8_t* buf, uint32_t len); + uint32_t write_partial(const uint8_t* buf, uint32_t len); + + void flush() override; + + uint8_t* borrow(uint8_t* buf, uint32_t* len); + void consume(uint32_t len); + +private: + TQIODeviceTransport(const TQIODeviceTransport&); + TQIODeviceTransport& operator=(const TQIODeviceTransport&); + + std::shared_ptr dev_; +}; +} +} +} // apache::thrift::transport + +#endif // #ifndef _THRIFT_ASYNC_TQIODEVICE_TRANSPORT_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/qt/TQTcpServer.cpp b/src/jaegertracing/thrift/lib/cpp/src/thrift/qt/TQTcpServer.cpp new file mode 100644 index 000000000..04044823e --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/qt/TQTcpServer.cpp @@ -0,0 +1,151 @@ +/* + * 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. + */ + +#include +#include + +#include +#include + +#include +#include + +#include +#include + +using apache::thrift::protocol::TProtocol; +using apache::thrift::protocol::TProtocolFactory; +using apache::thrift::transport::TTransport; +using apache::thrift::transport::TTransportException; +using apache::thrift::transport::TQIODeviceTransport; +using std::bind; +using std::function; +using std::placeholders::_1; +using std::shared_ptr; + +QT_USE_NAMESPACE + +namespace apache { +namespace thrift { +namespace async { + +struct TQTcpServer::ConnectionContext { + shared_ptr connection_; + shared_ptr transport_; + shared_ptr iprot_; + shared_ptr oprot_; + + explicit ConnectionContext(shared_ptr connection, + shared_ptr transport, + shared_ptr iprot, + shared_ptr oprot) + : connection_(connection), transport_(transport), iprot_(iprot), oprot_(oprot) {} +}; + +TQTcpServer::TQTcpServer(shared_ptr server, + shared_ptr processor, + shared_ptr pfact, + QObject* parent) + : QObject(parent), server_(server), processor_(processor), pfact_(pfact) { + qRegisterMetaType("QTcpSocket*"); + connect(server.get(), SIGNAL(newConnection()), SLOT(processIncoming())); +} + +TQTcpServer::~TQTcpServer() = default; + +void TQTcpServer::processIncoming() { + while (server_->hasPendingConnections()) { + // take ownership of the QTcpSocket; technically it could be deleted + // when the QTcpServer is destroyed, but any real app should delete this + // class before deleting the QTcpServer that we are using + shared_ptr connection(server_->nextPendingConnection()); + + shared_ptr transport; + shared_ptr iprot; + shared_ptr oprot; + + try { + transport = shared_ptr(new TQIODeviceTransport(connection)); + iprot = shared_ptr(pfact_->getProtocol(transport)); + oprot = shared_ptr(pfact_->getProtocol(transport)); + } catch (...) { + qWarning("[TQTcpServer] Failed to initialize transports/protocols"); + continue; + } + + ctxMap_[connection.get()] + = std::make_shared(connection, transport, iprot, oprot); + + connect(connection.get(), SIGNAL(readyRead()), SLOT(beginDecode())); + + connect(connection.get(), SIGNAL(disconnected()), SLOT(socketClosed())); + } +} + +void TQTcpServer::beginDecode() { + auto* connection(qobject_cast(sender())); + Q_ASSERT(connection); + + if (ctxMap_.find(connection) == ctxMap_.end()) { + qWarning("[TQTcpServer] Got data on an unknown QTcpSocket"); + return; + } + + shared_ptr ctx = ctxMap_[connection]; + + try { + processor_ + ->process(bind(&TQTcpServer::finish, this, ctx, _1), + ctx->iprot_, + ctx->oprot_); + } catch (const TTransportException& ex) { + qWarning("[TQTcpServer] TTransportException during processing: '%s'", ex.what()); + scheduleDeleteConnectionContext(connection); + } catch (...) { + qWarning("[TQTcpServer] Unknown processor exception"); + scheduleDeleteConnectionContext(connection); + } +} + +void TQTcpServer::socketClosed() { + auto* connection(qobject_cast(sender())); + Q_ASSERT(connection); + scheduleDeleteConnectionContext(connection); +} + +void TQTcpServer::deleteConnectionContext(QTcpSocket* connection) { + const ConnectionContextMap::size_type deleted = ctxMap_.erase(connection); + if (0 == deleted) { + qWarning("[TQTcpServer] Unknown QTcpSocket"); + } +} + +void TQTcpServer::scheduleDeleteConnectionContext(QTcpSocket* connection) { + QMetaObject::invokeMethod(this, "deleteConnectionContext", Qt::QueuedConnection, Q_ARG(QTcpSocket*, connection)); +} + +void TQTcpServer::finish(shared_ptr ctx, bool healthy) { + if (!healthy) { + qWarning("[TQTcpServer] Processor failed to process data successfully"); + deleteConnectionContext(ctx->connection_.get()); + } +} +} +} +} // apache::thrift::async diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/qt/TQTcpServer.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/qt/TQTcpServer.h new file mode 100644 index 000000000..25994ab8d --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/qt/TQTcpServer.h @@ -0,0 +1,81 @@ +/* + * 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. + */ + +#ifndef _THRIFT_TASYNC_QTCP_SERVER_H_ +#define _THRIFT_TASYNC_QTCP_SERVER_H_ + +#include +#include + +#include + +namespace apache { +namespace thrift { +namespace protocol { +class TProtocolFactory; +} +} +} // apache::thrift::protocol + +namespace apache { +namespace thrift { +namespace async { + +class TAsyncProcessor; + +/** + * Server that uses Qt to listen for connections. + * Simply give it a QTcpServer that is listening, along with an async + * processor and a protocol factory, and then run the Qt event loop. + */ +class TQTcpServer : public QObject { + Q_OBJECT +public: + TQTcpServer(std::shared_ptr server, + std::shared_ptr processor, + std::shared_ptr protocolFactory, + QObject* parent = nullptr); + ~TQTcpServer() override; + +private Q_SLOTS: + void processIncoming(); + void beginDecode(); + void socketClosed(); + void deleteConnectionContext(QTcpSocket* connection); + +private: + Q_DISABLE_COPY(TQTcpServer) + + struct ConnectionContext; + + void scheduleDeleteConnectionContext(QTcpSocket* connection); + void finish(std::shared_ptr ctx, bool healthy); + + std::shared_ptr server_; + std::shared_ptr processor_; + std::shared_ptr pfact_; + + typedef std::map > ConnectionContextMap; + ConnectionContextMap ctxMap_; +}; +} +} +} // apache::thrift::async + +#endif // #ifndef _THRIFT_TASYNC_QTCP_SERVER_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/server/TConnectedClient.cpp b/src/jaegertracing/thrift/lib/cpp/src/thrift/server/TConnectedClient.cpp new file mode 100644 index 000000000..9a78e3e9b --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/server/TConnectedClient.cpp @@ -0,0 +1,122 @@ +/* + * 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. + */ + +#include + +namespace apache { +namespace thrift { +namespace server { + +using apache::thrift::TProcessor; +using apache::thrift::protocol::TProtocol; +using apache::thrift::server::TServerEventHandler; +using apache::thrift::transport::TTransport; +using apache::thrift::transport::TTransportException; +using std::shared_ptr; +using std::string; + +TConnectedClient::TConnectedClient(const shared_ptr& processor, + const shared_ptr& inputProtocol, + const shared_ptr& outputProtocol, + const shared_ptr& eventHandler, + const shared_ptr& client) + + : processor_(processor), + inputProtocol_(inputProtocol), + outputProtocol_(outputProtocol), + eventHandler_(eventHandler), + client_(client), + opaqueContext_(nullptr) { +} + +TConnectedClient::~TConnectedClient() = default; + +void TConnectedClient::run() { + if (eventHandler_) { + opaqueContext_ = eventHandler_->createContext(inputProtocol_, outputProtocol_); + } + + for (bool done = false; !done;) { + if (eventHandler_) { + eventHandler_->processContext(opaqueContext_, client_); + } + + try { + if (!processor_->process(inputProtocol_, outputProtocol_, opaqueContext_)) { + break; + } + } catch (const TTransportException& ttx) { + switch (ttx.getType()) { + case TTransportException::END_OF_FILE: + case TTransportException::INTERRUPTED: + case TTransportException::TIMED_OUT: + // Client disconnected or was interrupted or did not respond within the receive timeout. + // No logging needed. Done. + done = true; + break; + + default: { + // All other transport exceptions are logged. + // State of connection is unknown. Done. + string errStr = string("TConnectedClient died: ") + ttx.what(); + GlobalOutput(errStr.c_str()); + done = true; + break; + } + } + } catch (const TException& tex) { + string errStr = string("TConnectedClient processing exception: ") + tex.what(); + GlobalOutput(errStr.c_str()); + // Disconnect from client, because we could not process the message. + done = true; + } + } + + cleanup(); +} + +void TConnectedClient::cleanup() { + if (eventHandler_) { + eventHandler_->deleteContext(opaqueContext_, inputProtocol_, outputProtocol_); + } + + try { + inputProtocol_->getTransport()->close(); + } catch (const TTransportException& ttx) { + string errStr = string("TConnectedClient input close failed: ") + ttx.what(); + GlobalOutput(errStr.c_str()); + } + + try { + outputProtocol_->getTransport()->close(); + } catch (const TTransportException& ttx) { + string errStr = string("TConnectedClient output close failed: ") + ttx.what(); + GlobalOutput(errStr.c_str()); + } + + try { + client_->close(); + } catch (const TTransportException& ttx) { + string errStr = string("TConnectedClient client close failed: ") + ttx.what(); + GlobalOutput(errStr.c_str()); + } +} +} +} +} // apache::thrift::server diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/server/TConnectedClient.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/server/TConnectedClient.h new file mode 100644 index 000000000..071571a88 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/server/TConnectedClient.h @@ -0,0 +1,110 @@ +/* + * 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. + */ + +#ifndef _THRIFT_SERVER_TCONNECTEDCLIENT_H_ +#define _THRIFT_SERVER_TCONNECTEDCLIENT_H_ 1 + +#include +#include +#include +#include +#include + +namespace apache { +namespace thrift { +namespace server { + +/** + * This represents a client connected to a TServer. The + * processing loop for a client must provide some required + * functionality common to all implementations so it is + * encapsulated here. + */ + +class TConnectedClient : public apache::thrift::concurrency::Runnable { +public: + /** + * Constructor. + * + * @param[in] processor the TProcessor + * @param[in] inputProtocol the input TProtocol + * @param[in] outputProtocol the output TProtocol + * @param[in] eventHandler the server event handler + * @param[in] client the TTransport representing the client + */ + TConnectedClient( + const std::shared_ptr& processor, + const std::shared_ptr& inputProtocol, + const std::shared_ptr& outputProtocol, + const std::shared_ptr& eventHandler, + const std::shared_ptr& client); + + /** + * Destructor. + */ + ~TConnectedClient() override; + + /** + * Drive the client until it is done. + * The client processing loop is: + * + * [optional] call eventHandler->createContext once + * [optional] call eventHandler->processContext per request + * call processor->process per request + * handle expected transport exceptions: + * END_OF_FILE means the client is gone + * INTERRUPTED means the client was interrupted + * by TServerTransport::interruptChildren() + * handle unexpected transport exceptions by logging + * handle standard exceptions by logging + * handle unexpected exceptions by logging + * cleanup() + */ + void run() override /* override */; + +protected: + /** + * Cleanup after a client. This happens if the client disconnects, + * or if the server is stopped, or if an exception occurs. + * + * The cleanup processing is: + * [optional] call eventHandler->deleteContext once + * close the inputProtocol's TTransport + * close the outputProtocol's TTransport + * close the client + */ + virtual void cleanup(); + +private: + std::shared_ptr processor_; + std::shared_ptr inputProtocol_; + std::shared_ptr outputProtocol_; + std::shared_ptr eventHandler_; + std::shared_ptr client_; + + /** + * Context acquired from the eventHandler_ if one exists. + */ + void* opaqueContext_; +}; +} +} +} + +#endif // #ifndef _THRIFT_SERVER_TCONNECTEDCLIENT_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/server/TNonblockingServer.cpp b/src/jaegertracing/thrift/lib/cpp/src/thrift/server/TNonblockingServer.cpp new file mode 100644 index 000000000..eea0427d2 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/server/TNonblockingServer.cpp @@ -0,0 +1,1518 @@ +/* + * 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. + */ + +#include + +#include +#include +#include +#include +#include + +#include +#include + +#ifdef HAVE_POLL_H +#include +#elif HAVE_SYS_POLL_H +#include +#elif HAVE_SYS_SELECT_H +#include +#endif + +#ifdef HAVE_SYS_SOCKET_H +#include +#endif + +#ifdef HAVE_NETINET_IN_H +#include +#include +#endif + +#ifdef HAVE_ARPA_INET_H +#include +#endif + +#ifdef HAVE_NETDB_H +#include +#endif + +#ifdef HAVE_FCNTL_H +#include +#endif + +#include + +#ifdef HAVE_SCHED_H +#include +#endif + +#ifndef AF_LOCAL +#define AF_LOCAL AF_UNIX +#endif + +#ifdef HAVE_INTTYPES_H +#include +#endif + +#ifdef HAVE_STDINT_H +#include +#endif + +namespace apache { +namespace thrift { +namespace server { + +using namespace apache::thrift::protocol; +using namespace apache::thrift::transport; +using namespace apache::thrift::concurrency; +using apache::thrift::transport::TSocket; +using apache::thrift::transport::TTransportException; +using std::shared_ptr; + +/// Three states for sockets: recv frame size, recv data, and send mode +enum TSocketState { SOCKET_RECV_FRAMING, SOCKET_RECV, SOCKET_SEND }; + +/** + * Five states for the nonblocking server: + * 1) initialize + * 2) read 4 byte frame size + * 3) read frame of data + * 4) send back data (if any) + * 5) force immediate connection close + */ +enum TAppState { + APP_INIT, + APP_READ_FRAME_SIZE, + APP_READ_REQUEST, + APP_WAIT_TASK, + APP_SEND_RESULT, + APP_CLOSE_CONNECTION +}; + +/** + * Represents a connection that is handled via libevent. This connection + * essentially encapsulates a socket that has some associated libevent state. + */ +class TNonblockingServer::TConnection { +private: + /// Server IO Thread handling this connection + TNonblockingIOThread* ioThread_; + + /// Server handle + TNonblockingServer* server_; + + /// TProcessor + std::shared_ptr processor_; + + /// Object wrapping network socket + std::shared_ptr tSocket_; + + /// Libevent object + struct event event_; + + /// Libevent flags + short eventFlags_; + + /// Socket mode + TSocketState socketState_; + + /// Application state + TAppState appState_; + + /// How much data needed to read + uint32_t readWant_; + + /// Where in the read buffer are we + uint32_t readBufferPos_; + + /// Read buffer + uint8_t* readBuffer_; + + /// Read buffer size + uint32_t readBufferSize_; + + /// Write buffer + uint8_t* writeBuffer_; + + /// Write buffer size + uint32_t writeBufferSize_; + + /// How far through writing are we? + uint32_t writeBufferPos_; + + /// Largest size of write buffer seen since buffer was constructed + size_t largestWriteBufferSize_; + + /// Count of the number of calls for use with getResizeBufferEveryN(). + int32_t callsForResize_; + + /// Transport to read from + std::shared_ptr inputTransport_; + + /// Transport that processor writes to + std::shared_ptr outputTransport_; + + /// extra transport generated by transport factory (e.g. BufferedRouterTransport) + std::shared_ptr factoryInputTransport_; + std::shared_ptr factoryOutputTransport_; + + /// Protocol decoder + std::shared_ptr inputProtocol_; + + /// Protocol encoder + std::shared_ptr outputProtocol_; + + /// Server event handler, if any + std::shared_ptr serverEventHandler_; + + /// Thrift call context, if any + void* connectionContext_; + + /// Go into read mode + void setRead() { setFlags(EV_READ | EV_PERSIST); } + + /// Go into write mode + void setWrite() { setFlags(EV_WRITE | EV_PERSIST); } + + /// Set socket idle + void setIdle() { setFlags(0); } + + /** + * Set event flags for this connection. + * + * @param eventFlags flags we pass to libevent for the connection. + */ + void setFlags(short eventFlags); + + /** + * Libevent handler called (via our static wrapper) when the connection + * socket had something happen. Rather than use the flags libevent passed, + * we use the connection state to determine whether we need to read or + * write the socket. + */ + void workSocket(); + +public: + class Task; + + /// Constructor + TConnection(std::shared_ptr socket, + TNonblockingIOThread* ioThread) { + readBuffer_ = nullptr; + readBufferSize_ = 0; + + ioThread_ = ioThread; + server_ = ioThread->getServer(); + + // Allocate input and output transports these only need to be allocated + // once per TConnection (they don't need to be reallocated on init() call) + inputTransport_.reset(new TMemoryBuffer(readBuffer_, readBufferSize_)); + outputTransport_.reset( + new TMemoryBuffer(static_cast(server_->getWriteBufferDefaultSize()))); + + tSocket_ = socket; + + init(ioThread); + } + + ~TConnection() { std::free(readBuffer_); } + + /// Close this connection and free or reset its resources. + void close(); + + /** + * Check buffers against any size limits and shrink it if exceeded. + * + * @param readLimit we reduce read buffer size to this (if nonzero). + * @param writeLimit if nonzero and write buffer is larger, replace it. + */ + void checkIdleBufferMemLimit(size_t readLimit, size_t writeLimit); + + /// Initialize + void init(TNonblockingIOThread* ioThread); + + /// set socket for connection + void setSocket(std::shared_ptr socket); + + /** + * This is called when the application transitions from one state into + * another. This means that it has finished writing the data that it needed + * to, or finished receiving the data that it needed to. + */ + void transition(); + + /** + * C-callable event handler for connection events. Provides a callback + * that libevent can understand which invokes connection_->workSocket(). + * + * @param fd the descriptor the event occurred on. + * @param which the flags associated with the event. + * @param v void* callback arg where we placed TConnection's "this". + */ + static void eventHandler(evutil_socket_t fd, short /* which */, void* v) { + assert(fd == static_cast(((TConnection*)v)->getTSocket()->getSocketFD())); + ((TConnection*)v)->workSocket(); + } + + /** + * Notification to server that processing has ended on this request. + * Can be called either when processing is completed or when a waiting + * task has been preemptively terminated (on overload). + * + * Don't call this from the IO thread itself. + * + * @return true if successful, false if unable to notify (check THRIFT_GET_SOCKET_ERROR). + */ + bool notifyIOThread() { return ioThread_->notify(this); } + + /* + * Returns the number of this connection's currently assigned IO + * thread. + */ + int getIOThreadNumber() const { return ioThread_->getThreadNumber(); } + + /// Force connection shutdown for this connection. + void forceClose() { + appState_ = APP_CLOSE_CONNECTION; + if (!notifyIOThread()) { + server_->decrementActiveProcessors(); + close(); + throw TException("TConnection::forceClose: failed write on notify pipe"); + } + } + + /// return the server this connection was initialized for. + TNonblockingServer* getServer() const { return server_; } + + /// get state of connection. + TAppState getState() const { return appState_; } + + /// return the TSocket transport wrapping this network connection + std::shared_ptr getTSocket() const { return tSocket_; } + + /// return the server event handler if any + std::shared_ptr getServerEventHandler() { return serverEventHandler_; } + + /// return the Thrift connection context if any + void* getConnectionContext() { return connectionContext_; } +}; + +class TNonblockingServer::TConnection::Task : public Runnable { +public: + Task(std::shared_ptr processor, + std::shared_ptr input, + std::shared_ptr output, + TConnection* connection) + : processor_(processor), + input_(input), + output_(output), + connection_(connection), + serverEventHandler_(connection_->getServerEventHandler()), + connectionContext_(connection_->getConnectionContext()) {} + + void run() override { + try { + for (;;) { + if (serverEventHandler_) { + serverEventHandler_->processContext(connectionContext_, connection_->getTSocket()); + } + if (!processor_->process(input_, output_, connectionContext_) + || !input_->getTransport()->peek()) { + break; + } + } + } catch (const TTransportException& ttx) { + GlobalOutput.printf("TNonblockingServer: client died: %s", ttx.what()); + } catch (const std::bad_alloc&) { + GlobalOutput("TNonblockingServer: caught bad_alloc exception."); + exit(1); + } catch (const std::exception& x) { + GlobalOutput.printf("TNonblockingServer: process() exception: %s: %s", + typeid(x).name(), + x.what()); + } catch (...) { + GlobalOutput.printf("TNonblockingServer: unknown exception while processing."); + } + + // Signal completion back to the libevent thread via a pipe + if (!connection_->notifyIOThread()) { + GlobalOutput.printf("TNonblockingServer: failed to notifyIOThread, closing."); + connection_->server_->decrementActiveProcessors(); + connection_->close(); + throw TException("TNonblockingServer::Task::run: failed write on notify pipe"); + } + } + + TConnection* getTConnection() { return connection_; } + +private: + std::shared_ptr processor_; + std::shared_ptr input_; + std::shared_ptr output_; + TConnection* connection_; + std::shared_ptr serverEventHandler_; + void* connectionContext_; +}; + +void TNonblockingServer::TConnection::init(TNonblockingIOThread* ioThread) { + ioThread_ = ioThread; + server_ = ioThread->getServer(); + appState_ = APP_INIT; + eventFlags_ = 0; + + readBufferPos_ = 0; + readWant_ = 0; + + writeBuffer_ = nullptr; + writeBufferSize_ = 0; + writeBufferPos_ = 0; + largestWriteBufferSize_ = 0; + + socketState_ = SOCKET_RECV_FRAMING; + callsForResize_ = 0; + + // get input/transports + factoryInputTransport_ = server_->getInputTransportFactory()->getTransport(inputTransport_); + factoryOutputTransport_ = server_->getOutputTransportFactory()->getTransport(outputTransport_); + + // Create protocol + if (server_->getHeaderTransport()) { + inputProtocol_ = server_->getInputProtocolFactory()->getProtocol(factoryInputTransport_, + factoryOutputTransport_); + outputProtocol_ = inputProtocol_; + } else { + inputProtocol_ = server_->getInputProtocolFactory()->getProtocol(factoryInputTransport_); + outputProtocol_ = server_->getOutputProtocolFactory()->getProtocol(factoryOutputTransport_); + } + + // Set up for any server event handler + serverEventHandler_ = server_->getEventHandler(); + if (serverEventHandler_) { + connectionContext_ = serverEventHandler_->createContext(inputProtocol_, outputProtocol_); + } else { + connectionContext_ = nullptr; + } + + // Get the processor + processor_ = server_->getProcessor(inputProtocol_, outputProtocol_, tSocket_); +} + +void TNonblockingServer::TConnection::setSocket(std::shared_ptr socket) { + tSocket_ = socket; +} + +void TNonblockingServer::TConnection::workSocket() { + int got = 0, left = 0, sent = 0; + uint32_t fetch = 0; + + switch (socketState_) { + case SOCKET_RECV_FRAMING: + union { + uint8_t buf[sizeof(uint32_t)]; + uint32_t size; + } framing; + + // if we've already received some bytes we kept them here + framing.size = readWant_; + // determine size of this frame + try { + // Read from the socket + fetch = tSocket_->read(&framing.buf[readBufferPos_], + uint32_t(sizeof(framing.size) - readBufferPos_)); + if (fetch == 0) { + // Whenever we get here it means a remote disconnect + close(); + return; + } + readBufferPos_ += fetch; + } catch (TTransportException& te) { + //In Nonblocking SSLSocket some operations need to be retried again. + //Current approach is parsing exception message, but a better solution needs to be investigated. + if(!strstr(te.what(), "retry")) { + GlobalOutput.printf("TConnection::workSocket(): %s", te.what()); + close(); + + return; + } + } + + if (readBufferPos_ < sizeof(framing.size)) { + // more needed before frame size is known -- save what we have so far + readWant_ = framing.size; + return; + } + + readWant_ = ntohl(framing.size); + if (readWant_ > server_->getMaxFrameSize()) { + // Don't allow giant frame sizes. This prevents bad clients from + // causing us to try and allocate a giant buffer. + GlobalOutput.printf( + "TNonblockingServer: frame size too large " + "(%" PRIu32 " > %" PRIu64 + ") from client %s. " + "Remote side not using TFramedTransport?", + readWant_, + (uint64_t)server_->getMaxFrameSize(), + tSocket_->getSocketInfo().c_str()); + close(); + return; + } + // size known; now get the rest of the frame + transition(); + + // If the socket has more data than the frame header, continue to work on it. This is not strictly necessary for + // regular sockets, because if there is more data, libevent will fire the event handler registered for read + // readiness, which will in turn call workSocket(). However, some socket types (such as TSSLSocket) may have the + // data sitting in their internal buffers and from libevent's perspective, there is no further data available. In + // that case, not having this workSocket() call here would result in a hang as we will never get to work the socket, + // despite having more data. + if (tSocket_->hasPendingDataToRead()) + { + workSocket(); + } + + return; + + case SOCKET_RECV: + // It is an error to be in this state if we already have all the data + assert(readBufferPos_ < readWant_); + + try { + // Read from the socket + fetch = readWant_ - readBufferPos_; + got = tSocket_->read(readBuffer_ + readBufferPos_, fetch); + } catch (TTransportException& te) { + //In Nonblocking SSLSocket some operations need to be retried again. + //Current approach is parsing exception message, but a better solution needs to be investigated. + if(!strstr(te.what(), "retry")) { + GlobalOutput.printf("TConnection::workSocket(): %s", te.what()); + close(); + } + + return; + } + + if (got > 0) { + // Move along in the buffer + readBufferPos_ += got; + + // Check that we did not overdo it + assert(readBufferPos_ <= readWant_); + + // We are done reading, move onto the next state + if (readBufferPos_ == readWant_) { + transition(); + } + return; + } + + // Whenever we get down here it means a remote disconnect + close(); + + return; + + case SOCKET_SEND: + // Should never have position past size + assert(writeBufferPos_ <= writeBufferSize_); + + // If there is no data to send, then let us move on + if (writeBufferPos_ == writeBufferSize_) { + GlobalOutput("WARNING: Send state with no data to send"); + transition(); + return; + } + + try { + left = writeBufferSize_ - writeBufferPos_; + sent = tSocket_->write_partial(writeBuffer_ + writeBufferPos_, left); + } catch (TTransportException& te) { + GlobalOutput.printf("TConnection::workSocket(): %s ", te.what()); + close(); + return; + } + + writeBufferPos_ += sent; + + // Did we overdo it? + assert(writeBufferPos_ <= writeBufferSize_); + + // We are done! + if (writeBufferPos_ == writeBufferSize_) { + transition(); + } + + return; + + default: + GlobalOutput.printf("Unexpected Socket State %d", socketState_); + assert(0); + } +} + +bool TNonblockingServer::getHeaderTransport() { + // Currently if there is no output protocol factory, + // we assume header transport (without having to create + // a new transport and check) + return getOutputProtocolFactory() == nullptr; +} + +/** + * This is called when the application transitions from one state into + * another. This means that it has finished writing the data that it needed + * to, or finished receiving the data that it needed to. + */ +void TNonblockingServer::TConnection::transition() { + // ensure this connection is active right now + assert(ioThread_); + assert(server_); + + // Switch upon the state that we are currently in and move to a new state + switch (appState_) { + + case APP_READ_REQUEST: + // We are done reading the request, package the read buffer into transport + // and get back some data from the dispatch function + if (server_->getHeaderTransport()) { + inputTransport_->resetBuffer(readBuffer_, readBufferPos_); + outputTransport_->resetBuffer(); + } else { + // We saved room for the framing size in case header transport needed it, + // but just skip it for the non-header case + inputTransport_->resetBuffer(readBuffer_ + 4, readBufferPos_ - 4); + outputTransport_->resetBuffer(); + + // Prepend four bytes of blank space to the buffer so we can + // write the frame size there later. + outputTransport_->getWritePtr(4); + outputTransport_->wroteBytes(4); + } + + server_->incrementActiveProcessors(); + + if (server_->isThreadPoolProcessing()) { + // We are setting up a Task to do this work and we will wait on it + + // Create task and dispatch to the thread manager + std::shared_ptr task = std::shared_ptr( + new Task(processor_, inputProtocol_, outputProtocol_, this)); + // The application is now waiting on the task to finish + appState_ = APP_WAIT_TASK; + + // Set this connection idle so that libevent doesn't process more + // data on it while we're still waiting for the threadmanager to + // finish this task + setIdle(); + + try { + server_->addTask(task); + } catch (IllegalStateException& ise) { + // The ThreadManager is not ready to handle any more tasks (it's probably shutting down). + GlobalOutput.printf("IllegalStateException: Server::process() %s", ise.what()); + server_->decrementActiveProcessors(); + close(); + } catch (TimedOutException& to) { + GlobalOutput.printf("[ERROR] TimedOutException: Server::process() %s", to.what()); + server_->decrementActiveProcessors(); + close(); + } + + return; + } else { + try { + if (serverEventHandler_) { + serverEventHandler_->processContext(connectionContext_, getTSocket()); + } + // Invoke the processor + processor_->process(inputProtocol_, outputProtocol_, connectionContext_); + } catch (const TTransportException& ttx) { + GlobalOutput.printf( + "TNonblockingServer transport error in " + "process(): %s", + ttx.what()); + server_->decrementActiveProcessors(); + close(); + return; + } catch (const std::exception& x) { + GlobalOutput.printf("Server::process() uncaught exception: %s: %s", + typeid(x).name(), + x.what()); + server_->decrementActiveProcessors(); + close(); + return; + } catch (...) { + GlobalOutput.printf("Server::process() unknown exception"); + server_->decrementActiveProcessors(); + close(); + return; + } + } + // fallthrough + + // Intentionally fall through here, the call to process has written into + // the writeBuffer_ + + case APP_WAIT_TASK: + // We have now finished processing a task and the result has been written + // into the outputTransport_, so we grab its contents and place them into + // the writeBuffer_ for actual writing by the libevent thread + + server_->decrementActiveProcessors(); + // Get the result of the operation + outputTransport_->getBuffer(&writeBuffer_, &writeBufferSize_); + + // If the function call generated return data, then move into the send + // state and get going + // 4 bytes were reserved for frame size + if (writeBufferSize_ > 4) { + + // Move into write state + writeBufferPos_ = 0; + socketState_ = SOCKET_SEND; + + // Put the frame size into the write buffer + auto frameSize = (int32_t)htonl(writeBufferSize_ - 4); + memcpy(writeBuffer_, &frameSize, 4); + + // Socket into write mode + appState_ = APP_SEND_RESULT; + setWrite(); + + return; + } + + // In this case, the request was oneway and we should fall through + // right back into the read frame header state + goto LABEL_APP_INIT; + + case APP_SEND_RESULT: + // it's now safe to perform buffer size housekeeping. + if (writeBufferSize_ > largestWriteBufferSize_) { + largestWriteBufferSize_ = writeBufferSize_; + } + if (server_->getResizeBufferEveryN() > 0 + && ++callsForResize_ >= server_->getResizeBufferEveryN()) { + checkIdleBufferMemLimit(server_->getIdleReadBufferLimit(), + server_->getIdleWriteBufferLimit()); + callsForResize_ = 0; + } + // fallthrough + + // N.B.: We also intentionally fall through here into the INIT state! + + LABEL_APP_INIT: + case APP_INIT: + + // Clear write buffer variables + writeBuffer_ = nullptr; + writeBufferPos_ = 0; + writeBufferSize_ = 0; + + // Into read4 state we go + socketState_ = SOCKET_RECV_FRAMING; + appState_ = APP_READ_FRAME_SIZE; + + readBufferPos_ = 0; + + // Register read event + setRead(); + + return; + + case APP_READ_FRAME_SIZE: + readWant_ += 4; + + // We just read the request length + // Double the buffer size until it is big enough + if (readWant_ > readBufferSize_) { + if (readBufferSize_ == 0) { + readBufferSize_ = 1; + } + uint32_t newSize = readBufferSize_; + while (readWant_ > newSize) { + newSize *= 2; + } + + auto* newBuffer = (uint8_t*)std::realloc(readBuffer_, newSize); + if (newBuffer == nullptr) { + // nothing else to be done... + throw std::bad_alloc(); + } + readBuffer_ = newBuffer; + readBufferSize_ = newSize; + } + + readBufferPos_ = 4; + *((uint32_t*)readBuffer_) = htonl(readWant_ - 4); + + // Move into read request state + socketState_ = SOCKET_RECV; + appState_ = APP_READ_REQUEST; + + return; + + case APP_CLOSE_CONNECTION: + server_->decrementActiveProcessors(); + close(); + return; + + default: + GlobalOutput.printf("Unexpected Application State %d", appState_); + assert(0); + } +} + +void TNonblockingServer::TConnection::setFlags(short eventFlags) { + // Catch the do nothing case + if (eventFlags_ == eventFlags) { + return; + } + + // Delete a previously existing event + if (eventFlags_ && event_del(&event_) == -1) { + GlobalOutput.perror("TConnection::setFlags() event_del", THRIFT_GET_SOCKET_ERROR); + return; + } + + // Update in memory structure + eventFlags_ = eventFlags; + + // Do not call event_set if there are no flags + if (!eventFlags_) { + return; + } + + /* + * event_set: + * + * Prepares the event structure &event to be used in future calls to + * event_add() and event_del(). The event will be prepared to call the + * eventHandler using the 'sock' file descriptor to monitor events. + * + * The events can be either EV_READ, EV_WRITE, or both, indicating + * that an application can read or write from the file respectively without + * blocking. + * + * The eventHandler will be called with the file descriptor that triggered + * the event and the type of event which will be one of: EV_TIMEOUT, + * EV_SIGNAL, EV_READ, EV_WRITE. + * + * The additional flag EV_PERSIST makes an event_add() persistent until + * event_del() has been called. + * + * Once initialized, the &event struct can be used repeatedly with + * event_add() and event_del() and does not need to be reinitialized unless + * the eventHandler and/or the argument to it are to be changed. However, + * when an ev structure has been added to libevent using event_add() the + * structure must persist until the event occurs (assuming EV_PERSIST + * is not set) or is removed using event_del(). You may not reuse the same + * ev structure for multiple monitored descriptors; each descriptor needs + * its own ev. + */ + event_set(&event_, tSocket_->getSocketFD(), eventFlags_, TConnection::eventHandler, this); + event_base_set(ioThread_->getEventBase(), &event_); + + // Add the event + if (event_add(&event_, nullptr) == -1) { + GlobalOutput.perror("TConnection::setFlags(): could not event_add", THRIFT_GET_SOCKET_ERROR); + } +} + +/** + * Closes a connection + */ +void TNonblockingServer::TConnection::close() { + setIdle(); + + if (serverEventHandler_) { + serverEventHandler_->deleteContext(connectionContext_, inputProtocol_, outputProtocol_); + } + ioThread_ = nullptr; + + // Close the socket + tSocket_->close(); + + // close any factory produced transports + factoryInputTransport_->close(); + factoryOutputTransport_->close(); + + // release processor and handler + processor_.reset(); + + // Give this object back to the server that owns it + server_->returnConnection(this); +} + +void TNonblockingServer::TConnection::checkIdleBufferMemLimit(size_t readLimit, size_t writeLimit) { + if (readLimit > 0 && readBufferSize_ > readLimit) { + free(readBuffer_); + readBuffer_ = nullptr; + readBufferSize_ = 0; + } + + if (writeLimit > 0 && largestWriteBufferSize_ > writeLimit) { + // just start over + outputTransport_->resetBuffer(static_cast(server_->getWriteBufferDefaultSize())); + largestWriteBufferSize_ = 0; + } +} + +TNonblockingServer::~TNonblockingServer() { + // Close any active connections (moves them to the idle connection stack) + while (activeConnections_.size()) { + activeConnections_.front()->close(); + } + // Clean up unused TConnection objects in connectionStack_ + while (!connectionStack_.empty()) { + TConnection* connection = connectionStack_.top(); + connectionStack_.pop(); + delete connection; + } + // The TNonblockingIOThread objects have shared_ptrs to the Thread + // objects and the Thread objects have shared_ptrs to the TNonblockingIOThread + // objects (as runnable) so these objects will never deallocate without help. + while (!ioThreads_.empty()) { + std::shared_ptr iot = ioThreads_.back(); + ioThreads_.pop_back(); + iot->setThread(std::shared_ptr()); + } +} + +/** + * Creates a new connection either by reusing an object off the stack or + * by allocating a new one entirely + */ +TNonblockingServer::TConnection* TNonblockingServer::createConnection(std::shared_ptr socket) { + // Check the stack + Guard g(connMutex_); + + // pick an IO thread to handle this connection -- currently round robin + assert(nextIOThread_ < ioThreads_.size()); + int selectedThreadIdx = nextIOThread_; + nextIOThread_ = static_cast((nextIOThread_ + 1) % ioThreads_.size()); + + TNonblockingIOThread* ioThread = ioThreads_[selectedThreadIdx].get(); + + // Check the connection stack to see if we can re-use + TConnection* result = nullptr; + if (connectionStack_.empty()) { + result = new TConnection(socket, ioThread); + ++numTConnections_; + } else { + result = connectionStack_.top(); + connectionStack_.pop(); + result->setSocket(socket); + result->init(ioThread); + } + activeConnections_.push_back(result); + return result; +} + +/** + * Returns a connection to the stack + */ +void TNonblockingServer::returnConnection(TConnection* connection) { + Guard g(connMutex_); + + activeConnections_.erase(std::remove(activeConnections_.begin(), + activeConnections_.end(), + connection), + activeConnections_.end()); + + if (connectionStackLimit_ && (connectionStack_.size() >= connectionStackLimit_)) { + delete connection; + --numTConnections_; + } else { + connection->checkIdleBufferMemLimit(idleReadBufferLimit_, idleWriteBufferLimit_); + connectionStack_.push(connection); + } +} + +/** + * Server socket had something happen. We accept all waiting client + * connections on fd and assign TConnection objects to handle those requests. + */ +void TNonblockingServer::handleEvent(THRIFT_SOCKET fd, short which) { + (void)which; + // Make sure that libevent didn't mess up the socket handles + assert(fd == serverSocket_); + + // Going to accept a new client socket + std::shared_ptr clientSocket; + + clientSocket = serverTransport_->accept(); + if (clientSocket) { + // If we're overloaded, take action here + if (overloadAction_ != T_OVERLOAD_NO_ACTION && serverOverloaded()) { + Guard g(connMutex_); + nConnectionsDropped_++; + nTotalConnectionsDropped_++; + if (overloadAction_ == T_OVERLOAD_CLOSE_ON_ACCEPT) { + clientSocket->close(); + return; + } else if (overloadAction_ == T_OVERLOAD_DRAIN_TASK_QUEUE) { + if (!drainPendingTask()) { + // Nothing left to discard, so we drop connection instead. + clientSocket->close(); + return; + } + } + } + + // Create a new TConnection for this client socket. + TConnection* clientConnection = createConnection(clientSocket); + + // Fail fast if we could not create a TConnection object + if (clientConnection == nullptr) { + GlobalOutput.printf("thriftServerEventHandler: failed TConnection factory"); + clientSocket->close(); + return; + } + + /* + * Either notify the ioThread that is assigned this connection to + * start processing, or if it is us, we'll just ask this + * connection to do its initial state change here. + * + * (We need to avoid writing to our own notification pipe, to + * avoid possible deadlocks if the pipe is full.) + * + * The IO thread #0 is the only one that handles these listen + * events, so unless the connection has been assigned to thread #0 + * we know it's not on our thread. + */ + if (clientConnection->getIOThreadNumber() == 0) { + clientConnection->transition(); + } else { + if (!clientConnection->notifyIOThread()) { + GlobalOutput.perror("[ERROR] notifyIOThread failed on fresh connection, closing", errno); + clientConnection->close(); + } + } + } +} + +/** + * Creates a socket to listen on and binds it to the local port. + */ +void TNonblockingServer::createAndListenOnSocket() { + serverTransport_->listen(); + serverSocket_ = serverTransport_->getSocketFD(); +} + + +void TNonblockingServer::setThreadManager(std::shared_ptr threadManager) { + threadManager_ = threadManager; + if (threadManager) { + threadManager->setExpireCallback( + std::bind(&TNonblockingServer::expireClose, + this, + std::placeholders::_1)); + threadPoolProcessing_ = true; + } else { + threadPoolProcessing_ = false; + } +} + +bool TNonblockingServer::serverOverloaded() { + size_t activeConnections = numTConnections_ - connectionStack_.size(); + if (numActiveProcessors_ > maxActiveProcessors_ || activeConnections > maxConnections_) { + if (!overloaded_) { + GlobalOutput.printf("TNonblockingServer: overload condition begun."); + overloaded_ = true; + } + } else { + if (overloaded_ && (numActiveProcessors_ <= overloadHysteresis_ * maxActiveProcessors_) + && (activeConnections <= overloadHysteresis_ * maxConnections_)) { + GlobalOutput.printf( + "TNonblockingServer: overload ended; " + "%u dropped (%llu total)", + nConnectionsDropped_, + nTotalConnectionsDropped_); + nConnectionsDropped_ = 0; + overloaded_ = false; + } + } + + return overloaded_; +} + +bool TNonblockingServer::drainPendingTask() { + if (threadManager_) { + std::shared_ptr task = threadManager_->removeNextPending(); + if (task) { + TConnection* connection = static_cast(task.get())->getTConnection(); + assert(connection && connection->getServer() && connection->getState() == APP_WAIT_TASK); + connection->forceClose(); + return true; + } + } + return false; +} + +void TNonblockingServer::expireClose(std::shared_ptr task) { + TConnection* connection = static_cast(task.get())->getTConnection(); + assert(connection && connection->getServer() && connection->getState() == APP_WAIT_TASK); + connection->forceClose(); +} + +void TNonblockingServer::stop() { + // Breaks the event loop in all threads so that they end ASAP. + for (auto & ioThread : ioThreads_) { + ioThread->stop(); + } +} + +void TNonblockingServer::registerEvents(event_base* user_event_base) { + userEventBase_ = user_event_base; + + // init listen socket + if (serverSocket_ == THRIFT_INVALID_SOCKET) + createAndListenOnSocket(); + + // set up the IO threads + assert(ioThreads_.empty()); + if (!numIOThreads_) { + numIOThreads_ = DEFAULT_IO_THREADS; + } + // User-provided event-base doesn't works for multi-threaded servers + assert(numIOThreads_ == 1 || !userEventBase_); + + for (uint32_t id = 0; id < numIOThreads_; ++id) { + // the first IO thread also does the listening on server socket + THRIFT_SOCKET listenFd = (id == 0 ? serverSocket_ : THRIFT_INVALID_SOCKET); + + shared_ptr thread( + new TNonblockingIOThread(this, id, listenFd, useHighPriorityIOThreads_)); + ioThreads_.push_back(thread); + } + + // Notify handler of the preServe event + if (eventHandler_) { + eventHandler_->preServe(); + } + + // Start all of our helper IO threads. Note that the threads run forever, + // only terminating if stop() is called. + assert(ioThreads_.size() == numIOThreads_); + assert(ioThreads_.size() > 0); + + GlobalOutput.printf("TNonblockingServer: Serving with %d io threads.", + ioThreads_.size()); + + // Launch all the secondary IO threads in separate threads + if (ioThreads_.size() > 1) { + ioThreadFactory_.reset(new ThreadFactory( + false // detached + )); + + assert(ioThreadFactory_.get()); + + // intentionally starting at thread 1, not 0 + for (uint32_t i = 1; i < ioThreads_.size(); ++i) { + shared_ptr thread = ioThreadFactory_->newThread(ioThreads_[i]); + ioThreads_[i]->setThread(thread); + thread->start(); + } + } + + // Register the events for the primary (listener) IO thread + ioThreads_[0]->registerEvents(); +} + +/** + * Main workhorse function, starts up the server listening on a port and + * loops over the libevent handler. + */ +void TNonblockingServer::serve() { + + if (ioThreads_.empty()) + registerEvents(nullptr); + + // Run the primary (listener) IO thread loop in our main thread; this will + // only return when the server is shutting down. + ioThreads_[0]->run(); + + // Ensure all threads are finished before exiting serve() + for (uint32_t i = 0; i < ioThreads_.size(); ++i) { + ioThreads_[i]->join(); + GlobalOutput.printf("TNonblocking: join done for IO thread #%d", i); + } +} + +TNonblockingIOThread::TNonblockingIOThread(TNonblockingServer* server, + int number, + THRIFT_SOCKET listenSocket, + bool useHighPriority) + : server_(server), + number_(number), + threadId_{}, + listenSocket_(listenSocket), + useHighPriority_(useHighPriority), + eventBase_(nullptr), + ownEventBase_(false), + serverEvent_{}, + notificationEvent_{} { + notificationPipeFDs_[0] = -1; + notificationPipeFDs_[1] = -1; +} + +TNonblockingIOThread::~TNonblockingIOThread() { + // make sure our associated thread is fully finished + join(); + + if (eventBase_ && ownEventBase_) { + event_base_free(eventBase_); + ownEventBase_ = false; + } + + if (listenSocket_ != THRIFT_INVALID_SOCKET) { + if (0 != ::THRIFT_CLOSESOCKET(listenSocket_)) { + GlobalOutput.perror("TNonblockingIOThread listenSocket_ close(): ", THRIFT_GET_SOCKET_ERROR); + } + listenSocket_ = THRIFT_INVALID_SOCKET; + } + + for (auto notificationPipeFD : notificationPipeFDs_) { + if (notificationPipeFD >= 0) { + if (0 != ::THRIFT_CLOSESOCKET(notificationPipeFD)) { + GlobalOutput.perror("TNonblockingIOThread notificationPipe close(): ", + THRIFT_GET_SOCKET_ERROR); + } + notificationPipeFD = THRIFT_INVALID_SOCKET; + } + } +} + +void TNonblockingIOThread::createNotificationPipe() { + if (evutil_socketpair(AF_LOCAL, SOCK_STREAM, 0, notificationPipeFDs_) == -1) { + GlobalOutput.perror("TNonblockingServer::createNotificationPipe ", EVUTIL_SOCKET_ERROR()); + throw TException("can't create notification pipe"); + } + if (evutil_make_socket_nonblocking(notificationPipeFDs_[0]) < 0 + || evutil_make_socket_nonblocking(notificationPipeFDs_[1]) < 0) { + ::THRIFT_CLOSESOCKET(notificationPipeFDs_[0]); + ::THRIFT_CLOSESOCKET(notificationPipeFDs_[1]); + throw TException("TNonblockingServer::createNotificationPipe() THRIFT_O_NONBLOCK"); + } + for (auto notificationPipeFD : notificationPipeFDs_) { +#if LIBEVENT_VERSION_NUMBER < 0x02000000 + int flags; + if ((flags = THRIFT_FCNTL(notificationPipeFD, F_GETFD, 0)) < 0 + || THRIFT_FCNTL(notificationPipeFD, F_SETFD, flags | FD_CLOEXEC) < 0) { +#else + if (evutil_make_socket_closeonexec(notificationPipeFD) < 0) { +#endif + ::THRIFT_CLOSESOCKET(notificationPipeFDs_[0]); + ::THRIFT_CLOSESOCKET(notificationPipeFDs_[1]); + throw TException( + "TNonblockingServer::createNotificationPipe() " + "FD_CLOEXEC"); + } + } +} + +/** + * Register the core libevent events onto the proper base. + */ +void TNonblockingIOThread::registerEvents() { + threadId_ = Thread::get_current(); + + assert(eventBase_ == nullptr); + eventBase_ = getServer()->getUserEventBase(); + if (eventBase_ == nullptr) { + eventBase_ = event_base_new(); + ownEventBase_ = true; + } + + // Print some libevent stats + if (number_ == 0) { + GlobalOutput.printf("TNonblockingServer: using libevent %s method %s", + event_get_version(), + event_base_get_method(eventBase_)); + } + + if (listenSocket_ != THRIFT_INVALID_SOCKET) { + // Register the server event + event_set(&serverEvent_, + listenSocket_, + EV_READ | EV_PERSIST, + TNonblockingIOThread::listenHandler, + server_); + event_base_set(eventBase_, &serverEvent_); + + // Add the event and start up the server + if (-1 == event_add(&serverEvent_, nullptr)) { + throw TException( + "TNonblockingServer::serve(): " + "event_add() failed on server listen event"); + } + GlobalOutput.printf("TNonblocking: IO thread #%d registered for listen.", number_); + } + + createNotificationPipe(); + + // Create an event to be notified when a task finishes + event_set(¬ificationEvent_, + getNotificationRecvFD(), + EV_READ | EV_PERSIST, + TNonblockingIOThread::notifyHandler, + this); + + // Attach to the base + event_base_set(eventBase_, ¬ificationEvent_); + + // Add the event and start up the server + if (-1 == event_add(¬ificationEvent_, nullptr)) { + throw TException( + "TNonblockingServer::serve(): " + "event_add() failed on task-done notification event"); + } + GlobalOutput.printf("TNonblocking: IO thread #%d registered for notify.", number_); +} + +bool TNonblockingIOThread::notify(TNonblockingServer::TConnection* conn) { + auto fd = getNotificationSendFD(); + if (fd < 0) { + return false; + } + + int ret = -1; + long kSize = sizeof(conn); + const char * pos = (const char *)const_cast_sockopt(&conn); + +#if defined(HAVE_POLL_H) || defined(HAVE_SYS_POLL_H) + struct pollfd pfd = {fd, POLLOUT, 0}; + + while (kSize > 0) { + pfd.revents = 0; + ret = poll(&pfd, 1, -1); + if (ret < 0) { + return false; + } else if (ret == 0) { + continue; + } + + if (pfd.revents & POLLHUP || pfd.revents & POLLERR) { + ::THRIFT_CLOSESOCKET(fd); + return false; + } + + if (pfd.revents & POLLOUT) { + ret = send(fd, pos, kSize, 0); + if (ret < 0) { + if (errno == EAGAIN) { + continue; + } + + ::THRIFT_CLOSESOCKET(fd); + return false; + } + + kSize -= ret; + pos += ret; + } + } +#else + fd_set wfds, efds; + + while (kSize > 0) { + FD_ZERO(&wfds); + FD_ZERO(&efds); + FD_SET(fd, &wfds); + FD_SET(fd, &efds); + ret = select(static_cast(fd + 1), NULL, &wfds, &efds, NULL); + if (ret < 0) { + return false; + } else if (ret == 0) { + continue; + } + + if (FD_ISSET(fd, &efds)) { + ::THRIFT_CLOSESOCKET(fd); + return false; + } + + if (FD_ISSET(fd, &wfds)) { + ret = send(fd, pos, kSize, 0); + if (ret < 0) { + if (errno == EAGAIN) { + continue; + } + + ::THRIFT_CLOSESOCKET(fd); + return false; + } + + kSize -= ret; + pos += ret; + } + } +#endif + + return true; +} + +/* static */ +void TNonblockingIOThread::notifyHandler(evutil_socket_t fd, short which, void* v) { + auto* ioThread = (TNonblockingIOThread*)v; + assert(ioThread); + (void)which; + + while (true) { + TNonblockingServer::TConnection* connection = nullptr; + const int kSize = sizeof(connection); + long nBytes = recv(fd, cast_sockopt(&connection), kSize, 0); + if (nBytes == kSize) { + if (connection == nullptr) { + // this is the command to stop our thread, exit the handler! + ioThread->breakLoop(false); + return; + } + connection->transition(); + } else if (nBytes > 0) { + // throw away these bytes and hope that next time we get a solid read + GlobalOutput.printf("notifyHandler: Bad read of %d bytes, wanted %d", nBytes, kSize); + ioThread->breakLoop(true); + return; + } else if (nBytes == 0) { + GlobalOutput.printf("notifyHandler: Notify socket closed!"); + ioThread->breakLoop(false); + // exit the loop + break; + } else { // nBytes < 0 + if (THRIFT_GET_SOCKET_ERROR != THRIFT_EWOULDBLOCK + && THRIFT_GET_SOCKET_ERROR != THRIFT_EAGAIN) { + GlobalOutput.perror("TNonblocking: notifyHandler read() failed: ", THRIFT_GET_SOCKET_ERROR); + ioThread->breakLoop(true); + return; + } + // exit the loop + break; + } + } +} + +void TNonblockingIOThread::breakLoop(bool error) { + if (error) { + GlobalOutput.printf("TNonblockingServer: IO thread #%d exiting with error.", number_); + // TODO: figure out something better to do here, but for now kill the + // whole process. + GlobalOutput.printf("TNonblockingServer: aborting process."); + ::abort(); + } + + // If we're running in the same thread, we can't use the notify(0) + // mechanism to stop the thread, but happily if we're running in the + // same thread, this means the thread can't be blocking in the event + // loop either. + if (!Thread::is_current(threadId_)) { + notify(nullptr); + } else { + // cause the loop to stop ASAP - even if it has things to do in it + event_base_loopbreak(eventBase_); + } +} + +void TNonblockingIOThread::setCurrentThreadHighPriority(bool value) { +#ifdef HAVE_SCHED_H + // Start out with a standard, low-priority setup for the sched params. + struct sched_param sp; + bzero((void*)&sp, sizeof(sp)); + int policy = SCHED_OTHER; + + // If desired, set up high-priority sched params structure. + if (value) { + // FIFO scheduler, ranked above default SCHED_OTHER queue + policy = SCHED_FIFO; + // The priority only compares us to other SCHED_FIFO threads, so we + // just pick a random priority halfway between min & max. + const int priority = (sched_get_priority_max(policy) + sched_get_priority_min(policy)) / 2; + + sp.sched_priority = priority; + } + + // Actually set the sched params for the current thread. + if (0 == pthread_setschedparam(pthread_self(), policy, &sp)) { + GlobalOutput.printf("TNonblocking: IO Thread #%d using high-priority scheduler!", number_); + } else { + GlobalOutput.perror("TNonblocking: pthread_setschedparam(): ", THRIFT_GET_SOCKET_ERROR); + } +#else + THRIFT_UNUSED_VARIABLE(value); +#endif +} + +void TNonblockingIOThread::run() { + if (eventBase_ == nullptr) { + registerEvents(); + } + if (useHighPriority_) { + setCurrentThreadHighPriority(true); + } + + if (eventBase_ != nullptr) + { + GlobalOutput.printf("TNonblockingServer: IO thread #%d entering loop...", number_); + // Run libevent engine, never returns, invokes calls to eventHandler + event_base_loop(eventBase_, 0); + + if (useHighPriority_) { + setCurrentThreadHighPriority(false); + } + + // cleans up our registered events + cleanupEvents(); + } + + GlobalOutput.printf("TNonblockingServer: IO thread #%d run() done!", number_); +} + +void TNonblockingIOThread::cleanupEvents() { + // stop the listen socket, if any + if (listenSocket_ != THRIFT_INVALID_SOCKET) { + if (event_del(&serverEvent_) == -1) { + GlobalOutput.perror("TNonblockingIOThread::stop() event_del: ", THRIFT_GET_SOCKET_ERROR); + } + } + + event_del(¬ificationEvent_); +} + +void TNonblockingIOThread::stop() { + // This should cause the thread to fall out of its event loop ASAP. + breakLoop(false); +} + +void TNonblockingIOThread::join() { + // If this was a thread created by a factory (not the thread that called + // serve()), we join() it to make sure we shut down fully. + if (thread_) { + try { + // Note that it is safe to both join() ourselves twice, as well as join + // the current thread as the pthread implementation checks for deadlock. + thread_->join(); + } catch (...) { + // swallow everything + } + } +} +} +} +} // apache::thrift::server diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/server/TNonblockingServer.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/server/TNonblockingServer.h new file mode 100644 index 000000000..82bc375bd --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/server/TNonblockingServer.h @@ -0,0 +1,860 @@ +/* + * 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. + */ + +#ifndef _THRIFT_SERVER_TNONBLOCKINGSERVER_H_ +#define _THRIFT_SERVER_TNONBLOCKINGSERVER_H_ 1 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif +#include +#include +#include + +namespace apache { +namespace thrift { +namespace server { + +using apache::thrift::transport::TMemoryBuffer; +using apache::thrift::transport::TSocket; +using apache::thrift::transport::TNonblockingServerTransport; +using apache::thrift::protocol::TProtocol; +using apache::thrift::concurrency::Runnable; +using apache::thrift::concurrency::ThreadManager; +using apache::thrift::concurrency::ThreadFactory; +using apache::thrift::concurrency::ThreadFactory; +using apache::thrift::concurrency::Thread; +using apache::thrift::concurrency::Mutex; +using apache::thrift::concurrency::Guard; + +#ifdef LIBEVENT_VERSION_NUMBER +#define LIBEVENT_VERSION_MAJOR (LIBEVENT_VERSION_NUMBER >> 24) +#define LIBEVENT_VERSION_MINOR ((LIBEVENT_VERSION_NUMBER >> 16) & 0xFF) +#define LIBEVENT_VERSION_REL ((LIBEVENT_VERSION_NUMBER >> 8) & 0xFF) +#else +// assume latest version 1 series +#define LIBEVENT_VERSION_MAJOR 1 +#define LIBEVENT_VERSION_MINOR 14 +#define LIBEVENT_VERSION_REL 13 +#define LIBEVENT_VERSION_NUMBER \ + ((LIBEVENT_VERSION_MAJOR << 24) | (LIBEVENT_VERSION_MINOR << 16) | (LIBEVENT_VERSION_REL << 8)) +#endif + +#if LIBEVENT_VERSION_NUMBER < 0x02000000 +typedef THRIFT_SOCKET evutil_socket_t; +#endif + +#ifndef SOCKOPT_CAST_T +#ifndef _WIN32 +#define SOCKOPT_CAST_T void +#else +#define SOCKOPT_CAST_T char +#endif // _WIN32 +#endif + +template +inline const SOCKOPT_CAST_T* const_cast_sockopt(const T* v) { + return reinterpret_cast(v); +} + +template +inline SOCKOPT_CAST_T* cast_sockopt(T* v) { + return reinterpret_cast(v); +} + +/** + * This is a non-blocking server in C++ for high performance that + * operates a set of IO threads (by default only one). It assumes that + * all incoming requests are framed with a 4 byte length indicator and + * writes out responses using the same framing. + */ + +/// Overload condition actions. +enum TOverloadAction { + T_OVERLOAD_NO_ACTION, ///< Don't handle overload */ + T_OVERLOAD_CLOSE_ON_ACCEPT, ///< Drop new connections immediately */ + T_OVERLOAD_DRAIN_TASK_QUEUE ///< Drop some tasks from head of task queue */ +}; + +class TNonblockingIOThread; + +class TNonblockingServer : public TServer { +private: + class TConnection; + + friend class TNonblockingIOThread; + +private: + /// Listen backlog + static const int LISTEN_BACKLOG = 1024; + + /// Default limit on size of idle connection pool + static const size_t CONNECTION_STACK_LIMIT = 1024; + + /// Default limit on frame size + static const int MAX_FRAME_SIZE = 256 * 1024 * 1024; + + /// Default limit on total number of connected sockets + static const int MAX_CONNECTIONS = INT_MAX; + + /// Default limit on connections in handler/task processing + static const int MAX_ACTIVE_PROCESSORS = INT_MAX; + + /// Default size of write buffer + static const int WRITE_BUFFER_DEFAULT_SIZE = 1024; + + /// Maximum size of read buffer allocated to idle connection (0 = unlimited) + static const int IDLE_READ_BUFFER_LIMIT = 1024; + + /// Maximum size of write buffer allocated to idle connection (0 = unlimited) + static const int IDLE_WRITE_BUFFER_LIMIT = 1024; + + /// # of calls before resizing oversized buffers (0 = check only on close) + static const int RESIZE_BUFFER_EVERY_N = 512; + + /// # of IO threads to use by default + static const int DEFAULT_IO_THREADS = 1; + + /// # of IO threads this server will use + size_t numIOThreads_; + + /// Whether to set high scheduling priority for IO threads + bool useHighPriorityIOThreads_; + + /// Server socket file descriptor + THRIFT_SOCKET serverSocket_; + + /// The optional user-provided event-base (for single-thread servers) + event_base* userEventBase_; + + /// For processing via thread pool, may be NULL + std::shared_ptr threadManager_; + + /// Is thread pool processing? + bool threadPoolProcessing_; + + // Factory to create the IO threads + std::shared_ptr ioThreadFactory_; + + // Vector of IOThread objects that will handle our IO + std::vector > ioThreads_; + + // Index of next IO Thread to be used (for round-robin) + uint32_t nextIOThread_; + + // Synchronizes access to connection stack and similar data + Mutex connMutex_; + + /// Number of TConnection object we've created + size_t numTConnections_; + + /// Number of Connections processing or waiting to process + size_t numActiveProcessors_; + + /// Limit for how many TConnection objects to cache + size_t connectionStackLimit_; + + /// Limit for number of connections processing or waiting to process + size_t maxActiveProcessors_; + + /// Limit for number of open connections + size_t maxConnections_; + + /// Limit for frame size + size_t maxFrameSize_; + + /// Time in milliseconds before an unperformed task expires (0 == infinite). + int64_t taskExpireTime_; + + /** + * Hysteresis for overload state. This is the fraction of the overload + * value that needs to be reached before the overload state is cleared; + * must be <= 1.0. + */ + double overloadHysteresis_; + + /// Action to take when we're overloaded. + TOverloadAction overloadAction_; + + /** + * The write buffer is initialized (and when idleWriteBufferLimit_ is checked + * and found to be exceeded, reinitialized) to this size. + */ + size_t writeBufferDefaultSize_; + + /** + * Max read buffer size for an idle TConnection. When we place an idle + * TConnection into connectionStack_ or on every resizeBufferEveryN_ calls, + * we will free the buffer (such that it will be reinitialized by the next + * received frame) if it has exceeded this limit. 0 disables this check. + */ + size_t idleReadBufferLimit_; + + /** + * Max write buffer size for an idle connection. When we place an idle + * TConnection into connectionStack_ or on every resizeBufferEveryN_ calls, + * we insure that its write buffer is <= to this size; otherwise we + * replace it with a new one of writeBufferDefaultSize_ bytes to insure that + * idle connections don't hog memory. 0 disables this check. + */ + size_t idleWriteBufferLimit_; + + /** + * Every N calls we check the buffer size limits on a connected TConnection. + * 0 disables (i.e. the checks are only done when a connection closes). + */ + int32_t resizeBufferEveryN_; + + /// Set if we are currently in an overloaded state. + bool overloaded_; + + /// Count of connections dropped since overload started + uint32_t nConnectionsDropped_; + + /// Count of connections dropped on overload since server started + uint64_t nTotalConnectionsDropped_; + + /** + * This is a stack of all the objects that have been created but that + * are NOT currently in use. When we close a connection, we place it on this + * stack so that the object can be reused later, rather than freeing the + * memory and reallocating a new object later. + */ + std::stack connectionStack_; + + /** + * This container holds pointers to all active connections. This container + * allows the server to clean up unlcosed connection objects at destruction, + * which in turn allows their transports, protocols, processors and handlers + * to deallocate and clean up correctly. + */ + std::vector activeConnections_; + + /* + */ + std::shared_ptr serverTransport_; + + /** + * Called when server socket had something happen. We accept all waiting + * client connections on listen socket fd and assign TConnection objects + * to handle those requests. + * + * @param which the event flag that triggered the handler. + */ + void handleEvent(THRIFT_SOCKET fd, short which); + + void init() { + serverSocket_ = THRIFT_INVALID_SOCKET; + numIOThreads_ = DEFAULT_IO_THREADS; + nextIOThread_ = 0; + useHighPriorityIOThreads_ = false; + userEventBase_ = nullptr; + threadPoolProcessing_ = false; + numTConnections_ = 0; + numActiveProcessors_ = 0; + connectionStackLimit_ = CONNECTION_STACK_LIMIT; + maxActiveProcessors_ = MAX_ACTIVE_PROCESSORS; + maxConnections_ = MAX_CONNECTIONS; + maxFrameSize_ = MAX_FRAME_SIZE; + taskExpireTime_ = 0; + overloadHysteresis_ = 0.8; + overloadAction_ = T_OVERLOAD_NO_ACTION; + writeBufferDefaultSize_ = WRITE_BUFFER_DEFAULT_SIZE; + idleReadBufferLimit_ = IDLE_READ_BUFFER_LIMIT; + idleWriteBufferLimit_ = IDLE_WRITE_BUFFER_LIMIT; + resizeBufferEveryN_ = RESIZE_BUFFER_EVERY_N; + overloaded_ = false; + nConnectionsDropped_ = 0; + nTotalConnectionsDropped_ = 0; + } + +public: + TNonblockingServer(const std::shared_ptr& processorFactory, + const std::shared_ptr& serverTransport) + : TServer(processorFactory), serverTransport_(serverTransport) { + init(); + } + + TNonblockingServer(const std::shared_ptr& processor, + const std::shared_ptr& serverTransport) + : TServer(processor), serverTransport_(serverTransport) { + init(); + } + + + TNonblockingServer(const std::shared_ptr& processorFactory, + const std::shared_ptr& protocolFactory, + const std::shared_ptr& serverTransport, + const std::shared_ptr& threadManager + = std::shared_ptr()) + : TServer(processorFactory), serverTransport_(serverTransport) { + init(); + + setInputProtocolFactory(protocolFactory); + setOutputProtocolFactory(protocolFactory); + setThreadManager(threadManager); + } + + TNonblockingServer(const std::shared_ptr& processor, + const std::shared_ptr& protocolFactory, + const std::shared_ptr& serverTransport, + const std::shared_ptr& threadManager + = std::shared_ptr()) + : TServer(processor), serverTransport_(serverTransport) { + init(); + + setInputProtocolFactory(protocolFactory); + setOutputProtocolFactory(protocolFactory); + setThreadManager(threadManager); + } + + TNonblockingServer(const std::shared_ptr& processorFactory, + const std::shared_ptr& inputTransportFactory, + const std::shared_ptr& outputTransportFactory, + const std::shared_ptr& inputProtocolFactory, + const std::shared_ptr& outputProtocolFactory, + const std::shared_ptr& serverTransport, + const std::shared_ptr& threadManager + = std::shared_ptr()) + : TServer(processorFactory), serverTransport_(serverTransport) { + init(); + + setInputTransportFactory(inputTransportFactory); + setOutputTransportFactory(outputTransportFactory); + setInputProtocolFactory(inputProtocolFactory); + setOutputProtocolFactory(outputProtocolFactory); + setThreadManager(threadManager); + } + + TNonblockingServer(const std::shared_ptr& processor, + const std::shared_ptr& inputTransportFactory, + const std::shared_ptr& outputTransportFactory, + const std::shared_ptr& inputProtocolFactory, + const std::shared_ptr& outputProtocolFactory, + const std::shared_ptr& serverTransport, + const std::shared_ptr& threadManager + = std::shared_ptr()) + : TServer(processor), serverTransport_(serverTransport) { + init(); + + setInputTransportFactory(inputTransportFactory); + setOutputTransportFactory(outputTransportFactory); + setInputProtocolFactory(inputProtocolFactory); + setOutputProtocolFactory(outputProtocolFactory); + setThreadManager(threadManager); + } + + ~TNonblockingServer() override; + + void setThreadManager(std::shared_ptr threadManager); + + int getListenPort() { return serverTransport_->getListenPort(); } + + std::shared_ptr getThreadManager() { return threadManager_; } + + /** + * Sets the number of IO threads used by this server. Can only be used before + * the call to serve() and has no effect afterwards. + */ + void setNumIOThreads(size_t numThreads) { + numIOThreads_ = numThreads; + // User-provided event-base doesn't works for multi-threaded servers + assert(numIOThreads_ <= 1 || !userEventBase_); + } + + /** Return whether the IO threads will get high scheduling priority */ + bool useHighPriorityIOThreads() const { return useHighPriorityIOThreads_; } + + /** Set whether the IO threads will get high scheduling priority. */ + void setUseHighPriorityIOThreads(bool val) { useHighPriorityIOThreads_ = val; } + + /** Return the number of IO threads used by this server. */ + size_t getNumIOThreads() const { return numIOThreads_; } + + /** + * Get the maximum number of unused TConnection we will hold in reserve. + * + * @return the current limit on TConnection pool size. + */ + size_t getConnectionStackLimit() const { return connectionStackLimit_; } + + /** + * Set the maximum number of unused TConnection we will hold in reserve. + * + * @param sz the new limit for TConnection pool size. + */ + void setConnectionStackLimit(size_t sz) { connectionStackLimit_ = sz; } + + bool isThreadPoolProcessing() const { return threadPoolProcessing_; } + + void addTask(std::shared_ptr task) { + threadManager_->add(task, 0LL, taskExpireTime_); + } + + /** + * Return the count of sockets currently connected to. + * + * @return count of connected sockets. + */ + size_t getNumConnections() const { return numTConnections_; } + + /** + * Return the count of sockets currently connected to. + * + * @return count of connected sockets. + */ + size_t getNumActiveConnections() const { return getNumConnections() - getNumIdleConnections(); } + + /** + * Return the count of connection objects allocated but not in use. + * + * @return count of idle connection objects. + */ + size_t getNumIdleConnections() const { return connectionStack_.size(); } + + /** + * Return count of number of connections which are currently processing. + * This is defined as a connection where all data has been received and + * either assigned a task (when threading) or passed to a handler (when + * not threading), and where the handler has not yet returned. + * + * @return # of connections currently processing. + */ + size_t getNumActiveProcessors() const { return numActiveProcessors_; } + + /// Increment the count of connections currently processing. + void incrementActiveProcessors() { + Guard g(connMutex_); + ++numActiveProcessors_; + } + + /// Decrement the count of connections currently processing. + void decrementActiveProcessors() { + Guard g(connMutex_); + if (numActiveProcessors_ > 0) { + --numActiveProcessors_; + } + } + + /** + * Get the maximum # of connections allowed before overload. + * + * @return current setting. + */ + size_t getMaxConnections() const { return maxConnections_; } + + /** + * Set the maximum # of connections allowed before overload. + * + * @param maxConnections new setting for maximum # of connections. + */ + void setMaxConnections(size_t maxConnections) { maxConnections_ = maxConnections; } + + /** + * Get the maximum # of connections waiting in handler/task before overload. + * + * @return current setting. + */ + size_t getMaxActiveProcessors() const { return maxActiveProcessors_; } + + /** + * Set the maximum # of connections waiting in handler/task before overload. + * + * @param maxActiveProcessors new setting for maximum # of active processes. + */ + void setMaxActiveProcessors(size_t maxActiveProcessors) { + maxActiveProcessors_ = maxActiveProcessors; + } + + /** + * Get the maximum allowed frame size. + * + * If a client tries to send a message larger than this limit, + * its connection will be closed. + * + * @return Maxium frame size, in bytes. + */ + size_t getMaxFrameSize() const { return maxFrameSize_; } + + /** + * Set the maximum allowed frame size. + * + * @param maxFrameSize The new maximum frame size. + */ + void setMaxFrameSize(size_t maxFrameSize) { maxFrameSize_ = maxFrameSize; } + + /** + * Get fraction of maximum limits before an overload condition is cleared. + * + * @return hysteresis fraction + */ + double getOverloadHysteresis() const { return overloadHysteresis_; } + + /** + * Set fraction of maximum limits before an overload condition is cleared. + * A good value would probably be between 0.5 and 0.9. + * + * @param hysteresisFraction fraction <= 1.0. + */ + void setOverloadHysteresis(double hysteresisFraction) { + if (hysteresisFraction <= 1.0 && hysteresisFraction > 0.0) { + overloadHysteresis_ = hysteresisFraction; + } + } + + /** + * Get the action the server will take on overload. + * + * @return a TOverloadAction enum value for the currently set action. + */ + TOverloadAction getOverloadAction() const { return overloadAction_; } + + /** + * Set the action the server is to take on overload. + * + * @param overloadAction a TOverloadAction enum value for the action. + */ + void setOverloadAction(TOverloadAction overloadAction) { overloadAction_ = overloadAction; } + + /** + * Get the time in milliseconds after which a task expires (0 == infinite). + * + * @return a 64-bit time in milliseconds. + */ + int64_t getTaskExpireTime() const { return taskExpireTime_; } + + /** + * Set the time in milliseconds after which a task expires (0 == infinite). + * + * @param taskExpireTime a 64-bit time in milliseconds. + */ + void setTaskExpireTime(int64_t taskExpireTime) { taskExpireTime_ = taskExpireTime; } + + /** + * Determine if the server is currently overloaded. + * This function checks the maximums for open connections and connections + * currently in processing, and sets an overload condition if they are + * exceeded. The overload will persist until both values are below the + * current hysteresis fraction of their maximums. + * + * @return true if an overload condition exists, false if not. + */ + bool serverOverloaded(); + + /** Pop and discard next task on threadpool wait queue. + * + * @return true if a task was discarded, false if the wait queue was empty. + */ + bool drainPendingTask(); + + /** + * Get the starting size of a TConnection object's write buffer. + * + * @return # bytes we initialize a TConnection object's write buffer to. + */ + size_t getWriteBufferDefaultSize() const { return writeBufferDefaultSize_; } + + /** + * Set the starting size of a TConnection object's write buffer. + * + * @param size # bytes we initialize a TConnection object's write buffer to. + */ + void setWriteBufferDefaultSize(size_t size) { writeBufferDefaultSize_ = size; } + + /** + * Get the maximum size of read buffer allocated to idle TConnection objects. + * + * @return # bytes beyond which we will dealloc idle buffer. + */ + size_t getIdleReadBufferLimit() const { return idleReadBufferLimit_; } + + /** + * [NOTE: This is for backwards compatibility, use getIdleReadBufferLimit().] + * Get the maximum size of read buffer allocated to idle TConnection objects. + * + * @return # bytes beyond which we will dealloc idle buffer. + */ + size_t getIdleBufferMemLimit() const { return idleReadBufferLimit_; } + + /** + * Set the maximum size read buffer allocated to idle TConnection objects. + * If a TConnection object is found (either on connection close or between + * calls when resizeBufferEveryN_ is set) with more than this much memory + * allocated to its read buffer, we free it and allow it to be reinitialized + * on the next received frame. + * + * @param limit of bytes beyond which we will shrink buffers when checked. + */ + void setIdleReadBufferLimit(size_t limit) { idleReadBufferLimit_ = limit; } + + /** + * [NOTE: This is for backwards compatibility, use setIdleReadBufferLimit().] + * Set the maximum size read buffer allocated to idle TConnection objects. + * If a TConnection object is found (either on connection close or between + * calls when resizeBufferEveryN_ is set) with more than this much memory + * allocated to its read buffer, we free it and allow it to be reinitialized + * on the next received frame. + * + * @param limit of bytes beyond which we will shrink buffers when checked. + */ + void setIdleBufferMemLimit(size_t limit) { idleReadBufferLimit_ = limit; } + + /** + * Get the maximum size of write buffer allocated to idle TConnection objects. + * + * @return # bytes beyond which we will reallocate buffers when checked. + */ + size_t getIdleWriteBufferLimit() const { return idleWriteBufferLimit_; } + + /** + * Set the maximum size write buffer allocated to idle TConnection objects. + * If a TConnection object is found (either on connection close or between + * calls when resizeBufferEveryN_ is set) with more than this much memory + * allocated to its write buffer, we destroy and construct that buffer with + * writeBufferDefaultSize_ bytes. + * + * @param limit of bytes beyond which we will shrink buffers when idle. + */ + void setIdleWriteBufferLimit(size_t limit) { idleWriteBufferLimit_ = limit; } + + /** + * Get # of calls made between buffer size checks. 0 means disabled. + * + * @return # of calls between buffer size checks. + */ + int32_t getResizeBufferEveryN() const { return resizeBufferEveryN_; } + + /** + * Check buffer sizes every "count" calls. This allows buffer limits + * to be enforced for persistent connections with a controllable degree + * of overhead. 0 disables checks except at connection close. + * + * @param count the number of calls between checks, or 0 to disable + */ + void setResizeBufferEveryN(int32_t count) { resizeBufferEveryN_ = count; } + + /** + * Main workhorse function, starts up the server listening on a port and + * loops over the libevent handler. + */ + void serve() override; + + /** + * Causes the server to terminate gracefully (can be called from any thread). + */ + void stop() override; + + /// Creates a socket to listen on and binds it to the local port. + void createAndListenOnSocket(); + + /** + * Register the optional user-provided event-base (for single-thread servers) + * + * This method should be used when the server is running in a single-thread + * mode, and the event base is provided by the user (i.e., the caller). + * + * @param user_event_base the user-provided event-base. The user is + * responsible for freeing the event base memory. + */ + void registerEvents(event_base* user_event_base); + + /** + * Returns the optional user-provided event-base (for single-thread servers). + */ + event_base* getUserEventBase() const { return userEventBase_; } + + /** Some transports, like THeaderTransport, require passing through + * the framing size instead of stripping it. + */ + bool getHeaderTransport(); + +private: + /** + * Callback function that the threadmanager calls when a task reaches + * its expiration time. It is needed to clean up the expired connection. + * + * @param task the runnable associated with the expired task. + */ + void expireClose(std::shared_ptr task); + + /** + * Return an initialized connection object. Creates or recovers from + * pool a TConnection and initializes it with the provided socket FD + * and flags. + * + * @param socket FD of socket associated with this connection. + * @param addr the sockaddr of the client + * @param addrLen the length of addr + * @return pointer to initialized TConnection object. + */ + TConnection* createConnection(std::shared_ptr socket); + + /** + * Returns a connection to pool or deletion. If the connection pool + * (a stack) isn't full, place the connection object on it, otherwise + * just delete it. + * + * @param connection the TConection being returned. + */ + void returnConnection(TConnection* connection); +}; + +class TNonblockingIOThread : public Runnable { +public: + // Creates an IO thread and sets up the event base. The listenSocket should + // be a valid FD on which listen() has already been called. If the + // listenSocket is < 0, accepting will not be done. + TNonblockingIOThread(TNonblockingServer* server, + int number, + THRIFT_SOCKET listenSocket, + bool useHighPriority); + + ~TNonblockingIOThread() override; + + // Returns the event-base for this thread. + event_base* getEventBase() const { return eventBase_; } + + // Returns the server for this thread. + TNonblockingServer* getServer() const { return server_; } + + // Returns the number of this IO thread. + int getThreadNumber() const { return number_; } + + // Returns the thread id associated with this object. This should + // only be called after the thread has been started. + Thread::id_t getThreadId() const { return threadId_; } + + // Returns the send-fd for task complete notifications. + evutil_socket_t getNotificationSendFD() const { return notificationPipeFDs_[1]; } + + // Returns the read-fd for task complete notifications. + evutil_socket_t getNotificationRecvFD() const { return notificationPipeFDs_[0]; } + + // Returns the actual thread object associated with this IO thread. + std::shared_ptr getThread() const { return thread_; } + + // Sets the actual thread object associated with this IO thread. + void setThread(const std::shared_ptr& t) { thread_ = t; } + + // Used by TConnection objects to indicate processing has finished. + bool notify(TNonblockingServer::TConnection* conn); + + // Enters the event loop and does not return until a call to stop(). + void run() override; + + // Exits the event loop as soon as possible. + void stop(); + + // Ensures that the event-loop thread is fully finished and shut down. + void join(); + + /// Registers the events for the notification & listen sockets + void registerEvents(); + +private: + /** + * C-callable event handler for signaling task completion. Provides a + * callback that libevent can understand that will read a connection + * object's address from a pipe and call connection->transition() for + * that object. + * + * @param fd the descriptor the event occurred on. + */ + static void notifyHandler(evutil_socket_t fd, short which, void* v); + + /** + * C-callable event handler for listener events. Provides a callback + * that libevent can understand which invokes server->handleEvent(). + * + * @param fd the descriptor the event occurred on. + * @param which the flags associated with the event. + * @param v void* callback arg where we placed TNonblockingServer's "this". + */ + static void listenHandler(evutil_socket_t fd, short which, void* v) { + ((TNonblockingServer*)v)->handleEvent(fd, which); + } + + /// Exits the loop ASAP in case of shutdown or error. + void breakLoop(bool error); + + /// Create the pipe used to notify I/O process of task completion. + void createNotificationPipe(); + + /// Unregisters our events for notification and listen sockets. + void cleanupEvents(); + + /// Sets (or clears) high priority scheduling status for the current thread. + void setCurrentThreadHighPriority(bool value); + +private: + /// associated server + TNonblockingServer* server_; + + /// thread number (for debugging). + const int number_; + + /// The actual physical thread id. + Thread::id_t threadId_; + + /// If listenSocket_ >= 0, adds an event on the event_base to accept conns + THRIFT_SOCKET listenSocket_; + + /// Sets a high scheduling priority when running + bool useHighPriority_; + + /// pointer to eventbase to be used for looping + event_base* eventBase_; + + /// Set to true if this class is responsible for freeing the event base + /// memory. + bool ownEventBase_; + + /// Used with eventBase_ for connection events (only in listener thread) + struct event serverEvent_; + + /// Used with eventBase_ for task completion notification + struct event notificationEvent_; + + /// File descriptors for pipe used for task completion notification. + evutil_socket_t notificationPipeFDs_[2]; + + /// Actual IO Thread + std::shared_ptr thread_; +}; +} +} +} // apache::thrift::server + +#endif // #ifndef _THRIFT_SERVER_TNONBLOCKINGSERVER_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/server/TServer.cpp b/src/jaegertracing/thrift/lib/cpp/src/thrift/server/TServer.cpp new file mode 100644 index 000000000..df731c2f0 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/server/TServer.cpp @@ -0,0 +1,52 @@ +/* + * 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. + */ + +#include + +#ifdef HAVE_SYS_TIME_H +#include +#endif +#ifdef HAVE_SYS_RESOURCE_H +#include +#endif + +#ifdef HAVE_UNISTD_H +#include +#endif + +namespace apache { +namespace thrift { +namespace server { + +#ifdef HAVE_SYS_RESOURCE_H +int increase_max_fds(int max_fds = (1 << 24)) { + struct rlimit fdmaxrl; + + for (fdmaxrl.rlim_cur = max_fds, fdmaxrl.rlim_max = max_fds; + max_fds && (setrlimit(RLIMIT_NOFILE, &fdmaxrl) < 0); + fdmaxrl.rlim_cur = max_fds, fdmaxrl.rlim_max = max_fds) { + max_fds /= 2; + } + + return static_cast(fdmaxrl.rlim_cur); +} +#endif +} +} +} // apache::thrift::server diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/server/TServer.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/server/TServer.h new file mode 100644 index 000000000..d2eabde12 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/server/TServer.h @@ -0,0 +1,273 @@ +/* + * 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. + */ + +#ifndef _THRIFT_SERVER_TSERVER_H_ +#define _THRIFT_SERVER_TSERVER_H_ 1 + +#include +#include +#include +#include + +#include + +namespace apache { +namespace thrift { +namespace server { + +using apache::thrift::TProcessor; +using apache::thrift::protocol::TBinaryProtocolFactory; +using apache::thrift::protocol::TProtocol; +using apache::thrift::protocol::TProtocolFactory; +using apache::thrift::transport::TServerTransport; +using apache::thrift::transport::TTransport; +using apache::thrift::transport::TTransportFactory; + +/** + * Virtual interface class that can handle events from the server core. To + * use this you should subclass it and implement the methods that you care + * about. Your subclass can also store local data that you may care about, + * such as additional "arguments" to these methods (stored in the object + * instance's state). + */ +class TServerEventHandler { +public: + virtual ~TServerEventHandler() = default; + + /** + * Called before the server begins. + */ + virtual void preServe() {} + + /** + * Called when a new client has connected and is about to being processing. + */ + virtual void* createContext(std::shared_ptr input, + std::shared_ptr output) { + (void)input; + (void)output; + return nullptr; + } + + /** + * Called when a client has finished request-handling to delete server + * context. + */ + virtual void deleteContext(void* serverContext, + std::shared_ptr input, + std::shared_ptr output) { + (void)serverContext; + (void)input; + (void)output; + } + + /** + * Called when a client is about to call the processor. + */ + virtual void processContext(void* serverContext, std::shared_ptr transport) { + (void)serverContext; + (void)transport; + } + +protected: + /** + * Prevent direct instantiation. + */ + TServerEventHandler() = default; +}; + +/** + * Thrift server. + * + */ +class TServer : public concurrency::Runnable { +public: + ~TServer() override = default; + + virtual void serve() = 0; + + virtual void stop() {} + + // Allows running the server as a Runnable thread + void run() override { serve(); } + + std::shared_ptr getProcessorFactory() { return processorFactory_; } + + std::shared_ptr getServerTransport() { return serverTransport_; } + + std::shared_ptr getInputTransportFactory() { return inputTransportFactory_; } + + std::shared_ptr getOutputTransportFactory() { + return outputTransportFactory_; + } + + std::shared_ptr getInputProtocolFactory() { return inputProtocolFactory_; } + + std::shared_ptr getOutputProtocolFactory() { return outputProtocolFactory_; } + + std::shared_ptr getEventHandler() { return eventHandler_; } + +protected: + TServer(const std::shared_ptr& processorFactory) + : processorFactory_(processorFactory) { + setInputTransportFactory(std::shared_ptr(new TTransportFactory())); + setOutputTransportFactory(std::shared_ptr(new TTransportFactory())); + setInputProtocolFactory(std::shared_ptr(new TBinaryProtocolFactory())); + setOutputProtocolFactory(std::shared_ptr(new TBinaryProtocolFactory())); + } + + TServer(const std::shared_ptr& processor) + : processorFactory_(new TSingletonProcessorFactory(processor)) { + setInputTransportFactory(std::shared_ptr(new TTransportFactory())); + setOutputTransportFactory(std::shared_ptr(new TTransportFactory())); + setInputProtocolFactory(std::shared_ptr(new TBinaryProtocolFactory())); + setOutputProtocolFactory(std::shared_ptr(new TBinaryProtocolFactory())); + } + + TServer(const std::shared_ptr& processorFactory, + const std::shared_ptr& serverTransport) + : processorFactory_(processorFactory), serverTransport_(serverTransport) { + setInputTransportFactory(std::shared_ptr(new TTransportFactory())); + setOutputTransportFactory(std::shared_ptr(new TTransportFactory())); + setInputProtocolFactory(std::shared_ptr(new TBinaryProtocolFactory())); + setOutputProtocolFactory(std::shared_ptr(new TBinaryProtocolFactory())); + } + + TServer(const std::shared_ptr& processor, + const std::shared_ptr& serverTransport) + : processorFactory_(new TSingletonProcessorFactory(processor)), + serverTransport_(serverTransport) { + setInputTransportFactory(std::shared_ptr(new TTransportFactory())); + setOutputTransportFactory(std::shared_ptr(new TTransportFactory())); + setInputProtocolFactory(std::shared_ptr(new TBinaryProtocolFactory())); + setOutputProtocolFactory(std::shared_ptr(new TBinaryProtocolFactory())); + } + + TServer(const std::shared_ptr& processorFactory, + const std::shared_ptr& serverTransport, + const std::shared_ptr& transportFactory, + const std::shared_ptr& protocolFactory) + : processorFactory_(processorFactory), + serverTransport_(serverTransport), + inputTransportFactory_(transportFactory), + outputTransportFactory_(transportFactory), + inputProtocolFactory_(protocolFactory), + outputProtocolFactory_(protocolFactory) {} + + TServer(const std::shared_ptr& processor, + const std::shared_ptr& serverTransport, + const std::shared_ptr& transportFactory, + const std::shared_ptr& protocolFactory) + : processorFactory_(new TSingletonProcessorFactory(processor)), + serverTransport_(serverTransport), + inputTransportFactory_(transportFactory), + outputTransportFactory_(transportFactory), + inputProtocolFactory_(protocolFactory), + outputProtocolFactory_(protocolFactory) {} + + TServer(const std::shared_ptr& processorFactory, + const std::shared_ptr& serverTransport, + const std::shared_ptr& inputTransportFactory, + const std::shared_ptr& outputTransportFactory, + const std::shared_ptr& inputProtocolFactory, + const std::shared_ptr& outputProtocolFactory) + : processorFactory_(processorFactory), + serverTransport_(serverTransport), + inputTransportFactory_(inputTransportFactory), + outputTransportFactory_(outputTransportFactory), + inputProtocolFactory_(inputProtocolFactory), + outputProtocolFactory_(outputProtocolFactory) {} + + TServer(const std::shared_ptr& processor, + const std::shared_ptr& serverTransport, + const std::shared_ptr& inputTransportFactory, + const std::shared_ptr& outputTransportFactory, + const std::shared_ptr& inputProtocolFactory, + const std::shared_ptr& outputProtocolFactory) + : processorFactory_(new TSingletonProcessorFactory(processor)), + serverTransport_(serverTransport), + inputTransportFactory_(inputTransportFactory), + outputTransportFactory_(outputTransportFactory), + inputProtocolFactory_(inputProtocolFactory), + outputProtocolFactory_(outputProtocolFactory) {} + + /** + * Get a TProcessor to handle calls on a particular connection. + * + * This method should only be called once per connection (never once per + * call). This allows the TProcessorFactory to return a different processor + * for each connection if it desires. + */ + std::shared_ptr getProcessor(std::shared_ptr inputProtocol, + std::shared_ptr outputProtocol, + std::shared_ptr transport) { + TConnectionInfo connInfo; + connInfo.input = inputProtocol; + connInfo.output = outputProtocol; + connInfo.transport = transport; + return processorFactory_->getProcessor(connInfo); + } + + // Class variables + std::shared_ptr processorFactory_; + std::shared_ptr serverTransport_; + + std::shared_ptr inputTransportFactory_; + std::shared_ptr outputTransportFactory_; + + std::shared_ptr inputProtocolFactory_; + std::shared_ptr outputProtocolFactory_; + + std::shared_ptr eventHandler_; + +public: + void setInputTransportFactory(std::shared_ptr inputTransportFactory) { + inputTransportFactory_ = inputTransportFactory; + } + + void setOutputTransportFactory(std::shared_ptr outputTransportFactory) { + outputTransportFactory_ = outputTransportFactory; + } + + void setInputProtocolFactory(std::shared_ptr inputProtocolFactory) { + inputProtocolFactory_ = inputProtocolFactory; + } + + void setOutputProtocolFactory(std::shared_ptr outputProtocolFactory) { + outputProtocolFactory_ = outputProtocolFactory; + } + + void setServerEventHandler(std::shared_ptr eventHandler) { + eventHandler_ = eventHandler; + } +}; + +/** + * Helper function to increase the max file descriptors limit + * for the current process and all of its children. + * By default, tries to increase it to as much as 2^24. + */ +#ifdef HAVE_SYS_RESOURCE_H +int increase_max_fds(int max_fds = (1 << 24)); +#endif +} +} +} // apache::thrift::server + +#endif // #ifndef _THRIFT_SERVER_TSERVER_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/server/TServerFramework.cpp b/src/jaegertracing/thrift/lib/cpp/src/thrift/server/TServerFramework.cpp new file mode 100644 index 000000000..35f3b254d --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/server/TServerFramework.cpp @@ -0,0 +1,244 @@ +/* + * 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. + */ + +#include +#include +#include +#include + +namespace apache { +namespace thrift { +namespace server { + +using apache::thrift::concurrency::Synchronized; +using apache::thrift::protocol::TProtocol; +using apache::thrift::protocol::TProtocolFactory; +using std::bind; +using std::shared_ptr; +using apache::thrift::transport::TServerTransport; +using apache::thrift::transport::TTransport; +using apache::thrift::transport::TTransportException; +using apache::thrift::transport::TTransportFactory; +using std::string; + +TServerFramework::TServerFramework(const shared_ptr& processorFactory, + const shared_ptr& serverTransport, + const shared_ptr& transportFactory, + const shared_ptr& protocolFactory) + : TServer(processorFactory, serverTransport, transportFactory, protocolFactory), + clients_(0), + hwm_(0), + limit_(INT64_MAX) { +} + +TServerFramework::TServerFramework(const shared_ptr& processor, + const shared_ptr& serverTransport, + const shared_ptr& transportFactory, + const shared_ptr& protocolFactory) + : TServer(processor, serverTransport, transportFactory, protocolFactory), + clients_(0), + hwm_(0), + limit_(INT64_MAX) { +} + +TServerFramework::TServerFramework(const shared_ptr& processorFactory, + const shared_ptr& serverTransport, + const shared_ptr& inputTransportFactory, + const shared_ptr& outputTransportFactory, + const shared_ptr& inputProtocolFactory, + const shared_ptr& outputProtocolFactory) + : TServer(processorFactory, + serverTransport, + inputTransportFactory, + outputTransportFactory, + inputProtocolFactory, + outputProtocolFactory), + clients_(0), + hwm_(0), + limit_(INT64_MAX) { +} + +TServerFramework::TServerFramework(const shared_ptr& processor, + const shared_ptr& serverTransport, + const shared_ptr& inputTransportFactory, + const shared_ptr& outputTransportFactory, + const shared_ptr& inputProtocolFactory, + const shared_ptr& outputProtocolFactory) + : TServer(processor, + serverTransport, + inputTransportFactory, + outputTransportFactory, + inputProtocolFactory, + outputProtocolFactory), + clients_(0), + hwm_(0), + limit_(INT64_MAX) { +} + +TServerFramework::~TServerFramework() = default; + +template +static void releaseOneDescriptor(const string& name, T& pTransport) { + if (pTransport) { + try { + pTransport->close(); + } catch (const TTransportException& ttx) { + string errStr = string("TServerFramework " + name + " close failed: ") + ttx.what(); + GlobalOutput(errStr.c_str()); + } + } +} + +void TServerFramework::serve() { + shared_ptr client; + shared_ptr inputTransport; + shared_ptr outputTransport; + shared_ptr inputProtocol; + shared_ptr outputProtocol; + + // Start the server listening + serverTransport_->listen(); + + // Run the preServe event to indicate server is now listening + // and that it is safe to connect. + if (eventHandler_) { + eventHandler_->preServe(); + } + + // Fetch client from server + for (;;) { + try { + // Dereference any resources from any previous client creation + // such that a blocking accept does not hold them indefinitely. + outputProtocol.reset(); + inputProtocol.reset(); + outputTransport.reset(); + inputTransport.reset(); + client.reset(); + + // If we have reached the limit on the number of concurrent + // clients allowed, wait for one or more clients to drain before + // accepting another. + { + Synchronized sync(mon_); + while (clients_ >= limit_) { + mon_.wait(); + } + } + + client = serverTransport_->accept(); + + inputTransport = inputTransportFactory_->getTransport(client); + outputTransport = outputTransportFactory_->getTransport(client); + if (!outputProtocolFactory_) { + inputProtocol = inputProtocolFactory_->getProtocol(inputTransport, outputTransport); + outputProtocol = inputProtocol; + } else { + inputProtocol = inputProtocolFactory_->getProtocol(inputTransport); + outputProtocol = outputProtocolFactory_->getProtocol(outputTransport); + } + + newlyConnectedClient(shared_ptr( + new TConnectedClient(getProcessor(inputProtocol, outputProtocol, client), + inputProtocol, + outputProtocol, + eventHandler_, + client), + bind(&TServerFramework::disposeConnectedClient, this, std::placeholders::_1))); + + } catch (TTransportException& ttx) { + releaseOneDescriptor("inputTransport", inputTransport); + releaseOneDescriptor("outputTransport", outputTransport); + releaseOneDescriptor("client", client); + if (ttx.getType() == TTransportException::TIMED_OUT) { + // Accept timeout - continue processing. + continue; + } else if (ttx.getType() == TTransportException::END_OF_FILE + || ttx.getType() == TTransportException::INTERRUPTED) { + // Server was interrupted. This only happens when stopping. + break; + } else { + // All other transport exceptions are logged. + // State of connection is unknown. Done. + string errStr = string("TServerTransport died: ") + ttx.what(); + GlobalOutput(errStr.c_str()); + break; + } + } + } + + releaseOneDescriptor("serverTransport", serverTransport_); +} + +int64_t TServerFramework::getConcurrentClientLimit() const { + Synchronized sync(mon_); + return limit_; +} + +int64_t TServerFramework::getConcurrentClientCount() const { + Synchronized sync(mon_); + return clients_; +} + +int64_t TServerFramework::getConcurrentClientCountHWM() const { + Synchronized sync(mon_); + return hwm_; +} + +void TServerFramework::setConcurrentClientLimit(int64_t newLimit) { + if (newLimit < 1) { + throw std::invalid_argument("newLimit must be greater than zero"); + } + Synchronized sync(mon_); + limit_ = newLimit; + if (limit_ - clients_ > 0) { + mon_.notify(); + } +} + +void TServerFramework::stop() { + // Order is important because serve() releases serverTransport_ when it is + // interrupted, which closes the socket that interruptChildren uses. + serverTransport_->interruptChildren(); + serverTransport_->interrupt(); +} + +void TServerFramework::newlyConnectedClient(const shared_ptr& pClient) { + { + Synchronized sync(mon_); + ++clients_; + hwm_ = (std::max)(hwm_, clients_); + } + + onClientConnected(pClient); +} + +void TServerFramework::disposeConnectedClient(TConnectedClient* pClient) { + onClientDisconnected(pClient); + delete pClient; + + Synchronized sync(mon_); + if (limit_ - --clients_ > 0) { + mon_.notify(); + } +} + +} +} +} // apache::thrift::server diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/server/TServerFramework.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/server/TServerFramework.h new file mode 100644 index 000000000..dac79ef59 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/server/TServerFramework.h @@ -0,0 +1,184 @@ +/* + * 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. + */ + +#ifndef _THRIFT_SERVER_TSERVERFRAMEWORK_H_ +#define _THRIFT_SERVER_TSERVERFRAMEWORK_H_ 1 + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace apache { +namespace thrift { +namespace server { + +/** + * TServerFramework provides a single consolidated processing loop for + * servers. By having a single processing loop, behavior between servers + * is more predictable and maintenance cost is lowered. Implementations + * of TServerFramework must provide a method to deal with a client that + * connects and one that disconnects. + * + * While this functionality could be rolled directly into TServer, and + * probably should be, it would break the TServer interface contract so + * to maintain backwards compatibility for third party servers, no TServers + * were harmed in the making of this class. + */ +class TServerFramework : public TServer { +public: + TServerFramework( + const std::shared_ptr& processorFactory, + const std::shared_ptr& serverTransport, + const std::shared_ptr& transportFactory, + const std::shared_ptr& protocolFactory); + + TServerFramework( + const std::shared_ptr& processor, + const std::shared_ptr& serverTransport, + const std::shared_ptr& transportFactory, + const std::shared_ptr& protocolFactory); + + TServerFramework( + const std::shared_ptr& processorFactory, + const std::shared_ptr& serverTransport, + const std::shared_ptr& inputTransportFactory, + const std::shared_ptr& outputTransportFactory, + const std::shared_ptr& inputProtocolFactory, + const std::shared_ptr& outputProtocolFactory); + + TServerFramework( + const std::shared_ptr& processor, + const std::shared_ptr& serverTransport, + const std::shared_ptr& inputTransportFactory, + const std::shared_ptr& outputTransportFactory, + const std::shared_ptr& inputProtocolFactory, + const std::shared_ptr& outputProtocolFactory); + + ~TServerFramework() override; + + /** + * Accept clients from the TServerTransport and add them for processing. + * Call stop() on another thread to interrupt processing + * and return control to the caller. + * Post-conditions (return guarantees): + * The serverTransport will be closed. + */ + void serve() override; + + /** + * Interrupt serve() so that it meets post-conditions and returns. + */ + void stop() override; + + /** + * Get the concurrent client limit. + * \returns the concurrent client limit + */ + virtual int64_t getConcurrentClientLimit() const; + + /** + * Get the number of currently connected clients. + * \returns the number of currently connected clients + */ + virtual int64_t getConcurrentClientCount() const; + + /** + * Get the highest number of concurrent clients. + * \returns the highest number of concurrent clients + */ + virtual int64_t getConcurrentClientCountHWM() const; + + /** + * Set the concurrent client limit. This can be changed while + * the server is serving however it will not necessarily be + * enforced until the next client is accepted and added. If the + * limit is lowered below the number of connected clients, no + * action is taken to disconnect the clients. + * The default value used if this is not called is INT64_MAX. + * \param[in] newLimit the new limit of concurrent clients + * \throws std::invalid_argument if newLimit is less than 1 + */ + virtual void setConcurrentClientLimit(int64_t newLimit); + +protected: + /** + * A client has connected. The implementation is responsible for managing the + * lifetime of the client object. This is called during the serve() thread, + * therefore a failure to return quickly will result in new client connection + * delays. + * + * \param[in] pClient the newly connected client + */ + virtual void onClientConnected(const std::shared_ptr& pClient) = 0; + + /** + * A client has disconnected. + * When called: + * The server no longer tracks the client. + * The client TTransport has already been closed. + * The implementation must not delete the pointer. + * + * \param[in] pClient the disconnected client + */ + virtual void onClientDisconnected(TConnectedClient* pClient) = 0; + +private: + /** + * Common handling for new connected clients. Implements concurrent + * client rate limiting after onClientConnected returns by blocking the + * serve() thread if the limit has been reached. + */ + void newlyConnectedClient(const std::shared_ptr& pClient); + + /** + * Smart pointer client deletion. + * Calls onClientDisconnected and then deletes pClient. + */ + void disposeConnectedClient(TConnectedClient* pClient); + + /** + * Monitor for limiting the number of concurrent clients. + */ + apache::thrift::concurrency::Monitor mon_; + + /** + * The number of concurrent clients. + */ + int64_t clients_; + + /** + * The high water mark of concurrent clients. + */ + int64_t hwm_; + + /** + * The limit on the number of concurrent clients. + */ + int64_t limit_; +}; +} +} +} // apache::thrift::server + +#endif // #ifndef _THRIFT_SERVER_TSERVERFRAMEWORK_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/server/TSimpleServer.cpp b/src/jaegertracing/thrift/lib/cpp/src/thrift/server/TSimpleServer.cpp new file mode 100644 index 000000000..ba7a183db --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/server/TSimpleServer.cpp @@ -0,0 +1,106 @@ +/* + * 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. + */ + +#include + +namespace apache { +namespace thrift { +namespace server { + +using apache::thrift::protocol::TProtocol; +using apache::thrift::protocol::TProtocolFactory; +using apache::thrift::transport::TServerTransport; +using apache::thrift::transport::TTransport; +using apache::thrift::transport::TTransportException; +using apache::thrift::transport::TTransportFactory; +using std::shared_ptr; +using std::string; + +TSimpleServer::TSimpleServer(const shared_ptr& processorFactory, + const shared_ptr& serverTransport, + const shared_ptr& transportFactory, + const shared_ptr& protocolFactory) + : TServerFramework(processorFactory, serverTransport, transportFactory, protocolFactory) { + TServerFramework::setConcurrentClientLimit(1); +} + +TSimpleServer::TSimpleServer(const shared_ptr& processor, + const shared_ptr& serverTransport, + const shared_ptr& transportFactory, + const shared_ptr& protocolFactory) + : TServerFramework(processor, serverTransport, transportFactory, protocolFactory) { + TServerFramework::setConcurrentClientLimit(1); +} + +TSimpleServer::TSimpleServer(const shared_ptr& processorFactory, + const shared_ptr& serverTransport, + const shared_ptr& inputTransportFactory, + const shared_ptr& outputTransportFactory, + const shared_ptr& inputProtocolFactory, + const shared_ptr& outputProtocolFactory) + : TServerFramework(processorFactory, + serverTransport, + inputTransportFactory, + outputTransportFactory, + inputProtocolFactory, + outputProtocolFactory) { + TServerFramework::setConcurrentClientLimit(1); +} + +TSimpleServer::TSimpleServer(const shared_ptr& processor, + const shared_ptr& serverTransport, + const shared_ptr& inputTransportFactory, + const shared_ptr& outputTransportFactory, + const shared_ptr& inputProtocolFactory, + const shared_ptr& outputProtocolFactory) + : TServerFramework(processor, + serverTransport, + inputTransportFactory, + outputTransportFactory, + inputProtocolFactory, + outputProtocolFactory) { + TServerFramework::setConcurrentClientLimit(1); +} + +TSimpleServer::~TSimpleServer() = default; + +/** + * The main body of customized implementation for TSimpleServer is quite simple: + * When a client connects, use the serve() thread to drive it to completion thus + * blocking new connections. + */ +void TSimpleServer::onClientConnected(const shared_ptr& pClient) { + pClient->run(); +} + +/** + * TSimpleServer does not track clients so there is nothing to do here. + */ +void TSimpleServer::onClientDisconnected(TConnectedClient*) { +} + +/** + * This makes little sense to the simple server because it is not capable + * of having more than one client at a time, so we hide it. + */ +void TSimpleServer::setConcurrentClientLimit(int64_t) { +} +} +} +} // apache::thrift::server diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/server/TSimpleServer.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/server/TSimpleServer.h new file mode 100644 index 000000000..3afeb79d5 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/server/TSimpleServer.h @@ -0,0 +1,77 @@ +/* + * 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. + */ + +#ifndef _THRIFT_SERVER_TSIMPLESERVER_H_ +#define _THRIFT_SERVER_TSIMPLESERVER_H_ 1 + +#include + +namespace apache { +namespace thrift { +namespace server { + +/** + * This is the most basic simple server. It is single-threaded and runs a + * continuous loop of accepting a single connection, processing requests on + * that connection until it closes, and then repeating. + */ +class TSimpleServer : public TServerFramework { +public: + TSimpleServer( + const std::shared_ptr& processorFactory, + const std::shared_ptr& serverTransport, + const std::shared_ptr& transportFactory, + const std::shared_ptr& protocolFactory); + + TSimpleServer( + const std::shared_ptr& processor, + const std::shared_ptr& serverTransport, + const std::shared_ptr& transportFactory, + const std::shared_ptr& protocolFactory); + + TSimpleServer( + const std::shared_ptr& processorFactory, + const std::shared_ptr& serverTransport, + const std::shared_ptr& inputTransportFactory, + const std::shared_ptr& outputTransportFactory, + const std::shared_ptr& inputProtocolFactory, + const std::shared_ptr& outputProtocolFactory); + + TSimpleServer( + const std::shared_ptr& processor, + const std::shared_ptr& serverTransport, + const std::shared_ptr& inputTransportFactory, + const std::shared_ptr& outputTransportFactory, + const std::shared_ptr& inputProtocolFactory, + const std::shared_ptr& outputProtocolFactory); + + ~TSimpleServer() override; + +protected: + void onClientConnected(const std::shared_ptr& pClient) override /* override */; + void onClientDisconnected(TConnectedClient* pClient) override /* override */; + +private: + void setConcurrentClientLimit(int64_t newLimit) override; // hide +}; +} +} +} // apache::thrift::server + +#endif // #ifndef _THRIFT_SERVER_TSIMPLESERVER_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/server/TThreadPoolServer.cpp b/src/jaegertracing/thrift/lib/cpp/src/thrift/server/TThreadPoolServer.cpp new file mode 100644 index 000000000..121dde3ea --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/server/TThreadPoolServer.cpp @@ -0,0 +1,131 @@ +/* + * 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. + */ + +#include + +namespace apache { +namespace thrift { +namespace server { + +using apache::thrift::concurrency::ThreadManager; +using apache::thrift::protocol::TProtocol; +using apache::thrift::protocol::TProtocolFactory; +using apache::thrift::transport::TServerTransport; +using apache::thrift::transport::TTransport; +using apache::thrift::transport::TTransportException; +using apache::thrift::transport::TTransportFactory; +using std::shared_ptr; +using std::string; + +TThreadPoolServer::TThreadPoolServer(const shared_ptr& processorFactory, + const shared_ptr& serverTransport, + const shared_ptr& transportFactory, + const shared_ptr& protocolFactory, + const shared_ptr& threadManager) + : TServerFramework(processorFactory, serverTransport, transportFactory, protocolFactory), + threadManager_(threadManager), + timeout_(0), + taskExpiration_(0) { +} + +TThreadPoolServer::TThreadPoolServer(const shared_ptr& processor, + const shared_ptr& serverTransport, + const shared_ptr& transportFactory, + const shared_ptr& protocolFactory, + const shared_ptr& threadManager) + : TServerFramework(processor, serverTransport, transportFactory, protocolFactory), + threadManager_(threadManager), + timeout_(0), + taskExpiration_(0) { +} + +TThreadPoolServer::TThreadPoolServer(const shared_ptr& processorFactory, + const shared_ptr& serverTransport, + const shared_ptr& inputTransportFactory, + const shared_ptr& outputTransportFactory, + const shared_ptr& inputProtocolFactory, + const shared_ptr& outputProtocolFactory, + const shared_ptr& threadManager) + : TServerFramework(processorFactory, + serverTransport, + inputTransportFactory, + outputTransportFactory, + inputProtocolFactory, + outputProtocolFactory), + threadManager_(threadManager), + timeout_(0), + taskExpiration_(0) { +} + +TThreadPoolServer::TThreadPoolServer(const shared_ptr& processor, + const shared_ptr& serverTransport, + const shared_ptr& inputTransportFactory, + const shared_ptr& outputTransportFactory, + const shared_ptr& inputProtocolFactory, + const shared_ptr& outputProtocolFactory, + const shared_ptr& threadManager) + : TServerFramework(processor, + serverTransport, + inputTransportFactory, + outputTransportFactory, + inputProtocolFactory, + outputProtocolFactory), + threadManager_(threadManager), + timeout_(0), + taskExpiration_(0) { +} + +TThreadPoolServer::~TThreadPoolServer() = default; + +void TThreadPoolServer::serve() { + TServerFramework::serve(); + threadManager_->stop(); +} + +int64_t TThreadPoolServer::getTimeout() const { + return timeout_; +} + +void TThreadPoolServer::setTimeout(int64_t value) { + timeout_ = value; +} + +int64_t TThreadPoolServer::getTaskExpiration() const { + return taskExpiration_; +} + +void TThreadPoolServer::setTaskExpiration(int64_t value) { + taskExpiration_ = value; +} + +std::shared_ptr +TThreadPoolServer::getThreadManager() const { + return threadManager_; +} + +void TThreadPoolServer::onClientConnected(const shared_ptr& pClient) { + threadManager_->add(pClient, getTimeout(), getTaskExpiration()); +} + +void TThreadPoolServer::onClientDisconnected(TConnectedClient*) { +} + +} +} +} // apache::thrift::server diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/server/TThreadPoolServer.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/server/TThreadPoolServer.h new file mode 100644 index 000000000..a9411b86c --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/server/TThreadPoolServer.h @@ -0,0 +1,101 @@ +/* + * 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. + */ + +#ifndef _THRIFT_SERVER_TTHREADPOOLSERVER_H_ +#define _THRIFT_SERVER_TTHREADPOOLSERVER_H_ 1 + +#include +#include +#include + +namespace apache { +namespace thrift { +namespace server { + +/** + * Manage clients using a thread pool. + */ +class TThreadPoolServer : public TServerFramework { +public: + TThreadPoolServer( + const std::shared_ptr& processorFactory, + const std::shared_ptr& serverTransport, + const std::shared_ptr& transportFactory, + const std::shared_ptr& protocolFactory, + const std::shared_ptr& threadManager + = apache::thrift::concurrency::ThreadManager::newSimpleThreadManager()); + + TThreadPoolServer( + const std::shared_ptr& processor, + const std::shared_ptr& serverTransport, + const std::shared_ptr& transportFactory, + const std::shared_ptr& protocolFactory, + const std::shared_ptr& threadManager + = apache::thrift::concurrency::ThreadManager::newSimpleThreadManager()); + + TThreadPoolServer( + const std::shared_ptr& processorFactory, + const std::shared_ptr& serverTransport, + const std::shared_ptr& inputTransportFactory, + const std::shared_ptr& outputTransportFactory, + const std::shared_ptr& inputProtocolFactory, + const std::shared_ptr& outputProtocolFactory, + const std::shared_ptr& threadManager + = apache::thrift::concurrency::ThreadManager::newSimpleThreadManager()); + + TThreadPoolServer( + const std::shared_ptr& processor, + const std::shared_ptr& serverTransport, + const std::shared_ptr& inputTransportFactory, + const std::shared_ptr& outputTransportFactory, + const std::shared_ptr& inputProtocolFactory, + const std::shared_ptr& outputProtocolFactory, + const std::shared_ptr& threadManager + = apache::thrift::concurrency::ThreadManager::newSimpleThreadManager()); + + ~TThreadPoolServer() override; + + /** + * Post-conditions (return guarantees): + * There will be no clients connected. + */ + void serve() override; + + virtual int64_t getTimeout() const; + virtual void setTimeout(int64_t value); + + virtual int64_t getTaskExpiration() const; + virtual void setTaskExpiration(int64_t value); + + virtual std::shared_ptr getThreadManager() const; + +protected: + void onClientConnected(const std::shared_ptr& pClient) override /* override */; + void onClientDisconnected(TConnectedClient* pClient) override /* override */; + + std::shared_ptr threadManager_; + std::atomic timeout_; + std::atomic taskExpiration_; +}; + +} +} +} // apache::thrift::server + +#endif // #ifndef _THRIFT_SERVER_TTHREADPOOLSERVER_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/server/TThreadedServer.cpp b/src/jaegertracing/thrift/lib/cpp/src/thrift/server/TThreadedServer.cpp new file mode 100644 index 000000000..79dcc70f7 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/server/TThreadedServer.cpp @@ -0,0 +1,151 @@ +/* + * 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. + */ + +#include +#include +#include +#include + +namespace apache { +namespace thrift { +namespace server { + +using apache::thrift::concurrency::Runnable; +using apache::thrift::concurrency::Synchronized; +using apache::thrift::concurrency::Thread; +using apache::thrift::concurrency::ThreadFactory; +using apache::thrift::protocol::TProtocol; +using apache::thrift::protocol::TProtocolFactory; +using std::make_shared; +using std::shared_ptr; +using apache::thrift::transport::TServerTransport; +using apache::thrift::transport::TTransport; +using apache::thrift::transport::TTransportException; +using apache::thrift::transport::TTransportFactory; + +TThreadedServer::TThreadedServer(const shared_ptr& processorFactory, + const shared_ptr& serverTransport, + const shared_ptr& transportFactory, + const shared_ptr& protocolFactory, + const shared_ptr& threadFactory) + : TServerFramework(processorFactory, serverTransport, transportFactory, protocolFactory), + threadFactory_(threadFactory) { +} + +TThreadedServer::TThreadedServer(const shared_ptr& processor, + const shared_ptr& serverTransport, + const shared_ptr& transportFactory, + const shared_ptr& protocolFactory, + const shared_ptr& threadFactory) + : TServerFramework(processor, serverTransport, transportFactory, protocolFactory), + threadFactory_(threadFactory) { +} + +TThreadedServer::TThreadedServer(const shared_ptr& processorFactory, + const shared_ptr& serverTransport, + const shared_ptr& inputTransportFactory, + const shared_ptr& outputTransportFactory, + const shared_ptr& inputProtocolFactory, + const shared_ptr& outputProtocolFactory, + const shared_ptr& threadFactory) + : TServerFramework(processorFactory, + serverTransport, + inputTransportFactory, + outputTransportFactory, + inputProtocolFactory, + outputProtocolFactory), + threadFactory_(threadFactory) { +} + +TThreadedServer::TThreadedServer(const shared_ptr& processor, + const shared_ptr& serverTransport, + const shared_ptr& inputTransportFactory, + const shared_ptr& outputTransportFactory, + const shared_ptr& inputProtocolFactory, + const shared_ptr& outputProtocolFactory, + const shared_ptr& threadFactory) + : TServerFramework(processor, + serverTransport, + inputTransportFactory, + outputTransportFactory, + inputProtocolFactory, + outputProtocolFactory), + threadFactory_(threadFactory) { +} + +TThreadedServer::~TThreadedServer() = default; + +void TThreadedServer::serve() { + TServerFramework::serve(); + + // Ensure post-condition of no active clients + Synchronized s(clientMonitor_); + while (!activeClientMap_.empty()) { + clientMonitor_.wait(); + } + + drainDeadClients(); +} + +void TThreadedServer::drainDeadClients() { + // we're in a monitor here + while (!deadClientMap_.empty()) { + auto it = deadClientMap_.begin(); + it->second->join(); + deadClientMap_.erase(it); + } +} + +void TThreadedServer::onClientConnected(const shared_ptr& pClient) { + Synchronized sync(clientMonitor_); + shared_ptr pRunnable = make_shared(pClient); + shared_ptr pThread = threadFactory_->newThread(pRunnable); + pRunnable->thread(pThread); + activeClientMap_.insert(ClientMap::value_type(pClient.get(), pThread)); + pThread->start(); +} + +void TThreadedServer::onClientDisconnected(TConnectedClient* pClient) { + Synchronized sync(clientMonitor_); + drainDeadClients(); // use the outgoing thread to do some maintenance on our dead client backlog + auto it = activeClientMap_.find(pClient); + if (it != activeClientMap_.end()) { + auto end = it; + deadClientMap_.insert(it, ++end); + activeClientMap_.erase(it); + } + if (activeClientMap_.empty()) { + clientMonitor_.notify(); + } +} + +TThreadedServer::TConnectedClientRunner::TConnectedClientRunner(const shared_ptr& pClient) + : pClient_(pClient) { +} + +TThreadedServer::TConnectedClientRunner::~TConnectedClientRunner() = default; + +void TThreadedServer::TConnectedClientRunner::run() /* override */ { + pClient_->run(); // Run the client + pClient_.reset(); // The client is done - release it here rather than in the destructor for safety +} + +} +} +} // apache::thrift::server diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/server/TThreadedServer.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/server/TThreadedServer.h new file mode 100644 index 000000000..756e5a063 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/server/TThreadedServer.h @@ -0,0 +1,143 @@ +/* + * 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. + */ + +#ifndef _THRIFT_SERVER_TTHREADEDSERVER_H_ +#define _THRIFT_SERVER_TTHREADEDSERVER_H_ 1 + +#include +#include +#include +#include +#include + +namespace apache { +namespace thrift { +namespace server { + +/** + * Manage clients using threads - threads are created one for each client and are + * released when the client disconnects. This server is used to make a dynamically + * scalable server up to the concurrent connection limit. + */ +class TThreadedServer : public TServerFramework { +public: + TThreadedServer( + const std::shared_ptr& processorFactory, + const std::shared_ptr& serverTransport, + const std::shared_ptr& transportFactory, + const std::shared_ptr& protocolFactory, + const std::shared_ptr& threadFactory + = std::shared_ptr( + new apache::thrift::concurrency::ThreadFactory(false))); + + TThreadedServer( + const std::shared_ptr& processor, + const std::shared_ptr& serverTransport, + const std::shared_ptr& transportFactory, + const std::shared_ptr& protocolFactory, + const std::shared_ptr& threadFactory + = std::shared_ptr( + new apache::thrift::concurrency::ThreadFactory(false))); + + TThreadedServer( + const std::shared_ptr& processorFactory, + const std::shared_ptr& serverTransport, + const std::shared_ptr& inputTransportFactory, + const std::shared_ptr& outputTransportFactory, + const std::shared_ptr& inputProtocolFactory, + const std::shared_ptr& outputProtocolFactory, + const std::shared_ptr& threadFactory + = std::shared_ptr( + new apache::thrift::concurrency::ThreadFactory(false))); + + TThreadedServer( + const std::shared_ptr& processor, + const std::shared_ptr& serverTransport, + const std::shared_ptr& inputTransportFactory, + const std::shared_ptr& outputTransportFactory, + const std::shared_ptr& inputProtocolFactory, + const std::shared_ptr& outputProtocolFactory, + const std::shared_ptr& threadFactory + = std::shared_ptr( + new apache::thrift::concurrency::ThreadFactory(false))); + + ~TThreadedServer() override; + + /** + * Post-conditions (return guarantees): + * There will be no clients connected. + */ + void serve() override; + +protected: + /** + * Drain recently connected clients by joining their threads - this is done lazily because + * we cannot do it inside the thread context that is disconnecting. + */ + virtual void drainDeadClients(); + + /** + * Implementation of TServerFramework::onClientConnected + */ + void onClientConnected(const std::shared_ptr& pClient) override /* override */; + + /** + * Implementation of TServerFramework::onClientDisconnected + */ + void onClientDisconnected(TConnectedClient *pClient) override /* override */; + + std::shared_ptr threadFactory_; + + /** + * A helper wrapper used to wrap the client in something we can use to maintain + * the lifetime of the connected client within a detached thread. We cannot simply + * track the threads because a shared_ptr hangs on to the Runnable it is + * passed, and TServerFramework requires the runnable (TConnectedClient) to be + * destroyed in order to work properly. + */ + class TConnectedClientRunner : public apache::thrift::concurrency::Runnable + { + public: + TConnectedClientRunner(const std::shared_ptr& pClient); + ~TConnectedClientRunner() override; + void run() override /* override */; + private: + std::shared_ptr pClient_; + }; + + apache::thrift::concurrency::Monitor clientMonitor_; + + typedef std::map > ClientMap; + + /** + * A map of active clients + */ + ClientMap activeClientMap_; + + /** + * A map of clients that have disconnected but their threads have not been joined + */ + ClientMap deadClientMap_; +}; + +} +} +} // apache::thrift::server + +#endif // #ifndef _THRIFT_SERVER_TTHREADEDSERVER_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/thrift-config.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/thrift-config.h new file mode 100644 index 000000000..d648706c7 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/thrift-config.h @@ -0,0 +1,24 @@ +/* + * 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. + */ + +#ifdef _WIN32 +#include +#else +#include +#endif diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/thrift_export.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/thrift_export.h new file mode 100644 index 000000000..f5c059fb7 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/thrift_export.h @@ -0,0 +1,20 @@ +#ifndef THRIFT_EXPORT_H +#define THRIFT_EXPORT_H + +#ifdef THRIFT_STATIC_DEFINE +# define THRIFT_EXPORT +#elif defined(_MSC_VER ) +# ifndef THRIFT_EXPORT +# ifdef thrift_EXPORTS + /* We are building this library */ +# define THRIFT_EXPORT __declspec(dllexport) +# else + /* We are using this library */ +# define THRIFT_EXPORT __declspec(dllimport) +# endif +# endif +#else +# define THRIFT_EXPORT +#endif + +#endif /* THRIFT_EXPORT_H */ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/PlatformSocket.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/PlatformSocket.h new file mode 100644 index 000000000..959105806 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/PlatformSocket.h @@ -0,0 +1,134 @@ +/* + * 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. + */ + +// clang-format off + +#ifndef _THRIFT_TRANSPORT_PLATFORM_SOCKET_H_ +# define _THRIFT_TRANSPORT_PLATFORM_SOCKET_H_ + +#ifdef _WIN32 +# include +# define THRIFT_GET_SOCKET_ERROR ::WSAGetLastError() +# define THRIFT_ERRNO (*_errno()) +# define THRIFT_EINPROGRESS WSAEINPROGRESS +# define THRIFT_EAGAIN WSAEWOULDBLOCK +# define THRIFT_EINTR WSAEINTR +# define THRIFT_ECONNRESET WSAECONNRESET +# define THRIFT_ENOTCONN WSAENOTCONN +# define THRIFT_ETIMEDOUT WSAETIMEDOUT +# define THRIFT_EWOULDBLOCK WSAEWOULDBLOCK +# define THRIFT_EPIPE WSAECONNRESET +# define THRIFT_NO_SOCKET_CACHING SO_EXCLUSIVEADDRUSE +# define THRIFT_SOCKET SOCKET +# define THRIFT_INVALID_SOCKET INVALID_SOCKET +# define THRIFT_SOCKETPAIR thrift_socketpair +# define THRIFT_FCNTL thrift_fcntl +# define THRIFT_O_NONBLOCK 1 +# define THRIFT_F_GETFL 0 +# define THRIFT_F_SETFL 1 +# define THRIFT_GETTIMEOFDAY thrift_gettimeofday +# define THRIFT_CLOSESOCKET closesocket +# define THRIFT_CLOSE _close +# define THRIFT_OPEN _open +# define THRIFT_FTRUNCATE _chsize_s +# define THRIFT_FSYNC _commit +# define THRIFT_LSEEK _lseek +# define THRIFT_WRITE _write +# define THRIFT_READ _read +# define THRIFT_IOCTL_SOCKET ioctlsocket +# define THRIFT_IOCTL_SOCKET_NUM_BYTES_TYPE u_long +# define THRIFT_FSTAT _fstat +# define THRIFT_STAT _stat +# ifdef _WIN32_WCE +# define THRIFT_GAI_STRERROR(...) thrift_wstr2str(gai_strerrorW(__VA_ARGS__)) +# else +# define THRIFT_GAI_STRERROR gai_strerrorA +# endif +# define THRIFT_SSIZET ptrdiff_t +# if (_MSC_VER < 1900) +# define THRIFT_SNPRINTF _snprintf +# else +# define THRIFT_SNPRINTF snprintf +# endif +# define THRIFT_SLEEP_SEC thrift_sleep +# define THRIFT_SLEEP_USEC thrift_usleep +# define THRIFT_TIMESPEC thrift_timespec +# define THRIFT_CTIME_R thrift_ctime_r +# define THRIFT_POLL thrift_poll +# if WINVER <= 0x0502 //XP, Server2003 +# define THRIFT_POLLFD thrift_pollfd +# define THRIFT_POLLIN 0x0300 +# define THRIFT_POLLOUT 0x0010 +# else //Vista, Win7... +# define THRIFT_POLLFD pollfd +# define THRIFT_POLLIN POLLIN +# define THRIFT_POLLOUT POLLOUT +# endif //WINVER +# define THRIFT_SHUT_RDWR SD_BOTH +# if !defined(AI_ADDRCONFIG) +# define AI_ADDRCONFIG 0x00000400 +# endif +#else //not _WIN32 +# include +# define THRIFT_GET_SOCKET_ERROR errno +# define THRIFT_ERRNO errno +# define THRIFT_EINTR EINTR +# define THRIFT_EINPROGRESS EINPROGRESS +# define THRIFT_ECONNRESET ECONNRESET +# define THRIFT_ENOTCONN ENOTCONN +# define THRIFT_ETIMEDOUT ETIMEDOUT +# define THRIFT_EWOULDBLOCK EWOULDBLOCK +# define THRIFT_EAGAIN EAGAIN +# define THRIFT_EPIPE EPIPE +# define THRIFT_NO_SOCKET_CACHING SO_REUSEADDR +# define THRIFT_SOCKET int +# define THRIFT_INVALID_SOCKET (-1) +# define THRIFT_SOCKETPAIR socketpair +# define THRIFT_FCNTL fcntl +# define THRIFT_O_NONBLOCK O_NONBLOCK +# define THRIFT_F_GETFL F_GETFL +# define THRIFT_F_SETFL F_SETFL +# define THRIFT_GETTIMEOFDAY gettimeofday +# define THRIFT_CLOSESOCKET close +# define THRIFT_CLOSE close +# define THRIFT_OPEN open +# define THRIFT_FTRUNCATE ftruncate +# define THRIFT_FSYNC fsync +# define THRIFT_LSEEK lseek +# define THRIFT_WRITE write +# define THRIFT_READ read +# define THRIFT_IOCTL_SOCKET ioctl +# define THRIFT_IOCTL_SOCKET_NUM_BYTES_TYPE int +# define THRIFT_STAT stat +# define THRIFT_FSTAT fstat +# define THRIFT_GAI_STRERROR gai_strerror +# define THRIFT_SSIZET ssize_t +# define THRIFT_SNPRINTF snprintf +# define THRIFT_SLEEP_SEC sleep +# define THRIFT_SLEEP_USEC usleep +# define THRIFT_TIMESPEC timespec +# define THRIFT_CTIME_R ctime_r +# define THRIFT_POLL poll +# define THRIFT_POLLFD pollfd +# define THRIFT_POLLIN POLLIN +# define THRIFT_POLLOUT POLLOUT +# define THRIFT_SHUT_RDWR SHUT_RDWR +#endif + +#endif // _THRIFT_TRANSPORT_PLATFORM_SOCKET_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TBufferTransports.cpp b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TBufferTransports.cpp new file mode 100644 index 000000000..4bb8713de --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TBufferTransports.cpp @@ -0,0 +1,415 @@ +/* + * 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. + */ + +#include +#include + +#include + +using std::string; + +namespace apache { +namespace thrift { +namespace transport { + +uint32_t TBufferedTransport::readSlow(uint8_t* buf, uint32_t len) { + auto have = static_cast(rBound_ - rBase_); + + // We should only take the slow path if we can't satisfy the read + // with the data already in the buffer. + assert(have < len); + + // If we have some data in the buffer, copy it out and return it. + // We have to return it without attempting to read more, since we aren't + // guaranteed that the underlying transport actually has more data, so + // attempting to read from it could block. + if (have > 0) { + memcpy(buf, rBase_, have); + setReadBuffer(rBuf_.get(), 0); + return have; + } + + // No data is available in our buffer. + // Get more from underlying transport up to buffer size. + // Note that this makes a lot of sense if len < rBufSize_ + // and almost no sense otherwise. TODO(dreiss): Fix that + // case (possibly including some readv hotness). + setReadBuffer(rBuf_.get(), transport_->read(rBuf_.get(), rBufSize_)); + + // Hand over whatever we have. + uint32_t give = (std::min)(len, static_cast(rBound_ - rBase_)); + memcpy(buf, rBase_, give); + rBase_ += give; + + return give; +} + +void TBufferedTransport::writeSlow(const uint8_t* buf, uint32_t len) { + auto have_bytes = static_cast(wBase_ - wBuf_.get()); + auto space = static_cast(wBound_ - wBase_); + // We should only take the slow path if we can't accommodate the write + // with the free space already in the buffer. + assert(wBound_ - wBase_ < static_cast(len)); + + // Now here's the tricky question: should we copy data from buf into our + // internal buffer and write it from there, or should we just write out + // the current internal buffer in one syscall and write out buf in another. + // If our currently buffered data plus buf is at least double our buffer + // size, we will have to do two syscalls no matter what (except in the + // degenerate case when our buffer is empty), so there is no use copying. + // Otherwise, there is sort of a sliding scale. If we have N-1 bytes + // buffered and need to write 2, it would be crazy to do two syscalls. + // On the other hand, if we have 2 bytes buffered and are writing 2N-3, + // we can save a syscall in the short term by loading up our buffer, writing + // it out, and copying the rest of the bytes into our buffer. Of course, + // if we get another 2-byte write, we haven't saved any syscalls at all, + // and have just copied nearly 2N bytes for nothing. Finding a perfect + // policy would require predicting the size of future writes, so we're just + // going to always eschew syscalls if we have less than 2N bytes to write. + + // The case where we have to do two syscalls. + // This case also covers the case where the buffer is empty, + // but it is clearer (I think) to think of it as two separate cases. + if ((have_bytes + len >= 2 * wBufSize_) || (have_bytes == 0)) { + // TODO(dreiss): writev + if (have_bytes > 0) { + transport_->write(wBuf_.get(), have_bytes); + } + transport_->write(buf, len); + wBase_ = wBuf_.get(); + return; + } + + // Fill up our internal buffer for a write. + memcpy(wBase_, buf, space); + buf += space; + len -= space; + transport_->write(wBuf_.get(), wBufSize_); + + // Copy the rest into our buffer. + assert(len < wBufSize_); + memcpy(wBuf_.get(), buf, len); + wBase_ = wBuf_.get() + len; + return; +} + +const uint8_t* TBufferedTransport::borrowSlow(uint8_t* buf, uint32_t* len) { + (void)buf; + (void)len; + // Simply return NULL. We don't know if there is actually data available on + // the underlying transport, so calling read() might block. + return nullptr; +} + +void TBufferedTransport::flush() { + // Write out any data waiting in the write buffer. + auto have_bytes = static_cast(wBase_ - wBuf_.get()); + if (have_bytes > 0) { + // Note that we reset wBase_ prior to the underlying write + // to ensure we're in a sane state (i.e. internal buffer cleaned) + // if the underlying write throws up an exception + wBase_ = wBuf_.get(); + transport_->write(wBuf_.get(), have_bytes); + } + + // Flush the underlying transport. + transport_->flush(); +} + +uint32_t TFramedTransport::readSlow(uint8_t* buf, uint32_t len) { + uint32_t want = len; + auto have = static_cast(rBound_ - rBase_); + + // We should only take the slow path if we can't satisfy the read + // with the data already in the buffer. + assert(have < want); + + // If we have some data in the buffer, copy it out and return it. + // We have to return it without attempting to read more, since we aren't + // guaranteed that the underlying transport actually has more data, so + // attempting to read from it could block. + if (have > 0) { + memcpy(buf, rBase_, have); + setReadBuffer(rBuf_.get(), 0); + return have; + } + + // Read another frame. + if (!readFrame()) { + // EOF. No frame available. + return 0; + } + + // TODO(dreiss): Should we warn when reads cross frames? + + // Hand over whatever we have. + uint32_t give = (std::min)(want, static_cast(rBound_ - rBase_)); + memcpy(buf, rBase_, give); + rBase_ += give; + want -= give; + + return (len - want); +} + +bool TFramedTransport::readFrame() { + // TODO(dreiss): Think about using readv here, even though it would + // result in (gasp) read-ahead. + + // Read the size of the next frame. + // We can't use readAll(&sz, sizeof(sz)), since that always throws an + // exception on EOF. We want to throw an exception only if EOF occurs after + // partial size data. + int32_t sz = -1; + uint32_t size_bytes_read = 0; + while (size_bytes_read < sizeof(sz)) { + uint8_t* szp = reinterpret_cast(&sz) + size_bytes_read; + uint32_t bytes_read + = transport_->read(szp, static_cast(sizeof(sz)) - size_bytes_read); + if (bytes_read == 0) { + if (size_bytes_read == 0) { + // EOF before any data was read. + return false; + } else { + // EOF after a partial frame header. Raise an exception. + throw TTransportException(TTransportException::END_OF_FILE, + "No more data to read after " + "partial frame header."); + } + } + size_bytes_read += bytes_read; + } + + sz = ntohl(sz); + + if (sz < 0) { + throw TTransportException("Frame size has negative value"); + } + + // Check for oversized frame + if (sz > static_cast(maxFrameSize_)) + throw TTransportException(TTransportException::CORRUPTED_DATA, "Received an oversized frame"); + + // Read the frame payload, and reset markers. + if (sz > static_cast(rBufSize_)) { + rBuf_.reset(new uint8_t[sz]); + rBufSize_ = sz; + } + transport_->readAll(rBuf_.get(), sz); + setReadBuffer(rBuf_.get(), sz); + return true; +} + +void TFramedTransport::writeSlow(const uint8_t* buf, uint32_t len) { + // Double buffer size until sufficient. + auto have = static_cast(wBase_ - wBuf_.get()); + uint32_t new_size = wBufSize_; + if (len + have < have /* overflow */ || len + have > 0x7fffffff) { + throw TTransportException(TTransportException::BAD_ARGS, + "Attempted to write over 2 GB to TFramedTransport."); + } + while (new_size < len + have) { + new_size = new_size > 0 ? new_size * 2 : 1; + } + + // TODO(dreiss): Consider modifying this class to use malloc/free + // so we can use realloc here. + + // Allocate new buffer. + auto* new_buf = new uint8_t[new_size]; + + // Copy the old buffer to the new one. + memcpy(new_buf, wBuf_.get(), have); + + // Now point buf to the new one. + wBuf_.reset(new_buf); + wBufSize_ = new_size; + wBase_ = wBuf_.get() + have; + wBound_ = wBuf_.get() + wBufSize_; + + // Copy the data into the new buffer. + memcpy(wBase_, buf, len); + wBase_ += len; +} + +void TFramedTransport::flush() { + int32_t sz_hbo, sz_nbo; + assert(wBufSize_ > sizeof(sz_nbo)); + + // Slip the frame size into the start of the buffer. + sz_hbo = static_cast(wBase_ - (wBuf_.get() + sizeof(sz_nbo))); + sz_nbo = (int32_t)htonl((uint32_t)(sz_hbo)); + memcpy(wBuf_.get(), (uint8_t*)&sz_nbo, sizeof(sz_nbo)); + + if (sz_hbo > 0) { + // Note that we reset wBase_ (with a pad for the frame size) + // prior to the underlying write to ensure we're in a sane state + // (i.e. internal buffer cleaned) if the underlying write throws + // up an exception + wBase_ = wBuf_.get() + sizeof(sz_nbo); + + // Write size and frame body. + transport_->write(wBuf_.get(), static_cast(sizeof(sz_nbo)) + sz_hbo); + } + + // Flush the underlying transport. + transport_->flush(); + + // reclaim write buffer + if (wBufSize_ > bufReclaimThresh_) { + wBufSize_ = DEFAULT_BUFFER_SIZE; + wBuf_.reset(new uint8_t[wBufSize_]); + setWriteBuffer(wBuf_.get(), wBufSize_); + + // reset wBase_ with a pad for the frame size + int32_t pad = 0; + wBase_ = wBuf_.get() + sizeof(pad); + } +} + +uint32_t TFramedTransport::writeEnd() { + return static_cast(wBase_ - wBuf_.get()); +} + +const uint8_t* TFramedTransport::borrowSlow(uint8_t* buf, uint32_t* len) { + (void)buf; + (void)len; + // Don't try to be clever with shifting buffers. + // If the fast path failed let the protocol use its slow path. + // Besides, who is going to try to borrow across messages? + return nullptr; +} + +uint32_t TFramedTransport::readEnd() { + // include framing bytes + auto bytes_read = static_cast(rBound_ - rBuf_.get() + sizeof(uint32_t)); + + if (rBufSize_ > bufReclaimThresh_) { + rBufSize_ = 0; + rBuf_.reset(); + setReadBuffer(rBuf_.get(), rBufSize_); + } + + return bytes_read; +} + +void TMemoryBuffer::computeRead(uint32_t len, uint8_t** out_start, uint32_t* out_give) { + // Correct rBound_ so we can use the fast path in the future. + rBound_ = wBase_; + + // Decide how much to give. + uint32_t give = (std::min)(len, available_read()); + + *out_start = rBase_; + *out_give = give; + + // Preincrement rBase_ so the caller doesn't have to. + rBase_ += give; +} + +uint32_t TMemoryBuffer::readSlow(uint8_t* buf, uint32_t len) { + uint8_t* start; + uint32_t give; + computeRead(len, &start, &give); + + // Copy into the provided buffer. + memcpy(buf, start, give); + + return give; +} + +uint32_t TMemoryBuffer::readAppendToString(std::string& str, uint32_t len) { + // Don't get some stupid assertion failure. + if (buffer_ == nullptr) { + return 0; + } + + uint8_t* start; + uint32_t give; + computeRead(len, &start, &give); + + // Append to the provided string. + str.append((char*)start, give); + + return give; +} + +void TMemoryBuffer::ensureCanWrite(uint32_t len) { + // Check available space + uint32_t avail = available_write(); + if (len <= avail) { + return; + } + + if (!owner_) { + throw TTransportException("Insufficient space in external MemoryBuffer"); + } + + // Grow the buffer as necessary. + uint64_t new_size = bufferSize_; + while (len > avail) { + new_size = new_size > 0 ? new_size * 2 : 1; + if (new_size > maxBufferSize_) { + throw TTransportException(TTransportException::BAD_ARGS, + "Internal buffer size overflow"); + } + avail = available_write() + (static_cast(new_size) - bufferSize_); + } + + // Allocate into a new pointer so we don't bork ours if it fails. + auto* new_buffer = static_cast(std::realloc(buffer_, new_size)); + if (new_buffer == nullptr) { + throw std::bad_alloc(); + } + + rBase_ = new_buffer + (rBase_ - buffer_); + rBound_ = new_buffer + (rBound_ - buffer_); + wBase_ = new_buffer + (wBase_ - buffer_); + wBound_ = new_buffer + new_size; + buffer_ = new_buffer; + bufferSize_ = static_cast(new_size); +} + +void TMemoryBuffer::writeSlow(const uint8_t* buf, uint32_t len) { + ensureCanWrite(len); + + // Copy into the buffer and increment wBase_. + memcpy(wBase_, buf, len); + wBase_ += len; +} + +void TMemoryBuffer::wroteBytes(uint32_t len) { + uint32_t avail = available_write(); + if (len > avail) { + throw TTransportException("Client wrote more bytes than size of buffer."); + } + wBase_ += len; +} + +const uint8_t* TMemoryBuffer::borrowSlow(uint8_t* buf, uint32_t* len) { + (void)buf; + rBound_ = wBase_; + if (available_read() >= *len) { + *len = available_read(); + return rBase_; + } + return nullptr; +} +} +} +} // apache::thrift::transport diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TBufferTransports.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TBufferTransports.h new file mode 100644 index 000000000..df06586bd --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TBufferTransports.h @@ -0,0 +1,747 @@ +/* + * 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. + */ + +#ifndef _THRIFT_TRANSPORT_TBUFFERTRANSPORTS_H_ +#define _THRIFT_TRANSPORT_TBUFFERTRANSPORTS_H_ 1 + +#include +#include +#include +#include + +#include +#include + +#ifdef __GNUC__ +#define TDB_LIKELY(val) (__builtin_expect((val), 1)) +#define TDB_UNLIKELY(val) (__builtin_expect((val), 0)) +#else +#define TDB_LIKELY(val) (val) +#define TDB_UNLIKELY(val) (val) +#endif + +namespace apache { +namespace thrift { +namespace transport { + +/** + * Base class for all transports that use read/write buffers for performance. + * + * TBufferBase is designed to implement the fast-path "memcpy" style + * operations that work in the common case. It does so with small and + * (eventually) nonvirtual, inlinable methods. TBufferBase is an abstract + * class. Subclasses are expected to define the "slow path" operations + * that have to be done when the buffers are full or empty. + * + */ +class TBufferBase : public TVirtualTransport { + +public: + /** + * Fast-path read. + * + * When we have enough data buffered to fulfill the read, we can satisfy it + * with a single memcpy, then adjust our internal pointers. If the buffer + * is empty, we call out to our slow path, implemented by a subclass. + * This method is meant to eventually be nonvirtual and inlinable. + */ + uint32_t read(uint8_t* buf, uint32_t len) { + uint8_t* new_rBase = rBase_ + len; + if (TDB_LIKELY(new_rBase <= rBound_)) { + std::memcpy(buf, rBase_, len); + rBase_ = new_rBase; + return len; + } + return readSlow(buf, len); + } + + /** + * Shortcutted version of readAll. + */ + uint32_t readAll(uint8_t* buf, uint32_t len) { + uint8_t* new_rBase = rBase_ + len; + if (TDB_LIKELY(new_rBase <= rBound_)) { + std::memcpy(buf, rBase_, len); + rBase_ = new_rBase; + return len; + } + return apache::thrift::transport::readAll(*this, buf, len); + } + + /** + * Fast-path write. + * + * When we have enough empty space in our buffer to accommodate the write, we + * can satisfy it with a single memcpy, then adjust our internal pointers. + * If the buffer is full, we call out to our slow path, implemented by a + * subclass. This method is meant to eventually be nonvirtual and + * inlinable. + */ + void write(const uint8_t* buf, uint32_t len) { + uint8_t* new_wBase = wBase_ + len; + if (TDB_LIKELY(new_wBase <= wBound_)) { + std::memcpy(wBase_, buf, len); + wBase_ = new_wBase; + return; + } + writeSlow(buf, len); + } + + /** + * Fast-path borrow. A lot like the fast-path read. + */ + const uint8_t* borrow(uint8_t* buf, uint32_t* len) { + if (TDB_LIKELY(static_cast(*len) <= rBound_ - rBase_)) { + // With strict aliasing, writing to len shouldn't force us to + // refetch rBase_ from memory. TODO(dreiss): Verify this. + *len = static_cast(rBound_ - rBase_); + return rBase_; + } + return borrowSlow(buf, len); + } + + /** + * Consume doesn't require a slow path. + */ + void consume(uint32_t len) { + if (TDB_LIKELY(static_cast(len) <= rBound_ - rBase_)) { + rBase_ += len; + } else { + throw TTransportException(TTransportException::BAD_ARGS, "consume did not follow a borrow."); + } + } + +protected: + /// Slow path read. + virtual uint32_t readSlow(uint8_t* buf, uint32_t len) = 0; + + /// Slow path write. + virtual void writeSlow(const uint8_t* buf, uint32_t len) = 0; + + /** + * Slow path borrow. + * + * POSTCONDITION: return == NULL || rBound_ - rBase_ >= *len + */ + virtual const uint8_t* borrowSlow(uint8_t* buf, uint32_t* len) = 0; + + /** + * Trivial constructor. + * + * Initialize pointers safely. Constructing is not a very + * performance-sensitive operation, so it is okay to just leave it to + * the concrete class to set up pointers correctly. + */ + TBufferBase() : rBase_(nullptr), rBound_(nullptr), wBase_(nullptr), wBound_(nullptr) {} + + /// Convenience mutator for setting the read buffer. + void setReadBuffer(uint8_t* buf, uint32_t len) { + rBase_ = buf; + rBound_ = buf + len; + } + + /// Convenience mutator for setting the write buffer. + void setWriteBuffer(uint8_t* buf, uint32_t len) { + wBase_ = buf; + wBound_ = buf + len; + } + + ~TBufferBase() override = default; + + /// Reads begin here. + uint8_t* rBase_; + /// Reads may extend to just before here. + uint8_t* rBound_; + + /// Writes begin here. + uint8_t* wBase_; + /// Writes may extend to just before here. + uint8_t* wBound_; +}; + +/** + * Buffered transport. For reads it will read more data than is requested + * and will serve future data out of a local buffer. For writes, data is + * stored to an in memory buffer before being written out. + * + */ +class TBufferedTransport : public TVirtualTransport { +public: + static const int DEFAULT_BUFFER_SIZE = 512; + + /// Use default buffer sizes. + TBufferedTransport(std::shared_ptr transport) + : transport_(transport), + rBufSize_(DEFAULT_BUFFER_SIZE), + wBufSize_(DEFAULT_BUFFER_SIZE), + rBuf_(new uint8_t[rBufSize_]), + wBuf_(new uint8_t[wBufSize_]) { + initPointers(); + } + + /// Use specified buffer sizes. + TBufferedTransport(std::shared_ptr transport, uint32_t sz) + : transport_(transport), + rBufSize_(sz), + wBufSize_(sz), + rBuf_(new uint8_t[rBufSize_]), + wBuf_(new uint8_t[wBufSize_]) { + initPointers(); + } + + /// Use specified read and write buffer sizes. + TBufferedTransport(std::shared_ptr transport, uint32_t rsz, uint32_t wsz) + : transport_(transport), + rBufSize_(rsz), + wBufSize_(wsz), + rBuf_(new uint8_t[rBufSize_]), + wBuf_(new uint8_t[wBufSize_]) { + initPointers(); + } + + void open() override { transport_->open(); } + + bool isOpen() const override { return transport_->isOpen(); } + + bool peek() override { + if (rBase_ == rBound_) { + setReadBuffer(rBuf_.get(), transport_->read(rBuf_.get(), rBufSize_)); + } + return (rBound_ > rBase_); + } + + void close() override { + flush(); + transport_->close(); + } + + uint32_t readSlow(uint8_t* buf, uint32_t len) override; + + void writeSlow(const uint8_t* buf, uint32_t len) override; + + void flush() override; + + /** + * Returns the origin of the underlying transport + */ + const std::string getOrigin() const override { return transport_->getOrigin(); } + + /** + * The following behavior is currently implemented by TBufferedTransport, + * but that may change in a future version: + * 1/ If len is at most rBufSize_, borrow will never return NULL. + * Depending on the underlying transport, it could throw an exception + * or hang forever. + * 2/ Some borrow requests may copy bytes internally. However, + * if len is at most rBufSize_/2, none of the copied bytes + * will ever have to be copied again. For optimial performance, + * stay under this limit. + */ + const uint8_t* borrowSlow(uint8_t* buf, uint32_t* len) override; + + std::shared_ptr getUnderlyingTransport() { return transport_; } + + /* + * TVirtualTransport provides a default implementation of readAll(). + * We want to use the TBufferBase version instead. + */ + uint32_t readAll(uint8_t* buf, uint32_t len) { return TBufferBase::readAll(buf, len); } + +protected: + void initPointers() { + setReadBuffer(rBuf_.get(), 0); + setWriteBuffer(wBuf_.get(), wBufSize_); + // Write size never changes. + } + + std::shared_ptr transport_; + + uint32_t rBufSize_; + uint32_t wBufSize_; + boost::scoped_array rBuf_; + boost::scoped_array wBuf_; +}; + +/** + * Wraps a transport into a buffered one. + * + */ +class TBufferedTransportFactory : public TTransportFactory { +public: + TBufferedTransportFactory() = default; + + ~TBufferedTransportFactory() override = default; + + /** + * Wraps the transport into a buffered one. + */ + std::shared_ptr getTransport(std::shared_ptr trans) override { + return std::shared_ptr(new TBufferedTransport(trans)); + } +}; + +/** + * Framed transport. All writes go into an in-memory buffer until flush is + * called, at which point the transport writes the length of the entire + * binary chunk followed by the data payload. This allows the receiver on the + * other end to always do fixed-length reads. + * + */ +class TFramedTransport : public TVirtualTransport { +public: + static const int DEFAULT_BUFFER_SIZE = 512; + static const int DEFAULT_MAX_FRAME_SIZE = 256 * 1024 * 1024; + + /// Use default buffer sizes. + TFramedTransport() + : transport_(), + rBufSize_(0), + wBufSize_(DEFAULT_BUFFER_SIZE), + rBuf_(), + wBuf_(new uint8_t[wBufSize_]), + bufReclaimThresh_((std::numeric_limits::max)()) { + initPointers(); + } + + TFramedTransport(std::shared_ptr transport) + : transport_(transport), + rBufSize_(0), + wBufSize_(DEFAULT_BUFFER_SIZE), + rBuf_(), + wBuf_(new uint8_t[wBufSize_]), + bufReclaimThresh_((std::numeric_limits::max)()), + maxFrameSize_(DEFAULT_MAX_FRAME_SIZE) { + initPointers(); + } + + TFramedTransport(std::shared_ptr transport, + uint32_t sz, + uint32_t bufReclaimThresh = (std::numeric_limits::max)()) + : transport_(transport), + rBufSize_(0), + wBufSize_(sz), + rBuf_(), + wBuf_(new uint8_t[wBufSize_]), + bufReclaimThresh_(bufReclaimThresh), + maxFrameSize_(DEFAULT_MAX_FRAME_SIZE) { + initPointers(); + } + + void open() override { transport_->open(); } + + bool isOpen() const override { return transport_->isOpen(); } + + bool peek() override { return (rBase_ < rBound_) || transport_->peek(); } + + void close() override { + flush(); + transport_->close(); + } + + uint32_t readSlow(uint8_t* buf, uint32_t len) override; + + void writeSlow(const uint8_t* buf, uint32_t len) override; + + void flush() override; + + uint32_t readEnd() override; + + uint32_t writeEnd() override; + + const uint8_t* borrowSlow(uint8_t* buf, uint32_t* len) override; + + std::shared_ptr getUnderlyingTransport() { return transport_; } + + /* + * TVirtualTransport provides a default implementation of readAll(). + * We want to use the TBufferBase version instead. + */ + using TBufferBase::readAll; + + /** + * Returns the origin of the underlying transport + */ + const std::string getOrigin() const override { return transport_->getOrigin(); } + + /** + * Set the maximum size of the frame at read + */ + void setMaxFrameSize(uint32_t maxFrameSize) { maxFrameSize_ = maxFrameSize; } + + /** + * Get the maximum size of the frame at read + */ + uint32_t getMaxFrameSize() { return maxFrameSize_; } + +protected: + /** + * Reads a frame of input from the underlying stream. + * + * Returns true if a frame was read successfully, or false on EOF. + * (Raises a TTransportException if EOF occurs after a partial frame.) + */ + virtual bool readFrame(); + + void initPointers() { + setReadBuffer(nullptr, 0); + setWriteBuffer(wBuf_.get(), wBufSize_); + + // Pad the buffer so we can insert the size later. + int32_t pad = 0; + this->write((uint8_t*)&pad, sizeof(pad)); + } + + std::shared_ptr transport_; + + uint32_t rBufSize_; + uint32_t wBufSize_; + boost::scoped_array rBuf_; + boost::scoped_array wBuf_; + uint32_t bufReclaimThresh_; + uint32_t maxFrameSize_; +}; + +/** + * Wraps a transport into a framed one. + * + */ +class TFramedTransportFactory : public TTransportFactory { +public: + TFramedTransportFactory() = default; + + ~TFramedTransportFactory() override = default; + + /** + * Wraps the transport into a framed one. + */ + std::shared_ptr getTransport(std::shared_ptr trans) override { + return std::shared_ptr(new TFramedTransport(trans)); + } +}; + +/** + * A memory buffer is a tranpsort that simply reads from and writes to an + * in memory buffer. Anytime you call write on it, the data is simply placed + * into a buffer, and anytime you call read, data is read from that buffer. + * + * The buffers are allocated using C constructs malloc,realloc, and the size + * doubles as necessary. We've considered using scoped + * + */ +class TMemoryBuffer : public TVirtualTransport { +private: + // Common initialization done by all constructors. + void initCommon(uint8_t* buf, uint32_t size, bool owner, uint32_t wPos) { + + maxBufferSize_ = (std::numeric_limits::max)(); + + if (buf == nullptr && size != 0) { + assert(owner); + buf = (uint8_t*)std::malloc(size); + if (buf == nullptr) { + throw std::bad_alloc(); + } + } + + buffer_ = buf; + bufferSize_ = size; + + rBase_ = buffer_; + rBound_ = buffer_ + wPos; + // TODO(dreiss): Investigate NULL-ing this if !owner. + wBase_ = buffer_ + wPos; + wBound_ = buffer_ + bufferSize_; + + owner_ = owner; + + // rBound_ is really an artifact. In principle, it should always be + // equal to wBase_. We update it in a few places (computeRead, etc.). + } + +public: + static const uint32_t defaultSize = 1024; + + /** + * This enum specifies how a TMemoryBuffer should treat + * memory passed to it via constructors or resetBuffer. + * + * OBSERVE: + * TMemoryBuffer will simply store a pointer to the memory. + * It is the callers responsibility to ensure that the pointer + * remains valid for the lifetime of the TMemoryBuffer, + * and that it is properly cleaned up. + * Note that no data can be written to observed buffers. + * + * COPY: + * TMemoryBuffer will make an internal copy of the buffer. + * The caller has no responsibilities. + * + * TAKE_OWNERSHIP: + * TMemoryBuffer will become the "owner" of the buffer, + * and will be responsible for freeing it. + * The membory must have been allocated with malloc. + */ + enum MemoryPolicy { OBSERVE = 1, COPY = 2, TAKE_OWNERSHIP = 3 }; + + /** + * Construct a TMemoryBuffer with a default-sized buffer, + * owned by the TMemoryBuffer object. + */ + TMemoryBuffer() { initCommon(nullptr, defaultSize, true, 0); } + + /** + * Construct a TMemoryBuffer with a buffer of a specified size, + * owned by the TMemoryBuffer object. + * + * @param sz The initial size of the buffer. + */ + TMemoryBuffer(uint32_t sz) { initCommon(nullptr, sz, true, 0); } + + /** + * Construct a TMemoryBuffer with buf as its initial contents. + * + * @param buf The initial contents of the buffer. + * Note that, while buf is a non-const pointer, + * TMemoryBuffer will not write to it if policy == OBSERVE, + * so it is safe to const_cast(whatever). + * @param sz The size of @c buf. + * @param policy See @link MemoryPolicy @endlink . + */ + TMemoryBuffer(uint8_t* buf, uint32_t sz, MemoryPolicy policy = OBSERVE) { + if (buf == nullptr && sz != 0) { + throw TTransportException(TTransportException::BAD_ARGS, + "TMemoryBuffer given null buffer with non-zero size."); + } + + switch (policy) { + case OBSERVE: + case TAKE_OWNERSHIP: + initCommon(buf, sz, policy == TAKE_OWNERSHIP, sz); + break; + case COPY: + initCommon(nullptr, sz, true, 0); + this->write(buf, sz); + break; + default: + throw TTransportException(TTransportException::BAD_ARGS, + "Invalid MemoryPolicy for TMemoryBuffer"); + } + } + + ~TMemoryBuffer() override { + if (owner_) { + std::free(buffer_); + } + } + + bool isOpen() const override { return true; } + + bool peek() override { return (rBase_ < wBase_); } + + void open() override {} + + void close() override {} + + // TODO(dreiss): Make bufPtr const. + void getBuffer(uint8_t** bufPtr, uint32_t* sz) { + *bufPtr = rBase_; + *sz = static_cast(wBase_ - rBase_); + } + + std::string getBufferAsString() { + if (buffer_ == nullptr) { + return ""; + } + uint8_t* buf; + uint32_t sz; + getBuffer(&buf, &sz); + return std::string((char*)buf, (std::string::size_type)sz); + } + + void appendBufferToString(std::string& str) { + if (buffer_ == nullptr) { + return; + } + uint8_t* buf; + uint32_t sz; + getBuffer(&buf, &sz); + str.append((char*)buf, sz); + } + + void resetBuffer() { + rBase_ = buffer_; + rBound_ = buffer_; + wBase_ = buffer_; + // It isn't safe to write into a buffer we don't own. + if (!owner_) { + wBound_ = wBase_; + bufferSize_ = 0; + } + } + + /// See constructor documentation. + void resetBuffer(uint8_t* buf, uint32_t sz, MemoryPolicy policy = OBSERVE) { + // Use a variant of the copy-and-swap trick for assignment operators. + // This is sub-optimal in terms of performance for two reasons: + // 1/ The constructing and swapping of the (small) values + // in the temporary object takes some time, and is not necessary. + // 2/ If policy == COPY, we allocate the new buffer before + // freeing the old one, precluding the possibility of + // reusing that memory. + // I doubt that either of these problems could be optimized away, + // but the second is probably no a common case, and the first is minor. + // I don't expect resetBuffer to be a common operation, so I'm willing to + // bite the performance bullet to make the method this simple. + + // Construct the new buffer. + TMemoryBuffer new_buffer(buf, sz, policy); + // Move it into ourself. + this->swap(new_buffer); + // Our old self gets destroyed. + } + + /// See constructor documentation. + void resetBuffer(uint32_t sz) { + // Construct the new buffer. + TMemoryBuffer new_buffer(sz); + // Move it into ourself. + this->swap(new_buffer); + // Our old self gets destroyed. + } + + std::string readAsString(uint32_t len) { + std::string str; + (void)readAppendToString(str, len); + return str; + } + + uint32_t readAppendToString(std::string& str, uint32_t len); + + // return number of bytes read + uint32_t readEnd() override { + // This cast should be safe, because buffer_'s size is a uint32_t + auto bytes = static_cast(rBase_ - buffer_); + if (rBase_ == wBase_) { + resetBuffer(); + } + return bytes; + } + + // Return number of bytes written + uint32_t writeEnd() override { + // This cast should be safe, because buffer_'s size is a uint32_t + return static_cast(wBase_ - buffer_); + } + + uint32_t available_read() const { + // Remember, wBase_ is the real rBound_. + return static_cast(wBase_ - rBase_); + } + + uint32_t available_write() const { return static_cast(wBound_ - wBase_); } + + // Returns a pointer to where the client can write data to append to + // the TMemoryBuffer, and ensures the buffer is big enough to accommodate a + // write of the provided length. The returned pointer is very convenient for + // passing to read(), recv(), or similar. You must call wroteBytes() as soon + // as data is written or the buffer will not be aware that data has changed. + uint8_t* getWritePtr(uint32_t len) { + ensureCanWrite(len); + return wBase_; + } + + // Informs the buffer that the client has written 'len' bytes into storage + // that had been provided by getWritePtr(). + void wroteBytes(uint32_t len); + + /* + * TVirtualTransport provides a default implementation of readAll(). + * We want to use the TBufferBase version instead. + */ + uint32_t readAll(uint8_t* buf, uint32_t len) { return TBufferBase::readAll(buf, len); } + + //! \brief Get the current buffer size + //! \returns the current buffer size + uint32_t getBufferSize() const { + return bufferSize_; + } + + //! \brief Get the current maximum buffer size + //! \returns the current maximum buffer size + uint32_t getMaxBufferSize() const { + return maxBufferSize_; + } + + //! \brief Change the maximum buffer size + //! \param[in] maxSize the new maximum buffer size allowed to grow to + //! \throws TTransportException(BAD_ARGS) if maxSize is less than the current buffer size + void setMaxBufferSize(uint32_t maxSize) { + if (maxSize < bufferSize_) { + throw TTransportException(TTransportException::BAD_ARGS, + "Maximum buffer size would be less than current buffer size"); + } + maxBufferSize_ = maxSize; + } + +protected: + void swap(TMemoryBuffer& that) { + using std::swap; + swap(buffer_, that.buffer_); + swap(bufferSize_, that.bufferSize_); + + swap(rBase_, that.rBase_); + swap(rBound_, that.rBound_); + swap(wBase_, that.wBase_); + swap(wBound_, that.wBound_); + + swap(owner_, that.owner_); + } + + // Make sure there's at least 'len' bytes available for writing. + void ensureCanWrite(uint32_t len); + + // Compute the position and available data for reading. + void computeRead(uint32_t len, uint8_t** out_start, uint32_t* out_give); + + uint32_t readSlow(uint8_t* buf, uint32_t len) override; + + void writeSlow(const uint8_t* buf, uint32_t len) override; + + const uint8_t* borrowSlow(uint8_t* buf, uint32_t* len) override; + + // Data buffer + uint8_t* buffer_; + + // Allocated buffer size + uint32_t bufferSize_; + + // Maximum allowed size + uint32_t maxBufferSize_; + + // Is this object the owner of the buffer? + bool owner_; + + // Don't forget to update constrctors, initCommon, and swap if + // you add new members. +}; +} +} +} // apache::thrift::transport + +#endif // #ifndef _THRIFT_TRANSPORT_TBUFFERTRANSPORTS_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TFDTransport.cpp b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TFDTransport.cpp new file mode 100644 index 000000000..93dd10021 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TFDTransport.cpp @@ -0,0 +1,93 @@ +/* + * 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. + */ + +#include +#include + +#include +#include + +#ifdef HAVE_UNISTD_H +#include +#endif + +#ifdef _WIN32 +#include +#endif + +using std::string; + +namespace apache { +namespace thrift { +namespace transport { + +void TFDTransport::close() { + if (!isOpen()) { + return; + } + + int rv = ::THRIFT_CLOSE(fd_); + int errno_copy = THRIFT_ERRNO; + fd_ = -1; + // Have to check uncaught_exception because this is called in the destructor. + if (rv < 0 && !std::uncaught_exception()) { + throw TTransportException(TTransportException::UNKNOWN, "TFDTransport::close()", errno_copy); + } +} + +uint32_t TFDTransport::read(uint8_t* buf, uint32_t len) { + unsigned int maxRetries = 5; // same as the TSocket default + unsigned int retries = 0; + while (true) { + THRIFT_SSIZET rv = ::THRIFT_READ(fd_, buf, len); + if (rv < 0) { + if (THRIFT_ERRNO == THRIFT_EINTR && retries < maxRetries) { + // If interrupted, try again + ++retries; + continue; + } + int errno_copy = THRIFT_ERRNO; + throw TTransportException(TTransportException::UNKNOWN, "TFDTransport::read()", errno_copy); + } + // this should be fine, since we already checked for negative values, + // and ::read should only return a 32-bit value since len is 32-bit. + return static_cast(rv); + } +} + +void TFDTransport::write(const uint8_t* buf, uint32_t len) { + while (len > 0) { + THRIFT_SSIZET rv = ::THRIFT_WRITE(fd_, buf, len); + + if (rv < 0) { + int errno_copy = THRIFT_ERRNO; + throw TTransportException(TTransportException::UNKNOWN, "TFDTransport::write()", errno_copy); + } else if (rv == 0) { + throw TTransportException(TTransportException::END_OF_FILE, "TFDTransport::write()"); + } + + buf += rv; + // this should be fine, as we've already checked for negative values, and + //::write shouldn't return more than a uint32_t since len is a uint32_t + len -= static_cast(rv); + } +} +} +} +} // apache::thrift::transport diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TFDTransport.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TFDTransport.h new file mode 100644 index 000000000..a3cf51948 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TFDTransport.h @@ -0,0 +1,77 @@ +/* + * 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. + */ + +#ifndef _THRIFT_TRANSPORT_TFDTRANSPORT_H_ +#define _THRIFT_TRANSPORT_TFDTRANSPORT_H_ 1 + +#include +#ifdef HAVE_SYS_TIME_H +#include +#endif + +#include +#include + +namespace apache { +namespace thrift { +namespace transport { + +/** + * Dead-simple wrapper around a file descriptor. + * + */ +class TFDTransport : public TVirtualTransport { +public: + enum ClosePolicy { NO_CLOSE_ON_DESTROY = 0, CLOSE_ON_DESTROY = 1 }; + + TFDTransport(int fd, ClosePolicy close_policy = NO_CLOSE_ON_DESTROY) + : fd_(fd), close_policy_(close_policy) {} + + ~TFDTransport() override { + if (close_policy_ == CLOSE_ON_DESTROY) { + try { + close(); + } catch (TTransportException& ex) { + GlobalOutput.printf("~TFDTransport TTransportException: '%s'", ex.what()); + } + } + } + + bool isOpen() const override { return fd_ >= 0; } + + void open() override {} + + void close() override; + + uint32_t read(uint8_t* buf, uint32_t len); + + void write(const uint8_t* buf, uint32_t len); + + void setFD(int fd) { fd_ = fd; } + int getFD() { return fd_; } + +protected: + int fd_; + ClosePolicy close_policy_; +}; +} +} +} // apache::thrift::transport + +#endif // #ifndef _THRIFT_TRANSPORT_TFDTRANSPORT_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TFileTransport.cpp b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TFileTransport.cpp new file mode 100644 index 000000000..eaf2bc365 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TFileTransport.cpp @@ -0,0 +1,1068 @@ +/* + * 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. + */ + +#include + +#include +#include +#include +#include + +#include + +#ifdef HAVE_SYS_TIME_H +#include +#else +#include +#endif +#include +#ifdef HAVE_UNISTD_H +#include +#endif +#ifdef HAVE_STRINGS_H +#include +#endif +#include +#include +#include +#include +#include +#ifdef HAVE_SYS_STAT_H +#include +#endif + +#ifdef _WIN32 +#include +#endif + +namespace apache { +namespace thrift { +namespace transport { + +using std::shared_ptr; +using std::cerr; +using std::cout; +using std::endl; +using std::string; +using namespace apache::thrift::protocol; +using namespace apache::thrift::concurrency; + +TFileTransport::TFileTransport(string path, bool readOnly) + : readState_(), + readBuff_(nullptr), + currentEvent_(nullptr), + readBuffSize_(DEFAULT_READ_BUFF_SIZE), + readTimeout_(NO_TAIL_READ_TIMEOUT), + chunkSize_(DEFAULT_CHUNK_SIZE), + eventBufferSize_(DEFAULT_EVENT_BUFFER_SIZE), + flushMaxUs_(DEFAULT_FLUSH_MAX_US), + flushMaxBytes_(DEFAULT_FLUSH_MAX_BYTES), + maxEventSize_(DEFAULT_MAX_EVENT_SIZE), + maxCorruptedEvents_(DEFAULT_MAX_CORRUPTED_EVENTS), + eofSleepTime_(DEFAULT_EOF_SLEEP_TIME_US), + corruptedEventSleepTime_(DEFAULT_CORRUPTED_SLEEP_TIME_US), + writerThreadIOErrorSleepTime_(DEFAULT_WRITER_THREAD_SLEEP_TIME_US), + dequeueBuffer_(nullptr), + enqueueBuffer_(nullptr), + notFull_(&mutex_), + notEmpty_(&mutex_), + closing_(false), + flushed_(&mutex_), + forceFlush_(false), + filename_(path), + fd_(0), + bufferAndThreadInitialized_(false), + offset_(0), + lastBadChunk_(0), + numCorruptedEventsInChunk_(0), + readOnly_(readOnly) { + threadFactory_.setDetached(false); + openLogFile(); +} + +void TFileTransport::resetOutputFile(int fd, string filename, off_t offset) { + filename_ = filename; + offset_ = offset; + + // check if current file is still open + if (fd_ > 0) { + // flush any events in the queue + flush(); + GlobalOutput.printf("error, current file (%s) not closed", filename_.c_str()); + if (-1 == ::THRIFT_CLOSE(fd_)) { + int errno_copy = THRIFT_ERRNO; + GlobalOutput.perror("TFileTransport: resetOutputFile() ::close() ", errno_copy); + throw TTransportException(TTransportException::UNKNOWN, + "TFileTransport: error in file close", + errno_copy); + } else { + // successfully closed fd + fd_ = 0; + } + } + + if (fd) { + fd_ = fd; + } else { + // open file if the input fd is 0 + openLogFile(); + } +} + +TFileTransport::~TFileTransport() { + // flush the buffer if a writer thread is active + if (writerThread_.get()) { + // set state to closing + closing_ = true; + + // wake up the writer thread + // Since closing_ is true, it will attempt to flush all data, then exit. + notEmpty_.notify(); + + writerThread_->join(); + writerThread_.reset(); + } + + if (dequeueBuffer_) { + delete dequeueBuffer_; + dequeueBuffer_ = nullptr; + } + + if (enqueueBuffer_) { + delete enqueueBuffer_; + enqueueBuffer_ = nullptr; + } + + if (readBuff_) { + delete[] readBuff_; + readBuff_ = nullptr; + } + + if (currentEvent_) { + delete currentEvent_; + currentEvent_ = nullptr; + } + + // close logfile + if (fd_ > 0) { + if (-1 == ::THRIFT_CLOSE(fd_)) { + GlobalOutput.perror("TFileTransport: ~TFileTransport() ::close() ", THRIFT_ERRNO); + } else { + // successfully closed fd + fd_ = 0; + } + } +} + +bool TFileTransport::initBufferAndWriteThread() { + if (bufferAndThreadInitialized_) { + T_ERROR("%s", "Trying to double-init TFileTransport"); + return false; + } + + if (!writerThread_.get()) { + writerThread_ = threadFactory_.newThread( + apache::thrift::concurrency::FunctionRunner::create(startWriterThread, this)); + writerThread_->start(); + } + + dequeueBuffer_ = new TFileTransportBuffer(eventBufferSize_); + enqueueBuffer_ = new TFileTransportBuffer(eventBufferSize_); + bufferAndThreadInitialized_ = true; + + return true; +} + +void TFileTransport::write(const uint8_t* buf, uint32_t len) { + if (readOnly_) { + throw TTransportException("TFileTransport: attempting to write to file opened readonly"); + } + + enqueueEvent(buf, len); +} + +template +struct uniqueDeleter +{ + void operator()(_T *ptr) const { delete ptr; } +}; + +void TFileTransport::enqueueEvent(const uint8_t* buf, uint32_t eventLen) { + // can't enqueue more events if file is going to close + if (closing_) { + return; + } + + // make sure that event size is valid + if ((maxEventSize_ > 0) && (eventLen > maxEventSize_)) { + T_ERROR("msg size is greater than max event size: %u > %u\n", eventLen, maxEventSize_); + return; + } + + if (eventLen == 0) { + T_ERROR("%s", "cannot enqueue an empty event"); + return; + } + + std::unique_ptr > toEnqueue(new eventInfo()); + toEnqueue->eventBuff_ = new uint8_t[(sizeof(uint8_t) * eventLen) + 4]; + + // first 4 bytes is the event length + memcpy(toEnqueue->eventBuff_, (void*)(&eventLen), 4); + // actual event contents + memcpy(toEnqueue->eventBuff_ + 4, buf, eventLen); + toEnqueue->eventSize_ = eventLen + 4; + + // lock mutex + Guard g(mutex_); + + // make sure that enqueue buffer is initialized and writer thread is running + if (!bufferAndThreadInitialized_) { + if (!initBufferAndWriteThread()) { + return; + } + } + + // Can't enqueue while buffer is full + while (enqueueBuffer_->isFull()) { + notFull_.wait(); + } + + // We shouldn't be trying to enqueue new data while a forced flush is + // requested. (Otherwise the writer thread might not ever be able to finish + // the flush if more data keeps being enqueued.) + assert(!forceFlush_); + + // add to the buffer + eventInfo* pEvent = toEnqueue.release(); + if (!enqueueBuffer_->addEvent(pEvent)) { + delete pEvent; + return; + } + + // signal anybody who's waiting for the buffer to be non-empty + notEmpty_.notify(); + + // this really should be a loop where it makes sure it got flushed + // because condition variables can get triggered by the os for no reason + // it is probably a non-factor for the time being +} + +bool TFileTransport::swapEventBuffers(const std::chrono::time_point *deadline) { + bool swap; + Guard g(mutex_); + + if (!enqueueBuffer_->isEmpty()) { + swap = true; + } else if (closing_) { + // even though there is no data to write, + // return immediately if the transport is closing + swap = false; + } else { + if (deadline != nullptr) { + // if we were handed a deadline time struct, do a timed wait + notEmpty_.waitForTime(*deadline); + } else { + // just wait until the buffer gets an item + notEmpty_.wait(); + } + + // could be empty if we timed out + swap = enqueueBuffer_->isEmpty(); + } + + if (swap) { + TFileTransportBuffer* temp = enqueueBuffer_; + enqueueBuffer_ = dequeueBuffer_; + dequeueBuffer_ = temp; + } + + if (swap) { + notFull_.notify(); + } + + return swap; +} + +void TFileTransport::writerThread() { + bool hasIOError = false; + + // open file if it is not open + if (!fd_) { + try { + openLogFile(); + } catch (...) { + int errno_copy = THRIFT_ERRNO; + GlobalOutput.perror("TFileTransport: writerThread() openLogFile() ", errno_copy); + fd_ = 0; + hasIOError = true; + } + } + + // set the offset to the correct value (EOF) + if (!hasIOError) { + try { + seekToEnd(); + // throw away any partial events + offset_ += readState_.lastDispatchPtr_; + if (0 == THRIFT_FTRUNCATE(fd_, offset_)) { + readState_.resetAllValues(); + } else { + int errno_copy = THRIFT_ERRNO; + GlobalOutput.perror("TFileTransport: writerThread() truncate ", errno_copy); + hasIOError = true; + } + } catch (...) { + int errno_copy = THRIFT_ERRNO; + GlobalOutput.perror("TFileTransport: writerThread() initialization ", errno_copy); + hasIOError = true; + } + } + + // Figure out the next time by which a flush must take place + auto ts_next_flush = getNextFlushTime(); + uint32_t unflushed = 0; + + while (1) { + // this will only be true when the destructor is being invoked + if (closing_) { + if (hasIOError) { + return; + } + + // Try to empty buffers before exit + if (enqueueBuffer_->isEmpty() && dequeueBuffer_->isEmpty()) { + ::THRIFT_FSYNC(fd_); + if (-1 == ::THRIFT_CLOSE(fd_)) { + int errno_copy = THRIFT_ERRNO; + GlobalOutput.perror("TFileTransport: writerThread() ::close() ", errno_copy); + } else { + // fd successfully closed + fd_ = 0; + } + return; + } + } + + if (swapEventBuffers(&ts_next_flush)) { + eventInfo* outEvent; + while (nullptr != (outEvent = dequeueBuffer_->getNext())) { + // Remove an event from the buffer and write it out to disk. If there is any IO error, for + // instance, + // the output file is unmounted or deleted, then this event is dropped. However, the writer + // thread + // will: (1) sleep for a short while; (2) try to reopen the file; (3) if successful then + // start writing + // from the end. + + while (hasIOError) { + T_ERROR( + "TFileTransport: writer thread going to sleep for %u microseconds due to IO errors", + writerThreadIOErrorSleepTime_); + THRIFT_SLEEP_USEC(writerThreadIOErrorSleepTime_); + if (closing_) { + return; + } + if (!fd_) { + ::THRIFT_CLOSE(fd_); + fd_ = 0; + } + try { + openLogFile(); + seekToEnd(); + unflushed = 0; + hasIOError = false; + T_LOG_OPER( + "TFileTransport: log file %s reopened by writer thread during error recovery", + filename_.c_str()); + } catch (...) { + T_ERROR("TFileTransport: unable to reopen log file %s during error recovery", + filename_.c_str()); + } + } + + // sanity check on event + if ((maxEventSize_ > 0) && (outEvent->eventSize_ > maxEventSize_)) { + T_ERROR("msg size is greater than max event size: %u > %u\n", + outEvent->eventSize_, + maxEventSize_); + continue; + } + + // If chunking is required, then make sure that msg does not cross chunk boundary + if ((outEvent->eventSize_ > 0) && (chunkSize_ != 0)) { + // event size must be less than chunk size + if (outEvent->eventSize_ > chunkSize_) { + T_ERROR("TFileTransport: event size(%u) > chunk size(%u): skipping event", + outEvent->eventSize_, + chunkSize_); + continue; + } + + int64_t chunk1 = offset_ / chunkSize_; + int64_t chunk2 = (offset_ + outEvent->eventSize_ - 1) / chunkSize_; + + // if adding this event will cross a chunk boundary, pad the chunk with zeros + if (chunk1 != chunk2) { + // refetch the offset to keep in sync + offset_ = THRIFT_LSEEK(fd_, 0, SEEK_CUR); + auto padding = (int32_t)((offset_ / chunkSize_ + 1) * chunkSize_ - offset_); + + auto* zeros = new uint8_t[padding]; + memset(zeros, '\0', padding); + boost::scoped_array array(zeros); + if (-1 == ::THRIFT_WRITE(fd_, zeros, padding)) { + int errno_copy = THRIFT_ERRNO; + GlobalOutput.perror("TFileTransport: writerThread() error while padding zeros ", + errno_copy); + hasIOError = true; + continue; + } + unflushed += padding; + offset_ += padding; + } + } + + // write the dequeued event to the file + if (outEvent->eventSize_ > 0) { + if (-1 == ::THRIFT_WRITE(fd_, outEvent->eventBuff_, outEvent->eventSize_)) { + int errno_copy = THRIFT_ERRNO; + GlobalOutput.perror("TFileTransport: error while writing event ", errno_copy); + hasIOError = true; + continue; + } + unflushed += outEvent->eventSize_; + offset_ += outEvent->eventSize_; + } + } + dequeueBuffer_->reset(); + } + + if (hasIOError) { + continue; + } + + // Local variable to cache the state of forceFlush_. + // + // We only want to check the value of forceFlush_ once each time around the + // loop. If we check it more than once without holding the lock the entire + // time, it could have changed state in between. This will result in us + // making inconsistent decisions. + bool forced_flush = false; + { + Guard g(mutex_); + if (forceFlush_) { + if (!enqueueBuffer_->isEmpty()) { + // If forceFlush_ is true, we need to flush all available data. + // If enqueueBuffer_ is not empty, go back to the start of the loop to + // write it out. + // + // We know the main thread is waiting on forceFlush_ to be cleared, + // so no new events will be added to enqueueBuffer_ until we clear + // forceFlush_. Therefore the next time around the loop enqueueBuffer_ + // is guaranteed to be empty. (I.e., we're guaranteed to make progress + // and clear forceFlush_ the next time around the loop.) + continue; + } + forced_flush = true; + } + } + + // determine if we need to perform an fsync + bool flush = false; + if (forced_flush || unflushed > flushMaxBytes_) { + flush = true; + } else { + if (std::chrono::steady_clock::now() > ts_next_flush) { + if (unflushed > 0) { + flush = true; + } else { + // If there is no new data since the last fsync, + // don't perform the fsync, but do reset the timer. + ts_next_flush = getNextFlushTime(); + } + } + } + + if (flush) { + // sync (force flush) file to disk + THRIFT_FSYNC(fd_); + unflushed = 0; + ts_next_flush = getNextFlushTime(); + + // notify anybody waiting for flush completion + if (forced_flush) { + Guard g(mutex_); + forceFlush_ = false; + assert(enqueueBuffer_->isEmpty()); + assert(dequeueBuffer_->isEmpty()); + flushed_.notifyAll(); + } + } + } +} + +void TFileTransport::flush() { + // file must be open for writing for any flushing to take place + if (!writerThread_.get()) { + return; + } + // wait for flush to take place + Guard g(mutex_); + + // Indicate that we are requesting a flush + forceFlush_ = true; + // Wake up the writer thread so it will perform the flush immediately + notEmpty_.notify(); + + while (forceFlush_) { + flushed_.wait(); + } +} + +uint32_t TFileTransport::readAll(uint8_t* buf, uint32_t len) { + uint32_t have = 0; + uint32_t get = 0; + + while (have < len) { + get = read(buf + have, len - have); + if (get <= 0) { + throw TEOFException(); + } + have += get; + } + + return have; +} + +bool TFileTransport::peek() { + // check if there is an event ready to be read + if (!currentEvent_) { + currentEvent_ = readEvent(); + } + + // did not manage to read an event from the file. This could have happened + // if the timeout expired or there was some other error + if (!currentEvent_) { + return false; + } + + // check if there is anything to read + return (currentEvent_->eventSize_ - currentEvent_->eventBuffPos_) > 0; +} + +uint32_t TFileTransport::read(uint8_t* buf, uint32_t len) { + // check if there an event is ready to be read + if (!currentEvent_) { + currentEvent_ = readEvent(); + } + + // did not manage to read an event from the file. This could have happened + // if the timeout expired or there was some other error + if (!currentEvent_) { + return 0; + } + + // read as much of the current event as possible + int32_t remaining = currentEvent_->eventSize_ - currentEvent_->eventBuffPos_; + if (remaining <= (int32_t)len) { + // copy over anything thats remaining + if (remaining > 0) { + memcpy(buf, currentEvent_->eventBuff_ + currentEvent_->eventBuffPos_, remaining); + } + delete (currentEvent_); + currentEvent_ = nullptr; + return remaining; + } + + // read as much as possible + memcpy(buf, currentEvent_->eventBuff_ + currentEvent_->eventBuffPos_, len); + currentEvent_->eventBuffPos_ += len; + return len; +} + +// note caller is responsible for freeing returned events +eventInfo* TFileTransport::readEvent() { + int readTries = 0; + + if (!readBuff_) { + readBuff_ = new uint8_t[readBuffSize_]; + } + + while (1) { + // read from the file if read buffer is exhausted + if (readState_.bufferPtr_ == readState_.bufferLen_) { + // advance the offset pointer + offset_ += readState_.bufferLen_; + readState_.bufferLen_ = static_cast(::THRIFT_READ(fd_, readBuff_, readBuffSize_)); + // if (readState_.bufferLen_) { + // T_DEBUG_L(1, "Amount read: %u (offset: %lu)", readState_.bufferLen_, offset_); + // } + readState_.bufferPtr_ = 0; + readState_.lastDispatchPtr_ = 0; + + // read error + if (readState_.bufferLen_ == -1) { + readState_.resetAllValues(); + GlobalOutput("TFileTransport: error while reading from file"); + throw TTransportException("TFileTransport: error while reading from file"); + } else if (readState_.bufferLen_ == 0) { // EOF + // wait indefinitely if there is no timeout + if (readTimeout_ == TAIL_READ_TIMEOUT) { + THRIFT_SLEEP_USEC(eofSleepTime_); + continue; + } else if (readTimeout_ == NO_TAIL_READ_TIMEOUT) { + // reset state + readState_.resetState(0); + return nullptr; + } else if (readTimeout_ > 0) { + // timeout already expired once + if (readTries > 0) { + readState_.resetState(0); + return nullptr; + } else { + THRIFT_SLEEP_USEC(readTimeout_ * 1000); + readTries++; + continue; + } + } + } + } + + readTries = 0; + + // attempt to read an event from the buffer + while (readState_.bufferPtr_ < readState_.bufferLen_) { + if (readState_.readingSize_) { + if (readState_.eventSizeBuffPos_ == 0) { + if ((offset_ + readState_.bufferPtr_) / chunkSize_ + != ((offset_ + readState_.bufferPtr_ + 3) / chunkSize_)) { + // skip one byte towards chunk boundary + // T_DEBUG_L(1, "Skipping a byte"); + readState_.bufferPtr_++; + continue; + } + } + + readState_.eventSizeBuff_[readState_.eventSizeBuffPos_++] + = readBuff_[readState_.bufferPtr_++]; + + if (readState_.eventSizeBuffPos_ == 4) { + if (readState_.getEventSize() == 0) { + // 0 length event indicates padding + // T_DEBUG_L(1, "Got padding"); + readState_.resetState(readState_.lastDispatchPtr_); + continue; + } + // got a valid event + readState_.readingSize_ = false; + if (readState_.event_) { + delete (readState_.event_); + } + readState_.event_ = new eventInfo(); + readState_.event_->eventSize_ = readState_.getEventSize(); + + // check if the event is corrupted and perform recovery if required + if (isEventCorrupted()) { + performRecovery(); + // start from the top + break; + } + } + } else { + if (!readState_.event_->eventBuff_) { + readState_.event_->eventBuff_ = new uint8_t[readState_.event_->eventSize_]; + readState_.event_->eventBuffPos_ = 0; + } + // take either the entire event or the remaining bytes in the buffer + int reclaimBuffer = (std::min)((uint32_t)(readState_.bufferLen_ - readState_.bufferPtr_), + readState_.event_->eventSize_ - readState_.event_->eventBuffPos_); + + // copy data from read buffer into event buffer + memcpy(readState_.event_->eventBuff_ + readState_.event_->eventBuffPos_, + readBuff_ + readState_.bufferPtr_, + reclaimBuffer); + + // increment position ptrs + readState_.event_->eventBuffPos_ += reclaimBuffer; + readState_.bufferPtr_ += reclaimBuffer; + + // check if the event has been read in full + if (readState_.event_->eventBuffPos_ == readState_.event_->eventSize_) { + // set the completed event to the current event + eventInfo* completeEvent = readState_.event_; + completeEvent->eventBuffPos_ = 0; + + readState_.event_ = nullptr; + readState_.resetState(readState_.bufferPtr_); + + // exit criteria + return completeEvent; + } + } + } + } +} + +bool TFileTransport::isEventCorrupted() { + // an error is triggered if: + if ((maxEventSize_ > 0) && (readState_.event_->eventSize_ > maxEventSize_)) { + // 1. Event size is larger than user-speficied max-event size + T_ERROR("Read corrupt event. Event size(%u) greater than max event size (%u)", + readState_.event_->eventSize_, + maxEventSize_); + return true; + } else if (readState_.event_->eventSize_ > chunkSize_) { + // 2. Event size is larger than chunk size + T_ERROR("Read corrupt event. Event size(%u) greater than chunk size (%u)", + readState_.event_->eventSize_, + chunkSize_); + return true; + } else if (((offset_ + readState_.bufferPtr_ - 4) / chunkSize_) + != ((offset_ + readState_.bufferPtr_ + readState_.event_->eventSize_ - 1) + / chunkSize_)) { + // 3. size indicates that event crosses chunk boundary + T_ERROR("Read corrupt event. Event crosses chunk boundary. Event size:%u Offset:%lu", + readState_.event_->eventSize_, + static_cast(offset_ + readState_.bufferPtr_ + 4)); + + return true; + } + + return false; +} + +void TFileTransport::performRecovery() { + // perform some kickass recovery + uint32_t curChunk = getCurChunk(); + if (lastBadChunk_ == curChunk) { + numCorruptedEventsInChunk_++; + } else { + lastBadChunk_ = curChunk; + numCorruptedEventsInChunk_ = 1; + } + + if (numCorruptedEventsInChunk_ < maxCorruptedEvents_) { + // maybe there was an error in reading the file from disk + // seek to the beginning of chunk and try again + seekToChunk(curChunk); + } else { + + // just skip ahead to the next chunk if we not already at the last chunk + if (curChunk != (getNumChunks() - 1)) { + seekToChunk(curChunk + 1); + } else if (readTimeout_ == TAIL_READ_TIMEOUT) { + // if tailing the file, wait until there is enough data to start + // the next chunk + while (curChunk == (getNumChunks() - 1)) { + THRIFT_SLEEP_USEC(corruptedEventSleepTime_); + } + seekToChunk(curChunk + 1); + } else { + // pretty hosed at this stage, rewind the file back to the last successful + // point and punt on the error + readState_.resetState(readState_.lastDispatchPtr_); + currentEvent_ = nullptr; + char errorMsg[1024]; + sprintf(errorMsg, + "TFileTransport: log file corrupted at offset: %lu", + static_cast(offset_ + readState_.lastDispatchPtr_)); + + GlobalOutput(errorMsg); + throw TTransportException(errorMsg); + } + } +} + +void TFileTransport::seekToChunk(int32_t chunk) { + if (fd_ <= 0) { + throw TTransportException("File not open"); + } + + int32_t numChunks = getNumChunks(); + + // file is empty, seeking to chunk is pointless + if (numChunks == 0) { + return; + } + + // negative indicates reverse seek (from the end) + if (chunk < 0) { + chunk += numChunks; + } + + // too large a value for reverse seek, just seek to beginning + if (chunk < 0) { + T_DEBUG("%s", "Incorrect value for reverse seek. Seeking to beginning..."); + chunk = 0; + } + + // cannot seek past EOF + bool seekToEnd = false; + off_t minEndOffset = 0; + if (chunk >= numChunks) { + T_DEBUG("%s", "Trying to seek past EOF. Seeking to EOF instead..."); + seekToEnd = true; + chunk = numChunks - 1; + // this is the min offset to process events till + minEndOffset = ::THRIFT_LSEEK(fd_, 0, SEEK_END); + } + + off_t newOffset = off_t(chunk) * chunkSize_; + offset_ = ::THRIFT_LSEEK(fd_, newOffset, SEEK_SET); + readState_.resetAllValues(); + currentEvent_ = nullptr; + if (offset_ == -1) { + GlobalOutput("TFileTransport: lseek error in seekToChunk"); + throw TTransportException("TFileTransport: lseek error in seekToChunk"); + } + + // seek to EOF if user wanted to go to last chunk + if (seekToEnd) { + uint32_t oldReadTimeout = getReadTimeout(); + setReadTimeout(NO_TAIL_READ_TIMEOUT); + // keep on reading unti the last event at point of seekChunk call + shared_ptr event; + while ((offset_ + readState_.bufferPtr_) < minEndOffset) { + event.reset(readEvent()); + if (event.get() == nullptr) { + break; + } + } + setReadTimeout(oldReadTimeout); + } +} + +void TFileTransport::seekToEnd() { + seekToChunk(getNumChunks()); +} + +uint32_t TFileTransport::getNumChunks() { + if (fd_ <= 0) { + return 0; + } + + struct THRIFT_STAT f_info; + int rv = ::THRIFT_FSTAT(fd_, &f_info); + + if (rv < 0) { + int errno_copy = THRIFT_ERRNO; + throw TTransportException(TTransportException::UNKNOWN, + "TFileTransport::getNumChunks() (fstat)", + errno_copy); + } + + if (f_info.st_size > 0) { + size_t numChunks = ((f_info.st_size) / chunkSize_) + 1; + if (numChunks > (std::numeric_limits::max)()) + throw TTransportException("Too many chunks"); + return static_cast(numChunks); + } + + // empty file has no chunks + return 0; +} + +uint32_t TFileTransport::getCurChunk() { + return static_cast(offset_ / chunkSize_); +} + +// Utility Functions +void TFileTransport::openLogFile() { +#ifndef _WIN32 + mode_t mode = readOnly_ ? S_IRUSR | S_IRGRP | S_IROTH : S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; + int flags = readOnly_ ? O_RDONLY : O_RDWR | O_CREAT | O_APPEND; +#else + int mode = readOnly_ ? _S_IREAD : _S_IREAD | _S_IWRITE; + int flags = readOnly_ ? _O_RDONLY : _O_RDWR | _O_CREAT | _O_APPEND; +#endif + fd_ = ::THRIFT_OPEN(filename_.c_str(), flags, mode); + offset_ = 0; + + // make sure open call was successful + if (fd_ == -1) { + int errno_copy = THRIFT_ERRNO; + GlobalOutput.perror("TFileTransport: openLogFile() ::open() file: " + filename_, errno_copy); + throw TTransportException(TTransportException::NOT_OPEN, filename_, errno_copy); + } +} + +std::chrono::time_point TFileTransport::getNextFlushTime() { + return std::chrono::steady_clock::now() + std::chrono::microseconds(flushMaxUs_); +} + +TFileTransportBuffer::TFileTransportBuffer(uint32_t size) + : bufferMode_(WRITE), writePoint_(0), readPoint_(0), size_(size) { + buffer_ = new eventInfo* [size]; +} + +TFileTransportBuffer::~TFileTransportBuffer() { + if (buffer_) { + for (uint32_t i = 0; i < writePoint_; i++) { + delete buffer_[i]; + } + delete[] buffer_; + buffer_ = nullptr; + } +} + +bool TFileTransportBuffer::addEvent(eventInfo* event) { + if (bufferMode_ == READ) { + GlobalOutput("Trying to write to a buffer in read mode"); + } + if (writePoint_ < size_) { + buffer_[writePoint_++] = event; + return true; + } else { + // buffer is full + return false; + } +} + +eventInfo* TFileTransportBuffer::getNext() { + if (bufferMode_ == WRITE) { + bufferMode_ = READ; + } + if (readPoint_ < writePoint_) { + return buffer_[readPoint_++]; + } else { + // no more entries + return nullptr; + } +} + +void TFileTransportBuffer::reset() { + if (bufferMode_ == WRITE || writePoint_ > readPoint_) { + T_DEBUG("%s", "Resetting a buffer with unread entries"); + } + // Clean up the old entries + for (uint32_t i = 0; i < writePoint_; i++) { + delete buffer_[i]; + } + bufferMode_ = WRITE; + writePoint_ = 0; + readPoint_ = 0; +} + +bool TFileTransportBuffer::isFull() { + return writePoint_ == size_; +} + +bool TFileTransportBuffer::isEmpty() { + return writePoint_ == 0; +} + +TFileProcessor::TFileProcessor(shared_ptr processor, + shared_ptr protocolFactory, + shared_ptr inputTransport) + : processor_(processor), + inputProtocolFactory_(protocolFactory), + outputProtocolFactory_(protocolFactory), + inputTransport_(inputTransport) { + + // default the output transport to a null transport (common case) + outputTransport_ = std::make_shared(); +} + +TFileProcessor::TFileProcessor(shared_ptr processor, + shared_ptr inputProtocolFactory, + shared_ptr outputProtocolFactory, + shared_ptr inputTransport) + : processor_(processor), + inputProtocolFactory_(inputProtocolFactory), + outputProtocolFactory_(outputProtocolFactory), + inputTransport_(inputTransport) { + + // default the output transport to a null transport (common case) + outputTransport_ = std::make_shared(); +} + +TFileProcessor::TFileProcessor(shared_ptr processor, + shared_ptr protocolFactory, + shared_ptr inputTransport, + shared_ptr outputTransport) + : processor_(processor), + inputProtocolFactory_(protocolFactory), + outputProtocolFactory_(protocolFactory), + inputTransport_(inputTransport), + outputTransport_(outputTransport) { +} + +void TFileProcessor::process(uint32_t numEvents, bool tail) { + shared_ptr inputProtocol = inputProtocolFactory_->getProtocol(inputTransport_); + shared_ptr outputProtocol = outputProtocolFactory_->getProtocol(outputTransport_); + + // set the read timeout to 0 if tailing is required + int32_t oldReadTimeout = inputTransport_->getReadTimeout(); + if (tail) { + // save old read timeout so it can be restored + inputTransport_->setReadTimeout(TFileTransport::TAIL_READ_TIMEOUT); + } + + uint32_t numProcessed = 0; + while (1) { + // bad form to use exceptions for flow control but there is really + // no other way around it + try { + processor_->process(inputProtocol, outputProtocol, nullptr); + numProcessed++; + if ((numEvents > 0) && (numProcessed == numEvents)) { + return; + } + } catch (TEOFException&) { + if (!tail) { + break; + } + } catch (TException& te) { + cerr << te.what() << endl; + break; + } + } + + // restore old read timeout + if (tail) { + inputTransport_->setReadTimeout(oldReadTimeout); + } +} + +void TFileProcessor::processChunk() { + shared_ptr inputProtocol = inputProtocolFactory_->getProtocol(inputTransport_); + shared_ptr outputProtocol = outputProtocolFactory_->getProtocol(outputTransport_); + + uint32_t curChunk = inputTransport_->getCurChunk(); + + while (1) { + // bad form to use exceptions for flow control but there is really + // no other way around it + try { + processor_->process(inputProtocol, outputProtocol, nullptr); + if (curChunk != inputTransport_->getCurChunk()) { + break; + } + } catch (TEOFException&) { + break; + } catch (TException& te) { + cerr << te.what() << endl; + break; + } + } +} +} +} +} // apache::thrift::transport diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TFileTransport.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TFileTransport.h new file mode 100644 index 000000000..0df5cf909 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TFileTransport.h @@ -0,0 +1,439 @@ +/* + * 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. + */ + +#ifndef _THRIFT_TRANSPORT_TFILETRANSPORT_H_ +#define _THRIFT_TRANSPORT_TFILETRANSPORT_H_ 1 + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +namespace apache { +namespace thrift { +namespace transport { + +using apache::thrift::TProcessor; +using apache::thrift::protocol::TProtocolFactory; +using apache::thrift::concurrency::Mutex; +using apache::thrift::concurrency::Monitor; + +// Data pertaining to a single event +typedef struct eventInfo { + uint8_t* eventBuff_; + uint32_t eventSize_; + uint32_t eventBuffPos_; + + eventInfo() : eventBuff_(nullptr), eventSize_(0), eventBuffPos_(0){}; + ~eventInfo() { + if (eventBuff_) { + delete[] eventBuff_; + } + } +} eventInfo; + +// information about current read state +typedef struct readState { + eventInfo* event_; + + // keep track of event size + uint8_t eventSizeBuff_[4]; + uint8_t eventSizeBuffPos_; + bool readingSize_; + + // read buffer variables + int32_t bufferPtr_; + int32_t bufferLen_; + + // last successful dispatch point + int32_t lastDispatchPtr_; + + void resetState(uint32_t lastDispatchPtr) { + readingSize_ = true; + eventSizeBuffPos_ = 0; + lastDispatchPtr_ = lastDispatchPtr; + } + + void resetAllValues() { + resetState(0); + bufferPtr_ = 0; + bufferLen_ = 0; + if (event_) { + delete (event_); + } + event_ = nullptr; + } + + inline uint32_t getEventSize() { + const void* buffer = reinterpret_cast(eventSizeBuff_); + return *reinterpret_cast(buffer); + } + + readState() { + event_ = nullptr; + resetAllValues(); + } + + ~readState() { + if (event_) { + delete (event_); + } + } + +} readState; + +/** + * TFileTransportBuffer - buffer class used by TFileTransport for queueing up events + * to be written to disk. Should be used in the following way: + * 1) Buffer created + * 2) Buffer written to (addEvent) + * 3) Buffer read from (getNext) + * 4) Buffer reset (reset) + * 5) Go back to 2, or destroy buffer + * + * The buffer should never be written to after it is read from, unless it is reset first. + * Note: The above rules are enforced mainly for debugging its sole client TFileTransport + * which uses the buffer in this way. + * + */ +class TFileTransportBuffer { +public: + TFileTransportBuffer(uint32_t size); + ~TFileTransportBuffer(); + + bool addEvent(eventInfo* event); + eventInfo* getNext(); + void reset(); + bool isFull(); + bool isEmpty(); + +private: + TFileTransportBuffer(); // should not be used + + enum mode { WRITE, READ }; + mode bufferMode_; + + uint32_t writePoint_; + uint32_t readPoint_; + uint32_t size_; + eventInfo** buffer_; +}; + +/** + * Abstract interface for transports used to read files + */ +class TFileReaderTransport : virtual public TTransport { +public: + virtual int32_t getReadTimeout() = 0; + virtual void setReadTimeout(int32_t readTimeout) = 0; + + virtual uint32_t getNumChunks() = 0; + virtual uint32_t getCurChunk() = 0; + virtual void seekToChunk(int32_t chunk) = 0; + virtual void seekToEnd() = 0; +}; + +/** + * Abstract interface for transports used to write files + */ +class TFileWriterTransport : virtual public TTransport { +public: + virtual uint32_t getChunkSize() = 0; + virtual void setChunkSize(uint32_t chunkSize) = 0; +}; + +/** + * File implementation of a transport. Reads and writes are done to a + * file on disk. + * + */ +class TFileTransport : public TFileReaderTransport, public TFileWriterTransport { +public: + TFileTransport(std::string path, bool readOnly = false); + ~TFileTransport() override; + + // TODO: what is the correct behaviour for this? + // the log file is generally always open + bool isOpen() const override { return true; } + + void write(const uint8_t* buf, uint32_t len); + void flush() override; + + uint32_t readAll(uint8_t* buf, uint32_t len); + uint32_t read(uint8_t* buf, uint32_t len); + bool peek() override; + + // log-file specific functions + void seekToChunk(int32_t chunk) override; + void seekToEnd() override; + uint32_t getNumChunks() override; + uint32_t getCurChunk() override; + + // for changing the output file + void resetOutputFile(int fd, std::string filename, off_t offset); + + // Setter/Getter functions for user-controllable options + void setReadBuffSize(uint32_t readBuffSize) { + if (readBuffSize) { + readBuffSize_ = readBuffSize; + } + } + uint32_t getReadBuffSize() { return readBuffSize_; } + + static const int32_t TAIL_READ_TIMEOUT = -1; + static const int32_t NO_TAIL_READ_TIMEOUT = 0; + void setReadTimeout(int32_t readTimeout) override { readTimeout_ = readTimeout; } + int32_t getReadTimeout() override { return readTimeout_; } + + void setChunkSize(uint32_t chunkSize) override { + if (chunkSize) { + chunkSize_ = chunkSize; + } + } + uint32_t getChunkSize() override { return chunkSize_; } + + void setEventBufferSize(uint32_t bufferSize) { + if (bufferAndThreadInitialized_) { + GlobalOutput("Cannot change the buffer size after writer thread started"); + return; + } + eventBufferSize_ = bufferSize; + } + + uint32_t getEventBufferSize() { return eventBufferSize_; } + + void setFlushMaxUs(uint32_t flushMaxUs) { + if (flushMaxUs) { + flushMaxUs_ = flushMaxUs; + } + } + uint32_t getFlushMaxUs() { return flushMaxUs_; } + + void setFlushMaxBytes(uint32_t flushMaxBytes) { + if (flushMaxBytes) { + flushMaxBytes_ = flushMaxBytes; + } + } + uint32_t getFlushMaxBytes() { return flushMaxBytes_; } + + void setMaxEventSize(uint32_t maxEventSize) { maxEventSize_ = maxEventSize; } + uint32_t getMaxEventSize() { return maxEventSize_; } + + void setMaxCorruptedEvents(uint32_t maxCorruptedEvents) { + maxCorruptedEvents_ = maxCorruptedEvents; + } + uint32_t getMaxCorruptedEvents() { return maxCorruptedEvents_; } + + void setEofSleepTimeUs(uint32_t eofSleepTime) { + if (eofSleepTime) { + eofSleepTime_ = eofSleepTime; + } + } + uint32_t getEofSleepTimeUs() { return eofSleepTime_; } + + /* + * Override TTransport *_virt() functions to invoke our implementations. + * We cannot use TVirtualTransport to provide these, since we need to inherit + * virtually from TTransport. + */ + uint32_t read_virt(uint8_t* buf, uint32_t len) override { return this->read(buf, len); } + uint32_t readAll_virt(uint8_t* buf, uint32_t len) override { return this->readAll(buf, len); } + void write_virt(const uint8_t* buf, uint32_t len) override { this->write(buf, len); } + +private: + // helper functions for writing to a file + void enqueueEvent(const uint8_t* buf, uint32_t eventLen); + bool swapEventBuffers(const std::chrono::time_point *deadline); + bool initBufferAndWriteThread(); + + // control for writer thread + static void* startWriterThread(void* ptr) { + static_cast(ptr)->writerThread(); + return nullptr; + } + void writerThread(); + + // helper functions for reading from a file + eventInfo* readEvent(); + + // event corruption-related functions + bool isEventCorrupted(); + void performRecovery(); + + // Utility functions + void openLogFile(); + std::chrono::time_point getNextFlushTime(); + + // Class variables + readState readState_; + uint8_t* readBuff_; + eventInfo* currentEvent_; + + uint32_t readBuffSize_; + static const uint32_t DEFAULT_READ_BUFF_SIZE = 1 * 1024 * 1024; + + int32_t readTimeout_; + static const int32_t DEFAULT_READ_TIMEOUT_MS = 200; + + // size of chunks that file will be split up into + uint32_t chunkSize_; + static const uint32_t DEFAULT_CHUNK_SIZE = 16 * 1024 * 1024; + + // size of event buffers + uint32_t eventBufferSize_; + static const uint32_t DEFAULT_EVENT_BUFFER_SIZE = 10000; + + // max number of microseconds that can pass without flushing + uint32_t flushMaxUs_; + static const uint32_t DEFAULT_FLUSH_MAX_US = 3000000; + + // max number of bytes that can be written without flushing + uint32_t flushMaxBytes_; + static const uint32_t DEFAULT_FLUSH_MAX_BYTES = 1000 * 1024; + + // max event size + uint32_t maxEventSize_; + static const uint32_t DEFAULT_MAX_EVENT_SIZE = 0; + + // max number of corrupted events per chunk + uint32_t maxCorruptedEvents_; + static const uint32_t DEFAULT_MAX_CORRUPTED_EVENTS = 0; + + // sleep duration when EOF is hit + uint32_t eofSleepTime_; + static const uint32_t DEFAULT_EOF_SLEEP_TIME_US = 500 * 1000; + + // sleep duration when a corrupted event is encountered + uint32_t corruptedEventSleepTime_; + static const uint32_t DEFAULT_CORRUPTED_SLEEP_TIME_US = 1 * 1000 * 1000; + + // sleep duration in seconds when an IO error is encountered in the writer thread + uint32_t writerThreadIOErrorSleepTime_; + static const uint32_t DEFAULT_WRITER_THREAD_SLEEP_TIME_US = 60 * 1000 * 1000; + + // writer thread + apache::thrift::concurrency::ThreadFactory threadFactory_; + std::shared_ptr writerThread_; + + // buffers to hold data before it is flushed. Each element of the buffer stores a msg that + // needs to be written to the file. The buffers are swapped by the writer thread. + TFileTransportBuffer* dequeueBuffer_; + TFileTransportBuffer* enqueueBuffer_; + + // conditions used to block when the buffer is full or empty + Monitor notFull_, notEmpty_; + std::atomic closing_; + + // To keep track of whether the buffer has been flushed + Monitor flushed_; + std::atomic forceFlush_; + + // Mutex that is grabbed when enqueueing and swapping the read/write buffers + Mutex mutex_; + + // File information + std::string filename_; + int fd_; + + // Whether the writer thread and buffers have been initialized + bool bufferAndThreadInitialized_; + + // Offset within the file + off_t offset_; + + // event corruption information + uint32_t lastBadChunk_; + uint32_t numCorruptedEventsInChunk_; + + bool readOnly_; +}; + +// Exception thrown when EOF is hit +class TEOFException : public TTransportException { +public: + TEOFException() : TTransportException(TTransportException::END_OF_FILE){}; +}; + +// wrapper class to process events from a file containing thrift events +class TFileProcessor { +public: + /** + * Constructor that defaults output transport to null transport + * + * @param processor processes log-file events + * @param protocolFactory protocol factory + * @param inputTransport file transport + */ + TFileProcessor(std::shared_ptr processor, + std::shared_ptr protocolFactory, + std::shared_ptr inputTransport); + + TFileProcessor(std::shared_ptr processor, + std::shared_ptr inputProtocolFactory, + std::shared_ptr outputProtocolFactory, + std::shared_ptr inputTransport); + + /** + * Constructor + * + * @param processor processes log-file events + * @param protocolFactory protocol factory + * @param inputTransport input file transport + * @param output output transport + */ + TFileProcessor(std::shared_ptr processor, + std::shared_ptr protocolFactory, + std::shared_ptr inputTransport, + std::shared_ptr outputTransport); + + /** + * processes events from the file + * + * @param numEvents number of events to process (0 for unlimited) + * @param tail tails the file if true + */ + void process(uint32_t numEvents, bool tail); + + /** + * process events until the end of the chunk + * + */ + void processChunk(); + +private: + std::shared_ptr processor_; + std::shared_ptr inputProtocolFactory_; + std::shared_ptr outputProtocolFactory_; + std::shared_ptr inputTransport_; + std::shared_ptr outputTransport_; +}; +} +} +} // apache::thrift::transport + +#endif // _THRIFT_TRANSPORT_TFILETRANSPORT_H_ + diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/THeaderTransport.cpp b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/THeaderTransport.cpp new file mode 100644 index 000000000..b582d8da7 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/THeaderTransport.cpp @@ -0,0 +1,611 @@ +/* + * 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. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +using std::map; +using std::string; +using std::vector; + +namespace apache { +namespace thrift { + +using std::shared_ptr; + +namespace transport { + +using namespace apache::thrift::protocol; +using apache::thrift::protocol::TBinaryProtocol; + +uint32_t THeaderTransport::readSlow(uint8_t* buf, uint32_t len) { + if (clientType == THRIFT_UNFRAMED_BINARY || clientType == THRIFT_UNFRAMED_COMPACT) { + return transport_->read(buf, len); + } + + return TFramedTransport::readSlow(buf, len); +} + +uint16_t THeaderTransport::getProtocolId() const { + if (clientType == THRIFT_HEADER_CLIENT_TYPE) { + return protoId; + } else if (clientType == THRIFT_UNFRAMED_COMPACT || clientType == THRIFT_FRAMED_COMPACT) { + return T_COMPACT_PROTOCOL; + } else { + return T_BINARY_PROTOCOL; // Assume other transports use TBinary + } +} + +void THeaderTransport::ensureReadBuffer(uint32_t sz) { + if (sz > rBufSize_) { + rBuf_.reset(new uint8_t[sz]); + rBufSize_ = sz; + } +} + +bool THeaderTransport::readFrame() { + // szN is network byte order of sz + uint32_t szN; + uint32_t sz; + + // Read the size of the next frame. + // We can't use readAll(&sz, sizeof(sz)), since that always throws an + // exception on EOF. We want to throw an exception only if EOF occurs after + // partial size data. + uint32_t sizeBytesRead = 0; + while (sizeBytesRead < sizeof(szN)) { + uint8_t* szp = reinterpret_cast(&szN) + sizeBytesRead; + uint32_t bytesRead = transport_->read(szp, sizeof(szN) - sizeBytesRead); + if (bytesRead == 0) { + if (sizeBytesRead == 0) { + // EOF before any data was read. + return false; + } else { + // EOF after a partial frame header. Raise an exception. + throw TTransportException(TTransportException::END_OF_FILE, + "No more data to read after " + "partial frame header."); + } + } + sizeBytesRead += bytesRead; + } + + sz = ntohl(szN); + + ensureReadBuffer(4); + + if ((sz & TBinaryProtocol::VERSION_MASK) == (uint32_t)TBinaryProtocol::VERSION_1) { + // unframed + clientType = THRIFT_UNFRAMED_BINARY; + memcpy(rBuf_.get(), &szN, sizeof(szN)); + setReadBuffer(rBuf_.get(), 4); + } else if (static_cast(sz >> 24) == TCompactProtocol::PROTOCOL_ID + && (static_cast(sz >> 16) & TCompactProtocol::VERSION_MASK) + == TCompactProtocol::VERSION_N) { + clientType = THRIFT_UNFRAMED_COMPACT; + memcpy(rBuf_.get(), &szN, sizeof(szN)); + setReadBuffer(rBuf_.get(), 4); + } else { + // Could be header format or framed. Check next uint32 + uint32_t magic_n; + uint32_t magic; + + if (sz > MAX_FRAME_SIZE) { + throw TTransportException(TTransportException::CORRUPTED_DATA, + "Header transport frame is too large"); + } + + ensureReadBuffer(sz); + + // We can use readAll here, because it would be an invalid frame otherwise + transport_->readAll(reinterpret_cast(&magic_n), sizeof(magic_n)); + memcpy(rBuf_.get(), &magic_n, sizeof(magic_n)); + magic = ntohl(magic_n); + + if ((magic & TBinaryProtocol::VERSION_MASK) == (uint32_t)TBinaryProtocol::VERSION_1) { + // framed + clientType = THRIFT_FRAMED_BINARY; + transport_->readAll(rBuf_.get() + 4, sz - 4); + setReadBuffer(rBuf_.get(), sz); + } else if (static_cast(magic >> 24) == TCompactProtocol::PROTOCOL_ID + && (static_cast(magic >> 16) & TCompactProtocol::VERSION_MASK) + == TCompactProtocol::VERSION_N) { + clientType = THRIFT_FRAMED_COMPACT; + transport_->readAll(rBuf_.get() + 4, sz - 4); + setReadBuffer(rBuf_.get(), sz); + } else if (HEADER_MAGIC == (magic & HEADER_MASK)) { + if (sz < 10) { + throw TTransportException(TTransportException::CORRUPTED_DATA, + "Header transport frame is too small"); + } + + transport_->readAll(rBuf_.get() + 4, sz - 4); + + // header format + clientType = THRIFT_HEADER_CLIENT_TYPE; + // flags + flags = magic & FLAGS_MASK; + // seqId + uint32_t seqId_n; + memcpy(&seqId_n, rBuf_.get() + 4, sizeof(seqId_n)); + seqId = ntohl(seqId_n); + // header size + uint16_t headerSize_n; + memcpy(&headerSize_n, rBuf_.get() + 8, sizeof(headerSize_n)); + uint16_t headerSize = ntohs(headerSize_n); + setReadBuffer(rBuf_.get(), sz); + readHeaderFormat(headerSize, sz); + } else { + clientType = THRIFT_UNKNOWN_CLIENT_TYPE; + throw TTransportException(TTransportException::BAD_ARGS, + "Could not detect client transport type"); + } + } + + return true; +} + +/** + * Reads a string from ptr, taking care not to reach headerBoundary + * Advances ptr on success + * + * @param str output string + * @throws CORRUPTED_DATA if size of string exceeds boundary + */ +void THeaderTransport::readString(uint8_t*& ptr, + /* out */ string& str, + uint8_t const* headerBoundary) { + int32_t strLen; + + uint32_t bytes = readVarint32(ptr, &strLen, headerBoundary); + if (strLen > headerBoundary - ptr) { + throw TTransportException(TTransportException::CORRUPTED_DATA, + "Info header length exceeds header size"); + } + ptr += bytes; + str.assign(reinterpret_cast(ptr), strLen); + ptr += strLen; +} + +void THeaderTransport::readHeaderFormat(uint16_t headerSize, uint32_t sz) { + readTrans_.clear(); // Clear out any previous transforms. + readHeaders_.clear(); // Clear out any previous headers. + + // skip over already processed magic(4), seqId(4), headerSize(2) + auto* ptr = reinterpret_cast(rBuf_.get() + 10); + + // Catch integer overflow, check for reasonable header size + if (headerSize >= 16384) { + throw TTransportException(TTransportException::CORRUPTED_DATA, + "Header size is unreasonable"); + } + headerSize *= 4; + const uint8_t* const headerBoundary = ptr + headerSize; + if (headerSize > sz) { + throw TTransportException(TTransportException::CORRUPTED_DATA, + "Header size is larger than frame"); + } + uint8_t* data = ptr + headerSize; + ptr += readVarint16(ptr, &protoId, headerBoundary); + int16_t numTransforms; + ptr += readVarint16(ptr, &numTransforms, headerBoundary); + + // For now all transforms consist of only the ID, not data. + for (int i = 0; i < numTransforms; i++) { + int32_t transId; + ptr += readVarint32(ptr, &transId, headerBoundary); + + readTrans_.push_back(transId); + } + + // Info headers + while (ptr < headerBoundary) { + int32_t infoId; + ptr += readVarint32(ptr, &infoId, headerBoundary); + + if (infoId == 0) { + // header padding + break; + } + if (infoId >= infoIdType::END) { + // cannot handle infoId + break; + } + switch (infoId) { + case infoIdType::KEYVALUE: + // Process key-value headers + uint32_t numKVHeaders; + ptr += readVarint32(ptr, (int32_t*)&numKVHeaders, headerBoundary); + // continue until we reach (padded) end of packet + while (numKVHeaders-- && ptr < headerBoundary) { + // format: key; value + // both: length (varint32); value (string) + string key, value; + readString(ptr, key, headerBoundary); + // value + readString(ptr, value, headerBoundary); + // save to headers + readHeaders_[key] = value; + } + break; + } + } + + // Untransform the data section. rBuf will contain result. + untransform(data, safe_numeric_cast(static_cast(sz) - (data - rBuf_.get()))); +} + +void THeaderTransport::untransform(uint8_t* ptr, uint32_t sz) { + // Update the transform buffer size if needed + resizeTransformBuffer(); + + for (vector::const_iterator it = readTrans_.begin(); it != readTrans_.end(); ++it) { + const uint16_t transId = *it; + + if (transId == ZLIB_TRANSFORM) { + z_stream stream; + int err; + + stream.next_in = ptr; + stream.avail_in = sz; + + // Setting these to 0 means use the default free/alloc functions + stream.zalloc = (alloc_func)nullptr; + stream.zfree = (free_func)nullptr; + stream.opaque = (voidpf)nullptr; + err = inflateInit(&stream); + if (err != Z_OK) { + throw TApplicationException(TApplicationException::MISSING_RESULT, + "Error while zlib deflateInit"); + } + stream.next_out = tBuf_.get(); + stream.avail_out = tBufSize_; + err = inflate(&stream, Z_FINISH); + if (err != Z_STREAM_END || stream.avail_out == 0) { + throw TApplicationException(TApplicationException::MISSING_RESULT, + "Error while zlib deflate"); + } + sz = stream.total_out; + + err = inflateEnd(&stream); + if (err != Z_OK) { + throw TApplicationException(TApplicationException::MISSING_RESULT, + "Error while zlib deflateEnd"); + } + + memcpy(ptr, tBuf_.get(), sz); + } else { + throw TApplicationException(TApplicationException::MISSING_RESULT, "Unknown transform"); + } + } + + setReadBuffer(ptr, sz); +} + +/** + * We may have updated the wBuf size, update the tBuf size to match. + * Should be called in transform. + * + * The buffer should be slightly larger than write buffer size due to + * compression transforms (that may slightly grow on small frame sizes) + */ +void THeaderTransport::resizeTransformBuffer(uint32_t additionalSize) { + if (tBufSize_ < wBufSize_ + DEFAULT_BUFFER_SIZE) { + uint32_t new_size = wBufSize_ + DEFAULT_BUFFER_SIZE + additionalSize; + auto* new_buf = new uint8_t[new_size]; + tBuf_.reset(new_buf); + tBufSize_ = new_size; + } +} + +void THeaderTransport::transform(uint8_t* ptr, uint32_t sz) { + // Update the transform buffer size if needed + resizeTransformBuffer(); + + for (vector::const_iterator it = writeTrans_.begin(); it != writeTrans_.end(); ++it) { + const uint16_t transId = *it; + + if (transId == ZLIB_TRANSFORM) { + z_stream stream; + int err; + + stream.next_in = ptr; + stream.avail_in = sz; + + stream.zalloc = (alloc_func)nullptr; + stream.zfree = (free_func)nullptr; + stream.opaque = (voidpf)nullptr; + err = deflateInit(&stream, Z_DEFAULT_COMPRESSION); + if (err != Z_OK) { + throw TTransportException(TTransportException::CORRUPTED_DATA, + "Error while zlib deflateInit"); + } + uint32_t tbuf_size = 0; + while (err == Z_OK) { + resizeTransformBuffer(tbuf_size); + + stream.next_out = tBuf_.get(); + stream.avail_out = tBufSize_; + err = deflate(&stream, Z_FINISH); + tbuf_size += DEFAULT_BUFFER_SIZE; + } + sz = stream.total_out; + + err = deflateEnd(&stream); + if (err != Z_OK) { + throw TTransportException(TTransportException::CORRUPTED_DATA, + "Error while zlib deflateEnd"); + } + + memcpy(ptr, tBuf_.get(), sz); + } else { + throw TTransportException(TTransportException::CORRUPTED_DATA, "Unknown transform"); + } + } + + wBase_ = wBuf_.get() + sz; +} + +void THeaderTransport::resetProtocol() { + // Set to anything except HTTP type so we don't flush again + clientType = THRIFT_HEADER_CLIENT_TYPE; + + // Read the header and decide which protocol to go with + readFrame(); +} + +uint32_t THeaderTransport::getWriteBytes() { + return safe_numeric_cast(wBase_ - wBuf_.get()); +} + +/** + * Writes a string to a byte buffer, as size (varint32) + string (non-null + * terminated) + * Automatically advances ptr to after the written portion + */ +void THeaderTransport::writeString(uint8_t*& ptr, const string& str) { + auto strLen = safe_numeric_cast(str.length()); + ptr += writeVarint32(strLen, ptr); + memcpy(ptr, str.c_str(), strLen); // no need to write \0 + ptr += strLen; +} + +void THeaderTransport::setHeader(const string& key, const string& value) { + writeHeaders_[key] = value; +} + +uint32_t THeaderTransport::getMaxWriteHeadersSize() const { + size_t maxWriteHeadersSize = 0; + THeaderTransport::StringToStringMap::const_iterator it; + for (it = writeHeaders_.begin(); it != writeHeaders_.end(); ++it) { + // add sizes of key and value to maxWriteHeadersSize + // 2 varints32 + the strings themselves + maxWriteHeadersSize += 5 + 5 + (it->first).length() + (it->second).length(); + } + return safe_numeric_cast(maxWriteHeadersSize); +} + +void THeaderTransport::clearHeaders() { + writeHeaders_.clear(); +} + +void THeaderTransport::flush() { + // Write out any data waiting in the write buffer. + uint32_t haveBytes = getWriteBytes(); + + if (clientType == THRIFT_HEADER_CLIENT_TYPE) { + transform(wBuf_.get(), haveBytes); + haveBytes = getWriteBytes(); // transform may have changed the size + } + + // Note that we reset wBase_ prior to the underlying write + // to ensure we're in a sane state (i.e. internal buffer cleaned) + // if the underlying write throws up an exception + wBase_ = wBuf_.get(); + + if (haveBytes > MAX_FRAME_SIZE) { + throw TTransportException(TTransportException::CORRUPTED_DATA, + "Attempting to send frame that is too large"); + } + + if (clientType == THRIFT_HEADER_CLIENT_TYPE) { + // header size will need to be updated at the end because of varints. + // Make it big enough here for max varint size, plus 4 for padding. + uint32_t headerSize = (2 + getNumTransforms()) * THRIFT_MAX_VARINT32_BYTES + 4; + // add approximate size of info headers + headerSize += getMaxWriteHeadersSize(); + + // Pkt size + uint32_t maxSzHbo = headerSize + haveBytes // thrift header + payload + + 10; // common header section + uint8_t* pkt = tBuf_.get(); + uint8_t* headerStart; + uint8_t* headerSizePtr; + uint8_t* pktStart = pkt; + + if (maxSzHbo > tBufSize_) { + throw TTransportException(TTransportException::CORRUPTED_DATA, + "Attempting to header frame that is too large"); + } + + uint32_t szHbo; + uint32_t szNbo; + uint16_t headerSizeN; + + // Fixup szHbo later + pkt += sizeof(szNbo); + uint16_t headerN = htons(HEADER_MAGIC >> 16); + memcpy(pkt, &headerN, sizeof(headerN)); + pkt += sizeof(headerN); + uint16_t flagsN = htons(flags); + memcpy(pkt, &flagsN, sizeof(flagsN)); + pkt += sizeof(flagsN); + uint32_t seqIdN = htonl(seqId); + memcpy(pkt, &seqIdN, sizeof(seqIdN)); + pkt += sizeof(seqIdN); + headerSizePtr = pkt; + // Fixup headerSizeN later + pkt += sizeof(headerSizeN); + headerStart = pkt; + + pkt += writeVarint32(protoId, pkt); + pkt += writeVarint32(getNumTransforms(), pkt); + + // For now, each transform is only the ID, no following data. + for (vector::const_iterator it = writeTrans_.begin(); it != writeTrans_.end(); ++it) { + pkt += writeVarint32(*it, pkt); + } + + // write info headers + + // for now only write kv-headers + auto headerCount = safe_numeric_cast(writeHeaders_.size()); + if (headerCount > 0) { + pkt += writeVarint32(infoIdType::KEYVALUE, pkt); + // Write key-value headers count + pkt += writeVarint32(static_cast(headerCount), pkt); + // Write info headers + map::const_iterator it; + for (it = writeHeaders_.begin(); it != writeHeaders_.end(); ++it) { + writeString(pkt, it->first); // key + writeString(pkt, it->second); // value + } + writeHeaders_.clear(); + } + + // Fixups after varint size calculations + headerSize = safe_numeric_cast(pkt - headerStart); + uint8_t padding = 4 - (headerSize % 4); + headerSize += padding; + + // Pad out pkt with 0x00 + for (int i = 0; i < padding; i++) { + *(pkt++) = 0x00; + } + + // Pkt size + ptrdiff_t szHbp = (headerStart - pktStart - 4); + if (static_cast(szHbp) > static_cast((std::numeric_limits().max)()) - (headerSize + haveBytes)) { + throw TTransportException(TTransportException::CORRUPTED_DATA, + "Header section size is unreasonable"); + } + szHbo = headerSize + haveBytes // thrift header + payload + + static_cast(szHbp); // common header section + headerSizeN = htons(headerSize / 4); + memcpy(headerSizePtr, &headerSizeN, sizeof(headerSizeN)); + + // Set framing size. + szNbo = htonl(szHbo); + memcpy(pktStart, &szNbo, sizeof(szNbo)); + + outTransport_->write(pktStart, szHbo - haveBytes + 4); + outTransport_->write(wBuf_.get(), haveBytes); + } else if (clientType == THRIFT_FRAMED_BINARY || clientType == THRIFT_FRAMED_COMPACT) { + auto szHbo = (uint32_t)haveBytes; + uint32_t szNbo = htonl(szHbo); + + outTransport_->write(reinterpret_cast(&szNbo), 4); + outTransport_->write(wBuf_.get(), haveBytes); + } else if (clientType == THRIFT_UNFRAMED_BINARY || clientType == THRIFT_UNFRAMED_COMPACT) { + outTransport_->write(wBuf_.get(), haveBytes); + } else { + throw TTransportException(TTransportException::BAD_ARGS, "Unknown client type"); + } + + // Flush the underlying transport. + outTransport_->flush(); +} + +/** + * Read an i16 from the wire as a varint. The MSB of each byte is set + * if there is another byte to follow. This can read up to 3 bytes. + */ +uint32_t THeaderTransport::readVarint16(uint8_t const* ptr, int16_t* i16, uint8_t const* boundary) { + int32_t val; + uint32_t rsize = readVarint32(ptr, &val, boundary); + *i16 = (int16_t)val; + return rsize; +} + +/** + * Read an i32 from the wire as a varint. The MSB of each byte is set + * if there is another byte to follow. This can read up to 5 bytes. + */ +uint32_t THeaderTransport::readVarint32(uint8_t const* ptr, int32_t* i32, uint8_t const* boundary) { + + uint32_t rsize = 0; + uint32_t val = 0; + int shift = 0; + + while (true) { + if (ptr == boundary) { + throw TApplicationException(TApplicationException::INVALID_MESSAGE_TYPE, + "Trying to read past header boundary"); + } + uint8_t byte = *(ptr++); + rsize++; + val |= (uint64_t)(byte & 0x7f) << shift; + shift += 7; + if (!(byte & 0x80)) { + *i32 = val; + return rsize; + } + } +} + +/** + * Write an i32 as a varint. Results in 1-5 bytes on the wire. + */ +uint32_t THeaderTransport::writeVarint32(int32_t n, uint8_t* pkt) { + uint8_t buf[5]; + uint32_t wsize = 0; + + while (true) { + if ((n & ~0x7F) == 0) { + buf[wsize++] = (int8_t)n; + break; + } else { + buf[wsize++] = (int8_t)((n & 0x7F) | 0x80); + n >>= 7; + } + } + + // Caller will advance pkt. + for (uint32_t i = 0; i < wsize; i++) { + pkt[i] = buf[i]; + } + + return wsize; +} + +uint32_t THeaderTransport::writeVarint16(int16_t n, uint8_t* pkt) { + return writeVarint32(n, pkt); +} +} +} +} // apache::thrift::transport diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/THeaderTransport.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/THeaderTransport.h new file mode 100644 index 000000000..d1e9d4339 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/THeaderTransport.h @@ -0,0 +1,275 @@ +/* + * 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. + */ + +#ifndef THRIFT_TRANSPORT_THEADERTRANSPORT_H_ +#define THRIFT_TRANSPORT_THEADERTRANSPORT_H_ 1 + +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_STDINT_H +#include +#elif HAVE_INTTYPES_H +#include +#endif + +#include + +#include +#include +#include +#include + +enum CLIENT_TYPE { + THRIFT_HEADER_CLIENT_TYPE = 0, + THRIFT_FRAMED_BINARY = 1, + THRIFT_UNFRAMED_BINARY = 2, + THRIFT_FRAMED_COMPACT = 3, + THRIFT_UNFRAMED_COMPACT = 4, + THRIFT_UNKNOWN_CLIENT_TYPE = 5, +}; + +namespace apache { +namespace thrift { +namespace transport { + +using apache::thrift::protocol::T_COMPACT_PROTOCOL; + +/** + * Header transport. All writes go into an in-memory buffer until flush is + * called, at which point the transport writes the length of the entire + * binary chunk followed by the data payload. This allows the receiver on the + * other end to always do fixed-length reads. + * + * Subclass TFramedTransport because most of the read/write methods are similar + * and need similar buffers. Major changes are readFrame & flush. + * + * Header Transport *must* be the same transport for both input and + * output when used on the server side - client responses should be + * the same protocol as those in the request. + */ +class THeaderTransport : public TVirtualTransport { +public: + static const int DEFAULT_BUFFER_SIZE = 512u; + static const int THRIFT_MAX_VARINT32_BYTES = 5; + + /// Use default buffer sizes. + explicit THeaderTransport(const std::shared_ptr& transport) + : TVirtualTransport(transport), + outTransport_(transport), + protoId(T_COMPACT_PROTOCOL), + clientType(THRIFT_HEADER_CLIENT_TYPE), + seqId(0), + flags(0), + tBufSize_(0), + tBuf_(nullptr) { + if (!transport_) throw std::invalid_argument("transport is empty"); + initBuffers(); + } + + THeaderTransport(const std::shared_ptr inTransport, + const std::shared_ptr outTransport) + : TVirtualTransport(inTransport), + outTransport_(outTransport), + protoId(T_COMPACT_PROTOCOL), + clientType(THRIFT_HEADER_CLIENT_TYPE), + seqId(0), + flags(0), + tBufSize_(0), + tBuf_(nullptr) { + if (!transport_) throw std::invalid_argument("inTransport is empty"); + if (!outTransport_) throw std::invalid_argument("outTransport is empty"); + initBuffers(); + } + + uint32_t readSlow(uint8_t* buf, uint32_t len) override; + void flush() override; + + void resizeTransformBuffer(uint32_t additionalSize = 0); + + uint16_t getProtocolId() const; + void setProtocolId(uint16_t protoId) { this->protoId = protoId; } + + void resetProtocol(); + + /** + * We know we got a packet in header format here, try to parse the header + * + * @param headerSize size of the header portion + * @param sz Size of the whole message, including header + */ + void readHeaderFormat(uint16_t headerSize, uint32_t sz); + + /** + * Untransform the data based on the received header flags + * On conclusion of function, setReadBuffer is called with the + * untransformed data. + * + * @param ptr ptr to data + * @param size of data + */ + void untransform(uint8_t* ptr, uint32_t sz); + + /** + * Transform the data based on our write transform flags + * At conclusion of function the write buffer is set to the + * transformed data. + * + * @param ptr Ptr to data to transform + * @param sz Size of data buffer + */ + void transform(uint8_t* ptr, uint32_t sz); + + uint16_t getNumTransforms() const { + return safe_numeric_cast(writeTrans_.size()); + } + + void setTransform(uint16_t transId) { writeTrans_.push_back(transId); } + + // Info headers + + typedef std::map StringToStringMap; + + // these work with write headers + void setHeader(const std::string& key, const std::string& value); + + void clearHeaders(); + + StringToStringMap& getWriteHeaders() { return writeHeaders_; } + + // these work with read headers + const StringToStringMap& getHeaders() const { return readHeaders_; } + + // accessors for seqId + int32_t getSequenceNumber() const { return seqId; } + void setSequenceNumber(int32_t seqId) { this->seqId = seqId; } + + enum TRANSFORMS { + ZLIB_TRANSFORM = 0x01, + }; + +protected: + /** + * Reads a frame of input from the underlying stream. + * + * Returns true if a frame was read successfully, or false on EOF. + * (Raises a TTransportException if EOF occurs after a partial frame.) + */ + bool readFrame() override; + + void ensureReadBuffer(uint32_t sz); + uint32_t getWriteBytes(); + + void initBuffers() { + setReadBuffer(nullptr, 0); + setWriteBuffer(wBuf_.get(), wBufSize_); + } + + std::shared_ptr outTransport_; + + // 0 and 16th bits must be 0 to differentiate from framed & unframed + static const uint32_t HEADER_MAGIC = 0x0FFF0000; + static const uint32_t HEADER_MASK = 0xFFFF0000; + static const uint32_t FLAGS_MASK = 0x0000FFFF; + + static const uint32_t MAX_FRAME_SIZE = 0x3FFFFFFF; + + int16_t protoId; + uint16_t clientType; + uint32_t seqId; + uint16_t flags; + + std::vector readTrans_; + std::vector writeTrans_; + + // Map to use for headers + StringToStringMap readHeaders_; + StringToStringMap writeHeaders_; + + /** + * Returns the maximum number of bytes that write k/v headers can take + */ + uint32_t getMaxWriteHeadersSize() const; + + struct infoIdType { + enum idType { + // start at 1 to avoid confusing header padding for an infoId + KEYVALUE = 1, + END // signal the end of infoIds we can handle + }; + }; + + // Buffers to use for transform processing + uint32_t tBufSize_; + boost::scoped_array tBuf_; + + void readString(uint8_t*& ptr, /* out */ std::string& str, uint8_t const* headerBoundary); + + void writeString(uint8_t*& ptr, const std::string& str); + + // Varint utils + /** + * Read an i16 from the wire as a varint. The MSB of each byte is set + * if there is another byte to follow. This can read up to 3 bytes. + */ + uint32_t readVarint16(uint8_t const* ptr, int16_t* i16, uint8_t const* boundary); + + /** + * Read an i32 from the wire as a varint. The MSB of each byte is set + * if there is another byte to follow. This can read up to 5 bytes. + */ + uint32_t readVarint32(uint8_t const* ptr, int32_t* i32, uint8_t const* boundary); + + /** + * Write an i32 as a varint. Results in 1-5 bytes on the wire. + */ + uint32_t writeVarint32(int32_t n, uint8_t* pkt); + + /** + * Write an i16 as a varint. Results in 1-3 bytes on the wire. + */ + uint32_t writeVarint16(int16_t n, uint8_t* pkt); +}; + +/** + * Wraps a transport into a header one. + * + */ +class THeaderTransportFactory : public TTransportFactory { +public: + THeaderTransportFactory() = default; + + ~THeaderTransportFactory() override = default; + + /** + * Wraps the transport into a header one. + */ + std::shared_ptr getTransport(std::shared_ptr trans) override { + return std::shared_ptr(new THeaderTransport(trans)); + } +}; +} +} +} // apache::thrift::transport + +#endif // #ifndef THRIFT_TRANSPORT_THEADERTRANSPORT_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/THttpClient.cpp b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/THttpClient.cpp new file mode 100644 index 000000000..04566c991 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/THttpClient.cpp @@ -0,0 +1,122 @@ +/* + * 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. + */ + +#include +#include +#include +#include + +#include +#include +#include + +using std::string; + +namespace apache { +namespace thrift { +namespace transport { + +THttpClient::THttpClient(std::shared_ptr transport, + std::string host, + std::string path) + : THttpTransport(transport), host_(host), path_(path) { +} + +THttpClient::THttpClient(string host, int port, string path) + : THttpTransport(std::shared_ptr(new TSocket(host, port))), + host_(host), + path_(path) { +} + +THttpClient::~THttpClient() = default; + +void THttpClient::parseHeader(char* header) { + char* colon = strchr(header, ':'); + if (colon == nullptr) { + return; + } + char* value = colon + 1; + + if (boost::istarts_with(header, "Transfer-Encoding")) { + if (boost::iends_with(value, "chunked")) { + chunked_ = true; + } + } else if (boost::istarts_with(header, "Content-Length")) { + chunked_ = false; + contentLength_ = atoi(value); + } +} + +bool THttpClient::parseStatusLine(char* status) { + char* http = status; + + char* code = strchr(http, ' '); + if (code == nullptr) { + throw TTransportException(string("Bad Status: ") + status); + } + + *code = '\0'; + while (*(code++) == ' ') { + }; + + char* msg = strchr(code, ' '); + if (msg == nullptr) { + throw TTransportException(string("Bad Status: ") + status); + } + *msg = '\0'; + + if (strcmp(code, "200") == 0) { + // HTTP 200 = OK, we got the response + return true; + } else if (strcmp(code, "100") == 0) { + // HTTP 100 = continue, just keep reading + return false; + } else { + throw TTransportException(string("Bad Status: ") + status); + } +} + +void THttpClient::flush() { + // Fetch the contents of the write buffer + uint8_t* buf; + uint32_t len; + writeBuffer_.getBuffer(&buf, &len); + + // Construct the HTTP header + std::ostringstream h; + h << "POST " << path_ << " HTTP/1.1" << CRLF << "Host: " << host_ << CRLF + << "Content-Type: application/x-thrift" << CRLF << "Content-Length: " << len << CRLF + << "Accept: application/x-thrift" << CRLF << "User-Agent: Thrift/" << PACKAGE_VERSION + << " (C++/THttpClient)" << CRLF << CRLF; + string header = h.str(); + + if (header.size() > (std::numeric_limits::max)()) + throw TTransportException("Header too big"); + // Write the header, then the data, then flush + transport_->write((const uint8_t*)header.c_str(), static_cast(header.size())); + transport_->write(buf, len); + transport_->flush(); + + // Reset the buffer and header variables + writeBuffer_.resetBuffer(); + readHeaders_ = true; +} +} +} +} // apache::thrift::transport diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/THttpClient.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/THttpClient.h new file mode 100644 index 000000000..fdca505c8 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/THttpClient.h @@ -0,0 +1,50 @@ +/* + * 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. + */ + +#ifndef _THRIFT_TRANSPORT_THTTPCLIENT_H_ +#define _THRIFT_TRANSPORT_THTTPCLIENT_H_ 1 + +#include + +namespace apache { +namespace thrift { +namespace transport { + +class THttpClient : public THttpTransport { +public: + THttpClient(std::shared_ptr transport, std::string host, std::string path = ""); + + THttpClient(std::string host, int port, std::string path = ""); + + ~THttpClient() override; + + void flush() override; + +protected: + std::string host_; + std::string path_; + + void parseHeader(char* header) override; + bool parseStatusLine(char* status) override; +}; +} +} +} // apache::thrift::transport + +#endif // #ifndef _THRIFT_TRANSPORT_THTTPCLIENT_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/THttpServer.cpp b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/THttpServer.cpp new file mode 100644 index 000000000..94ac681e7 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/THttpServer.cpp @@ -0,0 +1,168 @@ +/* + * 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. + */ + +#include +#include +#include + +#include +#include +#include +#if defined(_MSC_VER) || defined(__MINGW32__) + #include +#endif + +using std::string; + +namespace apache { +namespace thrift { +namespace transport { + +THttpServer::THttpServer(std::shared_ptr transport) : THttpTransport(transport) { +} + +THttpServer::~THttpServer() = default; + +#if defined(_MSC_VER) || defined(__MINGW32__) + #define THRIFT_GMTIME(TM, TIME) gmtime_s(&TM, &TIME) + #define THRIFT_strncasecmp(str1, str2, len) _strnicmp(str1, str2, len) + #define THRIFT_strcasestr(haystack, needle) StrStrIA(haystack, needle) +#else + #define THRIFT_GMTIME(TM, TIME) gmtime_r(&TIME, &TM) + #define THRIFT_strncasecmp(str1, str2, len) strncasecmp(str1, str2, len) + #define THRIFT_strcasestr(haystack, needle) strcasestr(haystack, needle) +#endif + +void THttpServer::parseHeader(char* header) { + char* colon = strchr(header, ':'); + if (colon == nullptr) { + return; + } + size_t sz = colon - header; + char* value = colon + 1; + + if (THRIFT_strncasecmp(header, "Transfer-Encoding", sz) == 0) { + if (THRIFT_strcasestr(value, "chunked") != nullptr) { + chunked_ = true; + } + } else if (THRIFT_strncasecmp(header, "Content-length", sz) == 0) { + chunked_ = false; + contentLength_ = atoi(value); + } else if (strncmp(header, "X-Forwarded-For", sz) == 0) { + origin_ = value; + } +} + +bool THttpServer::parseStatusLine(char* status) { + char* method = status; + + char* path = strchr(method, ' '); + if (path == nullptr) { + throw TTransportException(string("Bad Status: ") + status); + } + + *path = '\0'; + while (*(++path) == ' ') { + }; + + char* http = strchr(path, ' '); + if (http == nullptr) { + throw TTransportException(string("Bad Status: ") + status); + } + *http = '\0'; + + if (strcmp(method, "POST") == 0) { + // POST method ok, looking for content. + return true; + } else if (strcmp(method, "OPTIONS") == 0) { + // preflight OPTIONS method, we don't need further content. + // how to graciously close connection? + uint8_t* buf; + uint32_t len; + writeBuffer_.getBuffer(&buf, &len); + + // Construct the HTTP header + std::ostringstream h; + h << "HTTP/1.1 200 OK" << CRLF << "Date: " << getTimeRFC1123() << CRLF + << "Access-Control-Allow-Origin: *" << CRLF << "Access-Control-Allow-Methods: POST, OPTIONS" + << CRLF << "Access-Control-Allow-Headers: Content-Type" << CRLF << CRLF; + string header = h.str(); + + // Write the header, then the data, then flush + transport_->write((const uint8_t*)header.c_str(), static_cast(header.size())); + transport_->write(buf, len); + transport_->flush(); + + // Reset the buffer and header variables + writeBuffer_.resetBuffer(); + readHeaders_ = true; + return true; + } + throw TTransportException(string("Bad Status (unsupported method): ") + status); +} + +void THttpServer::flush() { + // Fetch the contents of the write buffer + uint8_t* buf; + uint32_t len; + writeBuffer_.getBuffer(&buf, &len); + + // Construct the HTTP header + std::ostringstream h; + h << "HTTP/1.1 200 OK" << CRLF << "Date: " << getTimeRFC1123() << CRLF << "Server: Thrift/" + << PACKAGE_VERSION << CRLF << "Access-Control-Allow-Origin: *" << CRLF + << "Content-Type: application/x-thrift" << CRLF << "Content-Length: " << len << CRLF + << "Connection: Keep-Alive" << CRLF << CRLF; + string header = h.str(); + + // Write the header, then the data, then flush + // cast should be fine, because none of "header" is under attacker control + transport_->write((const uint8_t*)header.c_str(), static_cast(header.size())); + transport_->write(buf, len); + transport_->flush(); + + // Reset the buffer and header variables + writeBuffer_.resetBuffer(); + readHeaders_ = true; +} + +std::string THttpServer::getTimeRFC1123() { + static const char* Days[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; + static const char* Months[] + = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; + char buff[128]; + + time_t t = time(nullptr); + struct tm tmb; + THRIFT_GMTIME(tmb, t); + + sprintf(buff, + "%s, %d %s %d %d:%d:%d GMT", + Days[tmb.tm_wday], + tmb.tm_mday, + Months[tmb.tm_mon], + tmb.tm_year + 1900, + tmb.tm_hour, + tmb.tm_min, + tmb.tm_sec); + return std::string(buff); +} +} +} +} // apache::thrift::transport diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/THttpServer.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/THttpServer.h new file mode 100644 index 000000000..0e83399d1 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/THttpServer.h @@ -0,0 +1,64 @@ +/* + * 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. + */ + +#ifndef _THRIFT_TRANSPORT_THTTPSERVER_H_ +#define _THRIFT_TRANSPORT_THTTPSERVER_H_ 1 + +#include + +namespace apache { +namespace thrift { +namespace transport { + +class THttpServer : public THttpTransport { +public: + THttpServer(std::shared_ptr transport); + + ~THttpServer() override; + + void flush() override; + +protected: + void readHeaders(); + void parseHeader(char* header) override; + bool parseStatusLine(char* status) override; + std::string getTimeRFC1123(); +}; + +/** + * Wraps a transport into HTTP protocol + */ +class THttpServerTransportFactory : public TTransportFactory { +public: + THttpServerTransportFactory() = default; + + ~THttpServerTransportFactory() override = default; + + /** + * Wraps the transport into a buffered one. + */ + std::shared_ptr getTransport(std::shared_ptr trans) override { + return std::shared_ptr(new THttpServer(trans)); + } +}; +} +} +} // apache::thrift::transport + +#endif // #ifndef _THRIFT_TRANSPORT_THTTPSERVER_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/THttpTransport.cpp b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/THttpTransport.cpp new file mode 100644 index 000000000..aea2b2847 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/THttpTransport.cpp @@ -0,0 +1,270 @@ +/* + * 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. + */ + +#include + +#include + +using std::string; + +namespace apache { +namespace thrift { +namespace transport { + +// Yeah, yeah, hacky to put these here, I know. +const char* THttpTransport::CRLF = "\r\n"; +const int THttpTransport::CRLF_LEN = 2; + +THttpTransport::THttpTransport(std::shared_ptr transport) + : transport_(transport), + origin_(""), + readHeaders_(true), + chunked_(false), + chunkedDone_(false), + chunkSize_(0), + contentLength_(0), + httpBuf_(nullptr), + httpPos_(0), + httpBufLen_(0), + httpBufSize_(1024) { + init(); +} + +void THttpTransport::init() { + httpBuf_ = (char*)std::malloc(httpBufSize_ + 1); + if (httpBuf_ == nullptr) { + throw std::bad_alloc(); + } + httpBuf_[httpBufLen_] = '\0'; +} + +THttpTransport::~THttpTransport() { + if (httpBuf_ != nullptr) { + std::free(httpBuf_); + } +} + +uint32_t THttpTransport::read(uint8_t* buf, uint32_t len) { + if (readBuffer_.available_read() == 0) { + readBuffer_.resetBuffer(); + uint32_t got = readMoreData(); + if (got == 0) { + return 0; + } + } + return readBuffer_.read(buf, len); +} + +uint32_t THttpTransport::readEnd() { + // Read any pending chunked data (footers etc.) + if (chunked_) { + while (!chunkedDone_) { + readChunked(); + } + } + return 0; +} + +uint32_t THttpTransport::readMoreData() { + uint32_t size; + + if (httpPos_ == httpBufLen_) { + // Get more data! + refill(); + } + + if (readHeaders_) { + readHeaders(); + } + + if (chunked_) { + size = readChunked(); + } else { + size = readContent(contentLength_); + readHeaders_ = true; + } + + return size; +} + +uint32_t THttpTransport::readChunked() { + uint32_t length = 0; + + char* line = readLine(); + uint32_t chunkSize = parseChunkSize(line); + if (chunkSize == 0) { + readChunkedFooters(); + } else { + // Read data content + length += readContent(chunkSize); + // Read trailing CRLF after content + readLine(); + } + return length; +} + +void THttpTransport::readChunkedFooters() { + // End of data, read footer lines until a blank one appears + while (true) { + char* line = readLine(); + if (strlen(line) == 0) { + chunkedDone_ = true; + break; + } + } +} + +uint32_t THttpTransport::parseChunkSize(char* line) { + char* semi = strchr(line, ';'); + if (semi != nullptr) { + *semi = '\0'; + } + uint32_t size = 0; + sscanf(line, "%x", &size); + return size; +} + +uint32_t THttpTransport::readContent(uint32_t size) { + uint32_t need = size; + while (need > 0) { + uint32_t avail = httpBufLen_ - httpPos_; + if (avail == 0) { + // We have given all the data, reset position to head of the buffer + httpPos_ = 0; + httpBufLen_ = 0; + refill(); + + // Now have available however much we read + avail = httpBufLen_; + } + uint32_t give = avail; + if (need < give) { + give = need; + } + readBuffer_.write((uint8_t*)(httpBuf_ + httpPos_), give); + httpPos_ += give; + need -= give; + } + return size; +} + +char* THttpTransport::readLine() { + while (true) { + char* eol = nullptr; + + eol = strstr(httpBuf_ + httpPos_, CRLF); + + // No CRLF yet? + if (eol == nullptr) { + // Shift whatever we have now to front and refill + shift(); + refill(); + } else { + // Return pointer to next line + *eol = '\0'; + char* line = httpBuf_ + httpPos_; + httpPos_ = static_cast((eol - httpBuf_) + CRLF_LEN); + return line; + } + } +} + +void THttpTransport::shift() { + if (httpBufLen_ > httpPos_) { + // Shift down remaining data and read more + uint32_t length = httpBufLen_ - httpPos_; + memmove(httpBuf_, httpBuf_ + httpPos_, length); + httpBufLen_ = length; + } else { + httpBufLen_ = 0; + } + httpPos_ = 0; + httpBuf_[httpBufLen_] = '\0'; +} + +void THttpTransport::refill() { + uint32_t avail = httpBufSize_ - httpBufLen_; + if (avail <= (httpBufSize_ / 4)) { + httpBufSize_ *= 2; + char* tmpBuf = (char*)std::realloc(httpBuf_, httpBufSize_ + 1); + if (tmpBuf == nullptr) { + throw std::bad_alloc(); + } + httpBuf_ = tmpBuf; + } + + // Read more data + uint32_t got = transport_->read((uint8_t*)(httpBuf_ + httpBufLen_), httpBufSize_ - httpBufLen_); + httpBufLen_ += got; + httpBuf_[httpBufLen_] = '\0'; + + if (got == 0) { + throw TTransportException(TTransportException::END_OF_FILE, "Could not refill buffer"); + } +} + +void THttpTransport::readHeaders() { + // Initialize headers state variables + contentLength_ = 0; + chunked_ = false; + chunkedDone_ = false; + chunkSize_ = 0; + + // Control state flow + bool statusLine = true; + bool finished = false; + + // Loop until headers are finished + while (true) { + char* line = readLine(); + + if (strlen(line) == 0) { + if (finished) { + readHeaders_ = false; + return; + } else { + // Must have been an HTTP 100, keep going for another status line + statusLine = true; + } + } else { + if (statusLine) { + statusLine = false; + finished = parseStatusLine(line); + } else { + parseHeader(line); + } + } + } +} + +void THttpTransport::write(const uint8_t* buf, uint32_t len) { + writeBuffer_.write(buf, len); +} + +const std::string THttpTransport::getOrigin() const { + std::ostringstream oss; + if (!origin_.empty()) { + oss << origin_ << ", "; + } + oss << transport_->getOrigin(); + return oss.str(); +} +} +} +} diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/THttpTransport.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/THttpTransport.h new file mode 100644 index 000000000..75f0d8c07 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/THttpTransport.h @@ -0,0 +1,104 @@ +/* + * 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. + */ + +#ifndef _THRIFT_TRANSPORT_THTTPTRANSPORT_H_ +#define _THRIFT_TRANSPORT_THTTPTRANSPORT_H_ 1 + +#include +#include + +namespace apache { +namespace thrift { +namespace transport { + +/** + * HTTP implementation of the thrift transport. This was irritating + * to write, but the alternatives in C++ land are daunting. Linking CURL + * requires 23 dynamic libraries last time I checked (WTF?!?). All we have + * here is a VERY basic HTTP/1.1 client which supports HTTP 100 Continue, + * chunked transfer encoding, keepalive, etc. Tested against Apache. + */ +class THttpTransport : public TVirtualTransport { +public: + THttpTransport(std::shared_ptr transport); + + ~THttpTransport() override; + + void open() override { transport_->open(); } + + bool isOpen() const override { return transport_->isOpen(); } + + bool peek() override { return transport_->peek(); } + + void close() override { transport_->close(); } + + uint32_t read(uint8_t* buf, uint32_t len); + + uint32_t readEnd() override; + + void write(const uint8_t* buf, uint32_t len); + + void flush() override = 0; + + const std::string getOrigin() const override; + +protected: + std::shared_ptr transport_; + std::string origin_; + + TMemoryBuffer writeBuffer_; + TMemoryBuffer readBuffer_; + + bool readHeaders_; + bool chunked_; + bool chunkedDone_; + uint32_t chunkSize_; + uint32_t contentLength_; + + char* httpBuf_; + uint32_t httpPos_; + uint32_t httpBufLen_; + uint32_t httpBufSize_; + + virtual void init(); + + uint32_t readMoreData(); + char* readLine(); + + void readHeaders(); + virtual void parseHeader(char* header) = 0; + virtual bool parseStatusLine(char* status) = 0; + + uint32_t readChunked(); + void readChunkedFooters(); + uint32_t parseChunkSize(char* line); + + uint32_t readContent(uint32_t size); + + void refill(); + void shift(); + + static const char* CRLF; + static const int CRLF_LEN; +}; +} +} +} // apache::thrift::transport + +#endif // #ifndef _THRIFT_TRANSPORT_THTTPCLIENT_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TNonblockingSSLServerSocket.cpp b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TNonblockingSSLServerSocket.cpp new file mode 100644 index 000000000..adec5d0f9 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TNonblockingSSLServerSocket.cpp @@ -0,0 +1,58 @@ +/* + * 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. + */ + +#include +#include + +namespace apache { +namespace thrift { +namespace transport { + +/** + * Nonblocking SSL server socket implementation. + */ +TNonblockingSSLServerSocket::TNonblockingSSLServerSocket(int port, std::shared_ptr factory) + : TNonblockingServerSocket(port), factory_(factory) { + factory_->server(true); +} + +TNonblockingSSLServerSocket::TNonblockingSSLServerSocket(const std::string& address, + int port, + std::shared_ptr factory) + : TNonblockingServerSocket(address, port), factory_(factory) { + factory_->server(true); +} + +TNonblockingSSLServerSocket::TNonblockingSSLServerSocket(int port, + int sendTimeout, + int recvTimeout, + std::shared_ptr factory) + : TNonblockingServerSocket(port, sendTimeout, recvTimeout), factory_(factory) { + factory_->server(true); +} + +std::shared_ptr TNonblockingSSLServerSocket::createSocket(THRIFT_SOCKET client) { + std::shared_ptr tSSLSocket; + tSSLSocket = factory_->createSocket(client); + tSSLSocket->setLibeventSafe(); + return tSSLSocket; +} +} +} +} diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TNonblockingSSLServerSocket.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TNonblockingSSLServerSocket.h new file mode 100644 index 000000000..a38bf1266 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TNonblockingSSLServerSocket.h @@ -0,0 +1,76 @@ +/* + * 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. + */ + +#ifndef _THRIFT_TRANSPORT_TNONBLOCKINGSSLSERVERSOCKET_H_ +#define _THRIFT_TRANSPORT_TNONBLOCKINGSSLSERVERSOCKET_H_ 1 + +#include + +namespace apache { +namespace thrift { +namespace transport { + +class TSSLSocketFactory; + +/** + * Nonblocking Server socket that accepts SSL connections. + */ +class TNonblockingSSLServerSocket : public TNonblockingServerSocket { +public: + /** + * Constructor. Binds to all interfaces. + * + * @param port Listening port + * @param factory SSL socket factory implementation + */ + TNonblockingSSLServerSocket(int port, std::shared_ptr factory); + + /** + * Constructor. Binds to the specified address. + * + * @param address Address to bind to + * @param port Listening port + * @param factory SSL socket factory implementation + */ + TNonblockingSSLServerSocket(const std::string& address, + int port, + std::shared_ptr factory); + + /** + * Constructor. Binds to all interfaces. + * + * @param port Listening port + * @param sendTimeout Socket send timeout + * @param recvTimeout Socket receive timeout + * @param factory SSL socket factory implementation + */ + TNonblockingSSLServerSocket(int port, + int sendTimeout, + int recvTimeout, + std::shared_ptr factory); + +protected: + std::shared_ptr createSocket(THRIFT_SOCKET socket) override; + std::shared_ptr factory_; +}; +} +} +} + +#endif diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TNonblockingServerSocket.cpp b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TNonblockingServerSocket.cpp new file mode 100644 index 000000000..9902b9063 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TNonblockingServerSocket.cpp @@ -0,0 +1,549 @@ +/* + * 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. + */ + +#include + +#include +#include +#include +#include +#ifdef HAVE_SYS_SOCKET_H +#include +#endif +#ifdef HAVE_SYS_UN_H +#include +#endif +#ifdef HAVE_SYS_POLL_H +#include +#endif +#ifdef HAVE_NETINET_IN_H +#include +#include +#endif +#ifdef HAVE_NETDB_H +#include +#endif +#include +#ifdef HAVE_UNISTD_H +#include +#endif + +#include +#include +#include + +#ifndef AF_LOCAL +#define AF_LOCAL AF_UNIX +#endif + +#ifndef SOCKOPT_CAST_T +#ifndef _WIN32 +#define SOCKOPT_CAST_T void +#else +#define SOCKOPT_CAST_T char +#endif // _WIN32 +#endif + +template +inline const SOCKOPT_CAST_T* const_cast_sockopt(const T* v) { + return reinterpret_cast(v); +} + +template +inline SOCKOPT_CAST_T* cast_sockopt(T* v) { + return reinterpret_cast(v); +} + +namespace apache { +namespace thrift { +namespace transport { + +using std::string; +using std::shared_ptr; + +TNonblockingServerSocket::TNonblockingServerSocket(int port) + : port_(port), + listenPort_(port), + serverSocket_(THRIFT_INVALID_SOCKET), + acceptBacklog_(DEFAULT_BACKLOG), + sendTimeout_(0), + recvTimeout_(0), + retryLimit_(0), + retryDelay_(0), + tcpSendBuffer_(0), + tcpRecvBuffer_(0), + keepAlive_(false), + listening_(false) { +} + +TNonblockingServerSocket::TNonblockingServerSocket(int port, int sendTimeout, int recvTimeout) + : port_(port), + listenPort_(port), + serverSocket_(THRIFT_INVALID_SOCKET), + acceptBacklog_(DEFAULT_BACKLOG), + sendTimeout_(sendTimeout), + recvTimeout_(recvTimeout), + retryLimit_(0), + retryDelay_(0), + tcpSendBuffer_(0), + tcpRecvBuffer_(0), + keepAlive_(false), + listening_(false) { +} + +TNonblockingServerSocket::TNonblockingServerSocket(const string& address, int port) + : port_(port), + listenPort_(port), + address_(address), + serverSocket_(THRIFT_INVALID_SOCKET), + acceptBacklog_(DEFAULT_BACKLOG), + sendTimeout_(0), + recvTimeout_(0), + retryLimit_(0), + retryDelay_(0), + tcpSendBuffer_(0), + tcpRecvBuffer_(0), + keepAlive_(false), + listening_(false) { +} + +TNonblockingServerSocket::TNonblockingServerSocket(const string& path) + : port_(0), + listenPort_(0), + path_(path), + serverSocket_(THRIFT_INVALID_SOCKET), + acceptBacklog_(DEFAULT_BACKLOG), + sendTimeout_(0), + recvTimeout_(0), + retryLimit_(0), + retryDelay_(0), + tcpSendBuffer_(0), + tcpRecvBuffer_(0), + keepAlive_(false), + listening_(false) { +} + +TNonblockingServerSocket::~TNonblockingServerSocket() { + close(); +} + +void TNonblockingServerSocket::setSendTimeout(int sendTimeout) { + sendTimeout_ = sendTimeout; +} + +void TNonblockingServerSocket::setRecvTimeout(int recvTimeout) { + recvTimeout_ = recvTimeout; +} + +void TNonblockingServerSocket::setAcceptBacklog(int accBacklog) { + acceptBacklog_ = accBacklog; +} + +void TNonblockingServerSocket::setRetryLimit(int retryLimit) { + retryLimit_ = retryLimit; +} + +void TNonblockingServerSocket::setRetryDelay(int retryDelay) { + retryDelay_ = retryDelay; +} + +void TNonblockingServerSocket::setTcpSendBuffer(int tcpSendBuffer) { + tcpSendBuffer_ = tcpSendBuffer; +} + +void TNonblockingServerSocket::setTcpRecvBuffer(int tcpRecvBuffer) { + tcpRecvBuffer_ = tcpRecvBuffer; +} + +void TNonblockingServerSocket::listen() { + listening_ = true; +#ifdef _WIN32 + TWinsockSingleton::create(); +#endif // _WIN32 + + // Validate port number + if (port_ < 0 || port_ > 0xFFFF) { + throw TTransportException(TTransportException::BAD_ARGS, "Specified port is invalid"); + } + + const struct addrinfo *res; + int error; + char port[sizeof("65535")]; + THRIFT_SNPRINTF(port, sizeof(port), "%d", port_); + + struct addrinfo hints; + std::memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG; + + // If address is not specified use wildcard address (NULL) + TGetAddrInfoWrapper info(address_.empty() ? nullptr : &address_[0], port, &hints); + + error = info.init(); + if (error) { + GlobalOutput.printf("getaddrinfo %d: %s", error, THRIFT_GAI_STRERROR(error)); + close(); + throw TTransportException(TTransportException::NOT_OPEN, + "Could not resolve host for server socket."); + } + + // Pick the ipv6 address first since ipv4 addresses can be mapped + // into ipv6 space. + for (res = info.res(); res; res = res->ai_next) { + if (res->ai_family == AF_INET6 || res->ai_next == nullptr) + break; + } + + if (!path_.empty()) { + serverSocket_ = socket(PF_UNIX, SOCK_STREAM, IPPROTO_IP); + } else if (res != nullptr) { + serverSocket_ = socket(res->ai_family, res->ai_socktype, res->ai_protocol); + } + + if (serverSocket_ == THRIFT_INVALID_SOCKET) { + int errno_copy = THRIFT_GET_SOCKET_ERROR; + GlobalOutput.perror("TNonblockingServerSocket::listen() socket() ", errno_copy); + close(); + throw TTransportException(TTransportException::NOT_OPEN, + "Could not create server socket.", + errno_copy); + } + + // Set THRIFT_NO_SOCKET_CACHING to prevent 2MSL delay on accept + int one = 1; + if (-1 == setsockopt(serverSocket_, + SOL_SOCKET, + THRIFT_NO_SOCKET_CACHING, + cast_sockopt(&one), + sizeof(one))) { +// ignore errors coming out of this setsockopt on Windows. This is because +// SO_EXCLUSIVEADDRUSE requires admin privileges on WinXP, but we don't +// want to force servers to be an admin. +#ifndef _WIN32 + int errno_copy = THRIFT_GET_SOCKET_ERROR; + GlobalOutput.perror("TNonblockingServerSocket::listen() setsockopt() THRIFT_NO_SOCKET_CACHING ", + errno_copy); + close(); + throw TTransportException(TTransportException::NOT_OPEN, + "Could not set THRIFT_NO_SOCKET_CACHING", + errno_copy); +#endif + } + + // Set TCP buffer sizes + if (tcpSendBuffer_ > 0) { + if (-1 == setsockopt(serverSocket_, + SOL_SOCKET, + SO_SNDBUF, + cast_sockopt(&tcpSendBuffer_), + sizeof(tcpSendBuffer_))) { + int errno_copy = THRIFT_GET_SOCKET_ERROR; + GlobalOutput.perror("TNonblockingServerSocket::listen() setsockopt() SO_SNDBUF ", errno_copy); + close(); + throw TTransportException(TTransportException::NOT_OPEN, + "Could not set SO_SNDBUF", + errno_copy); + } + } + + if (tcpRecvBuffer_ > 0) { + if (-1 == setsockopt(serverSocket_, + SOL_SOCKET, + SO_RCVBUF, + cast_sockopt(&tcpRecvBuffer_), + sizeof(tcpRecvBuffer_))) { + int errno_copy = THRIFT_GET_SOCKET_ERROR; + GlobalOutput.perror("TNonblockingServerSocket::listen() setsockopt() SO_RCVBUF ", errno_copy); + close(); + throw TTransportException(TTransportException::NOT_OPEN, + "Could not set SO_RCVBUF", + errno_copy); + } + } + +#ifdef IPV6_V6ONLY + if (res->ai_family == AF_INET6 && path_.empty()) { + int zero = 0; + if (-1 == setsockopt(serverSocket_, + IPPROTO_IPV6, + IPV6_V6ONLY, + cast_sockopt(&zero), + sizeof(zero))) { + GlobalOutput.perror("TNonblockingServerSocket::listen() IPV6_V6ONLY ", THRIFT_GET_SOCKET_ERROR); + } + } +#endif // #ifdef IPV6_V6ONLY + + // Turn linger off, don't want to block on calls to close + struct linger ling = {0, 0}; + if (-1 == setsockopt(serverSocket_, SOL_SOCKET, SO_LINGER, cast_sockopt(&ling), sizeof(ling))) { + int errno_copy = THRIFT_GET_SOCKET_ERROR; + GlobalOutput.perror("TNonblockingServerSocket::listen() setsockopt() SO_LINGER ", errno_copy); + close(); + throw TTransportException(TTransportException::NOT_OPEN, "Could not set SO_LINGER", errno_copy); + } + + // Keepalive to ensure full result flushing + if (-1 == setsockopt(serverSocket_, SOL_SOCKET, SO_KEEPALIVE, const_cast_sockopt(&one), sizeof(one))) { + int errno_copy = THRIFT_GET_SOCKET_ERROR; + GlobalOutput.perror("TNonblockingServerSocket::listen() setsockopt() SO_KEEPALIVE ", errno_copy); + close(); + throw TTransportException(TTransportException::NOT_OPEN, + "Could not set TCP_NODELAY", + errno_copy); + } + + // Set TCP nodelay if available, MAC OS X Hack + // See http://lists.danga.com/pipermail/memcached/2005-March/001240.html +#ifndef TCP_NOPUSH + // Unix Sockets do not need that + if (path_.empty()) { + // TCP Nodelay, speed over bandwidth + if (-1 + == setsockopt(serverSocket_, IPPROTO_TCP, TCP_NODELAY, cast_sockopt(&one), sizeof(one))) { + int errno_copy = THRIFT_GET_SOCKET_ERROR; + GlobalOutput.perror("TNonblockingServerSocket::listen() setsockopt() TCP_NODELAY ", errno_copy); + close(); + throw TTransportException(TTransportException::NOT_OPEN, + "Could not set TCP_NODELAY", + errno_copy); + } + } +#endif + + // Set NONBLOCK on the accept socket + int flags = THRIFT_FCNTL(serverSocket_, THRIFT_F_GETFL, 0); + if (flags == -1) { + int errno_copy = THRIFT_GET_SOCKET_ERROR; + GlobalOutput.perror("TNonblockingServerSocket::listen() THRIFT_FCNTL() THRIFT_F_GETFL ", errno_copy); + close(); + throw TTransportException(TTransportException::NOT_OPEN, + "THRIFT_FCNTL() THRIFT_F_GETFL failed", + errno_copy); + } + + if (-1 == THRIFT_FCNTL(serverSocket_, THRIFT_F_SETFL, flags | THRIFT_O_NONBLOCK)) { + int errno_copy = THRIFT_GET_SOCKET_ERROR; + GlobalOutput.perror("TNonblockingServerSocket::listen() THRIFT_FCNTL() THRIFT_O_NONBLOCK ", errno_copy); + close(); + throw TTransportException(TTransportException::NOT_OPEN, + "THRIFT_FCNTL() THRIFT_F_SETFL THRIFT_O_NONBLOCK failed", + errno_copy); + } + +#ifdef TCP_LOW_MIN_RTO + if (TSocket::getUseLowMinRto()) { + if (-1 == setsockopt(s, IPPROTO_TCP, TCP_LOW_MIN_RTO, const_cast_sockopt(&one), sizeof(one))) { + int errno_copy = THRIFT_GET_SOCKET_ERROR; + GlobalOutput.perror("TNonblockingServerSocket::listen() setsockopt() TCP_LOW_MIN_RTO ", errno_copy); + close(); + throw TTransportException(TTransportException::NOT_OPEN, + "Could not set TCP_NODELAY", + errno_copy); + } + } +#endif + + // prepare the port information + // we may want to try to bind more than once, since THRIFT_NO_SOCKET_CACHING doesn't + // always seem to work. The client can configure the retry variables. + int retries = 0; + int errno_copy = 0; + + if (!path_.empty()) { + +#ifndef _WIN32 + + // Unix Domain Socket + size_t len = path_.size() + 1; + if (len > sizeof(((sockaddr_un*)nullptr)->sun_path)) { + errno_copy = THRIFT_GET_SOCKET_ERROR; + GlobalOutput.perror("TSocket::listen() Unix Domain socket path too long", errno_copy); + throw TTransportException(TTransportException::NOT_OPEN, + "Unix Domain socket path too long", + errno_copy); + } + + struct sockaddr_un address; + address.sun_family = AF_UNIX; + memcpy(address.sun_path, path_.c_str(), len); + + auto structlen = static_cast(sizeof(address)); + + if (!address.sun_path[0]) { // abstract namespace socket +#ifdef __linux__ + // sun_path is not null-terminated in this case and structlen determines its length + structlen -= sizeof(address.sun_path) - len; +#else + GlobalOutput.perror("TSocket::open() Abstract Namespace Domain sockets only supported on linux: ", -99); + throw TTransportException(TTransportException::NOT_OPEN, + " Abstract Namespace Domain socket path not supported"); +#endif + } + + do { + if (0 == ::bind(serverSocket_, (struct sockaddr*)&address, structlen)) { + break; + } + errno_copy = THRIFT_GET_SOCKET_ERROR; + // use short circuit evaluation here to only sleep if we need to + } while ((retries++ < retryLimit_) && (THRIFT_SLEEP_SEC(retryDelay_) == 0)); +#else + GlobalOutput.perror("TSocket::open() Unix Domain socket path not supported on windows", -99); + throw TTransportException(TTransportException::NOT_OPEN, + " Unix Domain socket path not supported"); +#endif + } else { + do { + if (0 == ::bind(serverSocket_, res->ai_addr, static_cast(res->ai_addrlen))) { + break; + } + errno_copy = THRIFT_GET_SOCKET_ERROR; + // use short circuit evaluation here to only sleep if we need to + } while ((retries++ < retryLimit_) && (THRIFT_SLEEP_SEC(retryDelay_) == 0)); + + // retrieve bind info + if (port_ == 0 && retries <= retryLimit_) { + struct sockaddr_storage sa; + socklen_t len = sizeof(sa); + std::memset(&sa, 0, len); + if (::getsockname(serverSocket_, reinterpret_cast(&sa), &len) < 0) { + errno_copy = THRIFT_GET_SOCKET_ERROR; + GlobalOutput.perror("TNonblockingServerSocket::getPort() getsockname() ", errno_copy); + } else { + if (sa.ss_family == AF_INET6) { + const auto* sin = reinterpret_cast(&sa); + listenPort_ = ntohs(sin->sin6_port); + } else { + const auto* sin = reinterpret_cast(&sa); + listenPort_ = ntohs(sin->sin_port); + } + } + } + } + + // throw an error if we failed to bind properly + if (retries > retryLimit_) { + char errbuf[1024]; + if (!path_.empty()) { + THRIFT_SNPRINTF(errbuf, sizeof(errbuf), "TNonblockingServerSocket::listen() PATH %s", path_.c_str()); + } else { + THRIFT_SNPRINTF(errbuf, sizeof(errbuf), "TNonblockingServerSocket::listen() BIND %d", port_); + } + GlobalOutput(errbuf); + close(); + throw TTransportException(TTransportException::NOT_OPEN, + "Could not bind", + errno_copy); + } + + if (listenCallback_) + listenCallback_(serverSocket_); + + // Call listen + if (-1 == ::listen(serverSocket_, acceptBacklog_)) { + errno_copy = THRIFT_GET_SOCKET_ERROR; + GlobalOutput.perror("TNonblockingServerSocket::listen() listen() ", errno_copy); + close(); + throw TTransportException(TTransportException::NOT_OPEN, "Could not listen", errno_copy); + } + + // The socket is now listening! +} + +int TNonblockingServerSocket::getPort() { + return port_; +} + +int TNonblockingServerSocket::getListenPort() { + return listenPort_; +} + +shared_ptr TNonblockingServerSocket::acceptImpl() { + if (serverSocket_ == THRIFT_INVALID_SOCKET) { + throw TTransportException(TTransportException::NOT_OPEN, "TNonblockingServerSocket not listening"); + } + + struct sockaddr_storage clientAddress; + int size = sizeof(clientAddress); + THRIFT_SOCKET clientSocket + = ::accept(serverSocket_, (struct sockaddr*)&clientAddress, (socklen_t*)&size); + + if (clientSocket == THRIFT_INVALID_SOCKET) { + int errno_copy = THRIFT_GET_SOCKET_ERROR; + GlobalOutput.perror("TNonblockingServerSocket::acceptImpl() ::accept() ", errno_copy); + throw TTransportException(TTransportException::UNKNOWN, "accept()", errno_copy); + } + + // Explicitly set this socket to NONBLOCK mode + int flags = THRIFT_FCNTL(clientSocket, THRIFT_F_GETFL, 0); + if (flags == -1) { + int errno_copy = THRIFT_GET_SOCKET_ERROR; + ::THRIFT_CLOSESOCKET(clientSocket); + GlobalOutput.perror("TNonblockingServerSocket::acceptImpl() THRIFT_FCNTL() THRIFT_F_GETFL ", errno_copy); + throw TTransportException(TTransportException::UNKNOWN, + "THRIFT_FCNTL(THRIFT_F_GETFL)", + errno_copy); + } + + if (-1 == THRIFT_FCNTL(clientSocket, THRIFT_F_SETFL, flags | THRIFT_O_NONBLOCK)) { + int errno_copy = THRIFT_GET_SOCKET_ERROR; + ::THRIFT_CLOSESOCKET(clientSocket); + GlobalOutput + .perror("TNonblockingServerSocket::acceptImpl() THRIFT_FCNTL() THRIFT_F_SETFL ~THRIFT_O_NONBLOCK ", + errno_copy); + throw TTransportException(TTransportException::UNKNOWN, + "THRIFT_FCNTL(THRIFT_F_SETFL)", + errno_copy); + } + + shared_ptr client = createSocket(clientSocket); + if (sendTimeout_ > 0) { + client->setSendTimeout(sendTimeout_); + } + if (recvTimeout_ > 0) { + client->setRecvTimeout(recvTimeout_); + } + if (keepAlive_) { + client->setKeepAlive(keepAlive_); + } + client->setCachedAddress((sockaddr*)&clientAddress, size); + + if (acceptCallback_) + acceptCallback_(clientSocket); + + return client; +} + +shared_ptr TNonblockingServerSocket::createSocket(THRIFT_SOCKET clientSocket) { + return std::make_shared(clientSocket); +} + +void TNonblockingServerSocket::close() { + if (serverSocket_ != THRIFT_INVALID_SOCKET) { + shutdown(serverSocket_, THRIFT_SHUT_RDWR); + ::THRIFT_CLOSESOCKET(serverSocket_); + } + serverSocket_ = THRIFT_INVALID_SOCKET; + listening_ = false; +} +} +} +} // apache::thrift::transport diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TNonblockingServerSocket.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TNonblockingServerSocket.h new file mode 100644 index 000000000..a68c28d22 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TNonblockingServerSocket.h @@ -0,0 +1,136 @@ +/* + * 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. + */ + +#ifndef _THRIFT_TRANSPORT_TNONBLOCKINGSERVERSOCKET_H_ +#define _THRIFT_TRANSPORT_TNONBLOCKINGSERVERSOCKET_H_ 1 + +#include +#include + +namespace apache { +namespace thrift { +namespace transport { + +class TSocket; + +/** + * Nonblocking Server socket implementation of TNonblockingServerTransport. Wrapper around a unix + * socket listen and accept calls. + * + */ +class TNonblockingServerSocket : public TNonblockingServerTransport { +public: + typedef std::function socket_func_t; + + const static int DEFAULT_BACKLOG = 1024; + + /** + * Constructor. + * + * @param port Port number to bind to + */ + TNonblockingServerSocket(int port); + + /** + * Constructor. + * + * @param port Port number to bind to + * @param sendTimeout Socket send timeout + * @param recvTimeout Socket receive timeout + */ + TNonblockingServerSocket(int port, int sendTimeout, int recvTimeout); + + /** + * Constructor. + * + * @param address Address to bind to + * @param port Port number to bind to + */ + TNonblockingServerSocket(const std::string& address, int port); + + /** + * Constructor used for unix sockets. + * + * @param path Pathname for unix socket. + */ + TNonblockingServerSocket(const std::string& path); + + ~TNonblockingServerSocket() override; + + void setSendTimeout(int sendTimeout); + void setRecvTimeout(int recvTimeout); + + void setAcceptBacklog(int accBacklog); + + void setRetryLimit(int retryLimit); + void setRetryDelay(int retryDelay); + + void setKeepAlive(bool keepAlive) { keepAlive_ = keepAlive; } + + void setTcpSendBuffer(int tcpSendBuffer); + void setTcpRecvBuffer(int tcpRecvBuffer); + + // listenCallback gets called just before listen, and after all Thrift + // setsockopt calls have been made. If you have custom setsockopt + // things that need to happen on the listening socket, this is the place to do it. + void setListenCallback(const socket_func_t& listenCallback) { listenCallback_ = listenCallback; } + + // acceptCallback gets called after each accept call, on the newly created socket. + // It is called after all Thrift setsockopt calls have been made. If you have + // custom setsockopt things that need to happen on the accepted + // socket, this is the place to do it. + void setAcceptCallback(const socket_func_t& acceptCallback) { acceptCallback_ = acceptCallback; } + + THRIFT_SOCKET getSocketFD() override { return serverSocket_; } + + int getPort() override; + + int getListenPort() override; + + void listen() override; + void close() override; + +protected: + std::shared_ptr acceptImpl() override; + virtual std::shared_ptr createSocket(THRIFT_SOCKET client); + +private: + int port_; + int listenPort_; + std::string address_; + std::string path_; + THRIFT_SOCKET serverSocket_; + int acceptBacklog_; + int sendTimeout_; + int recvTimeout_; + int retryLimit_; + int retryDelay_; + int tcpSendBuffer_; + int tcpRecvBuffer_; + bool keepAlive_; + bool listening_; + + socket_func_t listenCallback_; + socket_func_t acceptCallback_; +}; +} +} +} // apache::thrift::transport + +#endif // #ifndef _THRIFT_TRANSPORT_TNONBLOCKINGSERVERSOCKET_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TNonblockingServerTransport.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TNonblockingServerTransport.h new file mode 100644 index 000000000..f81132869 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TNonblockingServerTransport.h @@ -0,0 +1,100 @@ +/* + * 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. + */ + +#ifndef _THRIFT_TRANSPORT_TNONBLOCKINGSERVERTRANSPORT_H_ +#define _THRIFT_TRANSPORT_TNONBLOCKINGSERVERTRANSPORT_H_ 1 + +#include +#include + +namespace apache { +namespace thrift { +namespace transport { + +/** + * Server transport framework. A server needs to have some facility for + * creating base transports to read/write from. The server is expected + * to keep track of TTransport children that it creates for purposes of + * controlling their lifetime. + */ +class TNonblockingServerTransport { +public: + virtual ~TNonblockingServerTransport() = default; + + /** + * Starts the server transport listening for new connections. Prior to this + * call most transports will not return anything when accept is called. + * + * @throws TTransportException if we were unable to listen + */ + virtual void listen() {} + + /** + * Gets a new dynamically allocated transport object and passes it to the + * caller. Note that it is the explicit duty of the caller to free the + * allocated object. The returned TTransport object must always be in the + * opened state. NULL should never be returned, instead an Exception should + * always be thrown. + * + * @return A new TTransport object + * @throws TTransportException if there is an error + */ + std::shared_ptr accept() { + std::shared_ptr result = acceptImpl(); + if (!result) { + throw TTransportException("accept() may not return NULL"); + } + return result; + } + + /** + * Utility method + * + * @return server socket file descriptor + * @throw TTransportException If an error occurs + */ + + virtual THRIFT_SOCKET getSocketFD() = 0; + + virtual int getPort() = 0; + + virtual int getListenPort() = 0; + + /** + * Closes this transport such that future calls to accept will do nothing. + */ + virtual void close() = 0; + +protected: + TNonblockingServerTransport() = default; + + /** + * Subclasses should implement this function for accept. + * + * @return A newly allocated TTransport object + * @throw TTransportException If an error occurs + */ + virtual std::shared_ptr acceptImpl() = 0; + +}; +} +} +} // apache::thrift::transport + +#endif // #ifndef _THRIFT_TRANSPORT_TNONBLOCKINGSERVERTRANSPORT_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TPipe.cpp b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TPipe.cpp new file mode 100644 index 000000000..72af4fcd6 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TPipe.cpp @@ -0,0 +1,398 @@ +/* +* 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. +*/ + +#include +#include +#ifdef _WIN32 +#include +#include +#endif + +namespace apache { +namespace thrift { +namespace transport { + +/** +* TPipe implementation. +*/ + +#ifdef _WIN32 + +uint32_t pipe_read(HANDLE pipe, uint8_t* buf, uint32_t len); +void pipe_write(HANDLE pipe, const uint8_t* buf, uint32_t len); + +uint32_t pseudo_sync_read(HANDLE pipe, HANDLE event, uint8_t* buf, uint32_t len); +void pseudo_sync_write(HANDLE pipe, HANDLE event, const uint8_t* buf, uint32_t len); + +class TPipeImpl : boost::noncopyable { +public: + TPipeImpl() {} + virtual ~TPipeImpl() {} + virtual uint32_t read(uint8_t* buf, uint32_t len) = 0; + virtual void write(const uint8_t* buf, uint32_t len) = 0; + virtual HANDLE getPipeHandle() = 0; // doubles as the read handle for anon pipe + virtual void setPipeHandle(HANDLE pipehandle) = 0; + virtual HANDLE getWrtPipeHandle() { return INVALID_HANDLE_VALUE; } + virtual void setWrtPipeHandle(HANDLE) {} + virtual bool isBufferedDataAvailable() { return false; } + virtual HANDLE getNativeWaitHandle() { return INVALID_HANDLE_VALUE; } +}; + +class TNamedPipeImpl : public TPipeImpl { +public: + explicit TNamedPipeImpl(TAutoHandle &pipehandle) : Pipe_(pipehandle.release()) {} + virtual ~TNamedPipeImpl() {} + virtual uint32_t read(uint8_t* buf, uint32_t len) { + return pseudo_sync_read(Pipe_.h, read_event_.h, buf, len); + } + virtual void write(const uint8_t* buf, uint32_t len) { + pseudo_sync_write(Pipe_.h, write_event_.h, buf, len); + } + + virtual HANDLE getPipeHandle() { return Pipe_.h; } + virtual void setPipeHandle(HANDLE pipehandle) { Pipe_.reset(pipehandle); } + +private: + TManualResetEvent read_event_; + TManualResetEvent write_event_; + TAutoHandle Pipe_; +}; + +class TAnonPipeImpl : public TPipeImpl { +public: + TAnonPipeImpl(HANDLE PipeRd, HANDLE PipeWrt) : PipeRd_(PipeRd), PipeWrt_(PipeWrt) {} + virtual ~TAnonPipeImpl() {} + virtual uint32_t read(uint8_t* buf, uint32_t len) { return pipe_read(PipeRd_.h, buf, len); } + virtual void write(const uint8_t* buf, uint32_t len) { pipe_write(PipeWrt_.h, buf, len); } + + virtual HANDLE getPipeHandle() { return PipeRd_.h; } + virtual void setPipeHandle(HANDLE PipeRd) { PipeRd_.reset(PipeRd); } + virtual HANDLE getWrtPipeHandle() { return PipeWrt_.h; } + virtual void setWrtPipeHandle(HANDLE PipeWrt) { PipeWrt_.reset(PipeWrt); } + +private: + TAutoHandle PipeRd_; + TAutoHandle PipeWrt_; +}; + +// If you want a select-like loop to work, use this subclass. Be warned... +// the read implementation has several context switches, so this is slower +// than using the regular named pipe implementation +class TWaitableNamedPipeImpl : public TPipeImpl { +public: + explicit TWaitableNamedPipeImpl(TAutoHandle &pipehandle) + : begin_unread_idx_(0), end_unread_idx_(0) { + readOverlap_.action = TOverlappedWorkItem::READ; + readOverlap_.h = pipehandle.h; + cancelOverlap_.action = TOverlappedWorkItem::CANCELIO; + cancelOverlap_.h = pipehandle.h; + buffer_.resize(1024 /*arbitrary buffer size*/, '\0'); + beginAsyncRead(&buffer_[0], static_cast(buffer_.size())); + Pipe_.reset(pipehandle.release()); + } + virtual ~TWaitableNamedPipeImpl() { + // see if there is an outstanding read request + if (begin_unread_idx_ == end_unread_idx_) { + // if so, cancel it, and wait for the dead completion + thread_->addWorkItem(&cancelOverlap_); + readOverlap_.overlappedResults(false /*ignore errors*/); + } + } + virtual uint32_t read(uint8_t* buf, uint32_t len); + virtual void write(const uint8_t* buf, uint32_t len) { + pseudo_sync_write(Pipe_.h, write_event_.h, buf, len); + } + + virtual HANDLE getPipeHandle() { return Pipe_.h; } + virtual void setPipeHandle(HANDLE pipehandle) { Pipe_.reset(pipehandle); } + virtual bool isBufferedDataAvailable() { return begin_unread_idx_ < end_unread_idx_; } + virtual HANDLE getNativeWaitHandle() { return ready_event_.h; } + +private: + void beginAsyncRead(uint8_t* buf, uint32_t len); + uint32_t endAsyncRead(); + + TAutoOverlapThread thread_; + TAutoHandle Pipe_; + TOverlappedWorkItem readOverlap_; + TOverlappedWorkItem cancelOverlap_; + TManualResetEvent ready_event_; + TManualResetEvent write_event_; + std::vector buffer_; + uint32_t begin_unread_idx_; + uint32_t end_unread_idx_; +}; + +void TWaitableNamedPipeImpl::beginAsyncRead(uint8_t* buf, uint32_t len) { + begin_unread_idx_ = end_unread_idx_ = 0; + readOverlap_.reset(buf, len, ready_event_.h); + thread_->addWorkItem(&readOverlap_); + if (readOverlap_.success == FALSE && readOverlap_.last_error != ERROR_IO_PENDING) { + GlobalOutput.perror("TPipe ::ReadFile errored GLE=", readOverlap_.last_error); + throw TTransportException(TTransportException::UNKNOWN, "TPipe: ReadFile failed"); + } +} + +uint32_t TWaitableNamedPipeImpl::endAsyncRead() { + return readOverlap_.overlappedResults(); +} + +uint32_t TWaitableNamedPipeImpl::read(uint8_t* buf, uint32_t len) { + if (begin_unread_idx_ == end_unread_idx_) { + end_unread_idx_ = endAsyncRead(); + } + + uint32_t __idxsize = end_unread_idx_ - begin_unread_idx_; + uint32_t bytes_to_copy = (len < __idxsize) ? len : __idxsize; + memcpy(buf, &buffer_[begin_unread_idx_], bytes_to_copy); + begin_unread_idx_ += bytes_to_copy; + if (begin_unread_idx_ != end_unread_idx_) { + assert(len == bytes_to_copy); + // we were able to fulfill the read with just the bytes in our + // buffer, and we still have buffer left + return bytes_to_copy; + } + uint32_t bytes_copied = bytes_to_copy; + + // all of the requested data has been read. Kick off an async read for the next round. + beginAsyncRead(&buffer_[0], static_cast(buffer_.size())); + + return bytes_copied; +} + +void pseudo_sync_write(HANDLE pipe, HANDLE event, const uint8_t* buf, uint32_t len) { + OVERLAPPED tempOverlap; + memset(&tempOverlap, 0, sizeof(tempOverlap)); + tempOverlap.hEvent = event; + + uint32_t written = 0; + while (written < len) { + BOOL result = ::WriteFile(pipe, buf + written, len - written, NULL, &tempOverlap); + + if (result == FALSE && ::GetLastError() != ERROR_IO_PENDING) { + GlobalOutput.perror("TPipe ::WriteFile errored GLE=", ::GetLastError()); + throw TTransportException(TTransportException::UNKNOWN, "TPipe: write failed"); + } + + DWORD bytes = 0; + result = ::GetOverlappedResult(pipe, &tempOverlap, &bytes, TRUE); + if (!result) { + GlobalOutput.perror("TPipe ::GetOverlappedResult errored GLE=", ::GetLastError()); + throw TTransportException(TTransportException::UNKNOWN, "TPipe: GetOverlappedResult failed"); + } + written += bytes; + } +} + +uint32_t pseudo_sync_read(HANDLE pipe, HANDLE event, uint8_t* buf, uint32_t len) { + OVERLAPPED tempOverlap; + memset(&tempOverlap, 0, sizeof(tempOverlap)); + tempOverlap.hEvent = event; + + BOOL result = ::ReadFile(pipe, buf, len, NULL, &tempOverlap); + + if (result == FALSE && ::GetLastError() != ERROR_IO_PENDING) { + GlobalOutput.perror("TPipe ::ReadFile errored GLE=", ::GetLastError()); + throw TTransportException(TTransportException::UNKNOWN, "TPipe: read failed"); + } + + DWORD bytes = 0; + result = ::GetOverlappedResult(pipe, &tempOverlap, &bytes, TRUE); + if (!result) { + GlobalOutput.perror("TPipe ::GetOverlappedResult errored GLE=", ::GetLastError()); + throw TTransportException(TTransportException::UNKNOWN, "TPipe: GetOverlappedResult failed"); + } + return bytes; +} + +//---- Constructors ---- +TPipe::TPipe(TAutoHandle &Pipe) + : impl_(new TWaitableNamedPipeImpl(Pipe)), TimeoutSeconds_(3), isAnonymous_(false) { +} + +TPipe::TPipe(HANDLE Pipe) + : TimeoutSeconds_(3), isAnonymous_(false) +{ + TAutoHandle pipeHandle(Pipe); + impl_.reset(new TWaitableNamedPipeImpl(pipeHandle)); +} + +TPipe::TPipe(const char* pipename) : TimeoutSeconds_(3), isAnonymous_(false) { + setPipename(pipename); +} + +TPipe::TPipe(const std::string& pipename) : TimeoutSeconds_(3), isAnonymous_(false) { + setPipename(pipename); +} + +TPipe::TPipe(HANDLE PipeRd, HANDLE PipeWrt) + : impl_(new TAnonPipeImpl(PipeRd, PipeWrt)), TimeoutSeconds_(3), isAnonymous_(true) { +} + +TPipe::TPipe() : TimeoutSeconds_(3), isAnonymous_(false) { +} + +TPipe::~TPipe() { +} + +//--------------------------------------------------------- +// Transport callbacks +//--------------------------------------------------------- +bool TPipe::isOpen() const { + return impl_.get() != NULL; +} + +bool TPipe::peek() { + return isOpen(); +} + +void TPipe::open() { + if (isOpen()) + return; + + TAutoHandle hPipe; + do { + DWORD flags = FILE_FLAG_OVERLAPPED; // async mode, so we can do reads at the same time as writes + hPipe.reset(CreateFileA(pipename_.c_str(), + GENERIC_READ | GENERIC_WRITE, + 0, // no sharing + NULL, // default security attributes + OPEN_EXISTING, // opens existing pipe + flags, + NULL)); // no template file + + if (hPipe.h != INVALID_HANDLE_VALUE) + break; // success! + + if (::GetLastError() != ERROR_PIPE_BUSY) { + GlobalOutput.perror("TPipe::open ::CreateFile errored GLE=", ::GetLastError()); + throw TTransportException(TTransportException::NOT_OPEN, "Unable to open pipe"); + } + } while (::WaitNamedPipeA(pipename_.c_str(), TimeoutSeconds_ * 1000)); + + if (hPipe.h == INVALID_HANDLE_VALUE) { + GlobalOutput.perror("TPipe::open ::CreateFile errored GLE=", ::GetLastError()); + throw TTransportException(TTransportException::NOT_OPEN, "Unable to open pipe"); + } + + impl_.reset(new TNamedPipeImpl(hPipe)); +} + +void TPipe::close() { + impl_.reset(); +} + +uint32_t TPipe::read(uint8_t* buf, uint32_t len) { + if (!isOpen()) + throw TTransportException(TTransportException::NOT_OPEN, "Called read on non-open pipe"); + return impl_->read(buf, len); +} + +uint32_t pipe_read(HANDLE pipe, uint8_t* buf, uint32_t len) { + DWORD cbRead; + int fSuccess = ReadFile(pipe, // pipe handle + buf, // buffer to receive reply + len, // size of buffer + &cbRead, // number of bytes read + NULL); // not overlapped + + if (!fSuccess && GetLastError() != ERROR_MORE_DATA) + return 0; // No more data, possibly because client disconnected. + + return cbRead; +} + +void TPipe::write(const uint8_t* buf, uint32_t len) { + if (!isOpen()) + throw TTransportException(TTransportException::NOT_OPEN, "Called write on non-open pipe"); + impl_->write(buf, len); +} + +void pipe_write(HANDLE pipe, const uint8_t* buf, uint32_t len) { + DWORD cbWritten; + int fSuccess = WriteFile(pipe, // pipe handle + buf, // message + len, // message length + &cbWritten, // bytes written + NULL); // not overlapped + + if (!fSuccess) + throw TTransportException(TTransportException::NOT_OPEN, "Write to pipe failed"); +} + +//--------------------------------------------------------- +// Accessors +//--------------------------------------------------------- + +std::string TPipe::getPipename() { + return pipename_; +} + +void TPipe::setPipename(const std::string& pipename) { + if (pipename.find("\\\\") == std::string::npos) + pipename_ = "\\\\.\\pipe\\" + pipename; + else + pipename_ = pipename; +} + +HANDLE TPipe::getPipeHandle() { + if (impl_) + return impl_->getPipeHandle(); + return INVALID_HANDLE_VALUE; +} + +void TPipe::setPipeHandle(HANDLE pipehandle) { + if (isAnonymous_) + impl_->setPipeHandle(pipehandle); + else + { + TAutoHandle pipe(pipehandle); + impl_.reset(new TNamedPipeImpl(pipe)); + } +} + +HANDLE TPipe::getWrtPipeHandle() { + if (impl_) + return impl_->getWrtPipeHandle(); + return INVALID_HANDLE_VALUE; +} + +void TPipe::setWrtPipeHandle(HANDLE pipehandle) { + if (impl_) + impl_->setWrtPipeHandle(pipehandle); +} + +HANDLE TPipe::getNativeWaitHandle() { + if (impl_) + return impl_->getNativeWaitHandle(); + return INVALID_HANDLE_VALUE; +} + +long TPipe::getConnTimeout() { + return TimeoutSeconds_; +} + +void TPipe::setConnTimeout(long seconds) { + TimeoutSeconds_ = seconds; +} + +#endif //_WIN32 +} +} +} // apache::thrift::transport diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TPipe.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TPipe.h new file mode 100644 index 000000000..ba149b109 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TPipe.h @@ -0,0 +1,113 @@ +/* + * 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. + */ + +#ifndef _THRIFT_TRANSPORT_TPIPE_H_ +#define _THRIFT_TRANSPORT_TPIPE_H_ 1 + +#include +#include +#ifndef _WIN32 +#include +#endif +#ifdef _WIN32 +#include +#endif +#include +#ifdef _WIN32 +#include +#endif + +namespace apache { +namespace thrift { +namespace transport { + +/** + * Windows Pipes implementation of the TTransport interface. + * Don't destroy a TPipe at global scope, as that will cause a thread join + * during DLLMain. That also means that client objects using TPipe shouldn't be at global + * scope. + */ +#ifdef _WIN32 +class TPipeImpl; + +class TPipe : public TVirtualTransport { +public: + // Constructs a new pipe object. + TPipe(); + // Named pipe constructors - + explicit TPipe(HANDLE Pipe); // HANDLE is a void* + explicit TPipe(TAutoHandle& Pipe); // this ctor will clear out / move from Pipe + // need a const char * overload so string literals don't go to the HANDLE overload + explicit TPipe(const char* pipename); + explicit TPipe(const std::string& pipename); + // Anonymous pipe - + TPipe(HANDLE PipeRd, HANDLE PipeWrt); + + // Destroys the pipe object, closing it if necessary. + virtual ~TPipe(); + + // Returns whether the pipe is open & valid. + bool isOpen() const override; + + // Checks whether more data is available in the pipe. + bool peek() override; + + // Creates and opens the named/anonymous pipe. + void open() override; + + // Shuts down communications on the pipe. + void close() override; + + // Reads from the pipe. + virtual uint32_t read(uint8_t* buf, uint32_t len); + + // Writes to the pipe. + virtual void write(const uint8_t* buf, uint32_t len); + + // Accessors + std::string getPipename(); + void setPipename(const std::string& pipename); + HANDLE getPipeHandle(); // doubles as the read handle for anon pipe + void setPipeHandle(HANDLE pipehandle); + HANDLE getWrtPipeHandle(); + void setWrtPipeHandle(HANDLE pipehandle); + long getConnTimeout(); + void setConnTimeout(long seconds); + + // this function is intended to be used in generic / template situations, + // so its name needs to be the same as TPipeServer's + HANDLE getNativeWaitHandle(); + +private: + std::shared_ptr impl_; + + std::string pipename_; + + long TimeoutSeconds_; + bool isAnonymous_; +}; + +#else +typedef TSocket TPipe; +#endif +} +} +} // apache::thrift::transport + +#endif // #ifndef _THRIFT_TRANSPORT_TPIPE_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TPipeServer.cpp b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TPipeServer.cpp new file mode 100644 index 000000000..47d882251 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TPipeServer.cpp @@ -0,0 +1,481 @@ +/* + * 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. + */ + +#include +#include + +#include +#include +#include + +#ifdef _WIN32 +#include +#include +#include +#endif //_WIN32 + +namespace apache { +namespace thrift { +namespace transport { + +#ifdef _WIN32 + +using std::shared_ptr; + +class TPipeServerImpl : boost::noncopyable { +public: + TPipeServerImpl() {} + virtual ~TPipeServerImpl() {} + virtual void interrupt() = 0; + virtual std::shared_ptr acceptImpl() = 0; + + virtual HANDLE getPipeHandle() = 0; + virtual HANDLE getWrtPipeHandle() = 0; + virtual HANDLE getClientRdPipeHandle() = 0; + virtual HANDLE getClientWrtPipeHandle() = 0; + virtual HANDLE getNativeWaitHandle() { return NULL; } +}; + +class TAnonPipeServer : public TPipeServerImpl { +public: + TAnonPipeServer() { + // The anonymous pipe needs to be created first so that the server can + // pass the handles on to the client before the serve (acceptImpl) + // blocking call. + if (!createAnonPipe()) { + GlobalOutput.perror("TPipeServer Create(Anon)Pipe failed, GLE=", GetLastError()); + throw TTransportException(TTransportException::NOT_OPEN, + " TPipeServer Create(Anon)Pipe failed"); + } + } + + virtual ~TAnonPipeServer() { + PipeR_.reset(); + PipeW_.reset(); + ClientAnonRead_.reset(); + ClientAnonWrite_.reset(); + } + + virtual void interrupt() {} // not currently implemented + + virtual std::shared_ptr acceptImpl(); + + virtual HANDLE getPipeHandle() { return PipeR_.h; } + virtual HANDLE getWrtPipeHandle() { return PipeW_.h; } + virtual HANDLE getClientRdPipeHandle() { return ClientAnonRead_.h; } + virtual HANDLE getClientWrtPipeHandle() { return ClientAnonWrite_.h; } + +private: + bool createAnonPipe(); + + TAutoHandle PipeR_; // Anonymous Pipe (R) + TAutoHandle PipeW_; // Anonymous Pipe (W) + + // Client side anonymous pipe handles + //? Do we need duplicates to send to client? + TAutoHandle ClientAnonRead_; + TAutoHandle ClientAnonWrite_; +}; + +class TNamedPipeServer : public TPipeServerImpl { +public: + TNamedPipeServer(const std::string& pipename, uint32_t bufsize, uint32_t maxconnections) + : stopping_(false), pipename_(pipename), bufsize_(bufsize), maxconns_(maxconnections) + { + connectOverlap_.action = TOverlappedWorkItem::CONNECT; + cancelOverlap_.action = TOverlappedWorkItem::CANCELIO; + TAutoCrit lock(pipe_protect_); + initiateNamedConnect(lock); + } + virtual ~TNamedPipeServer() {} + + virtual void interrupt() { + TAutoCrit lock(pipe_protect_); + cached_client_.reset(); + if (Pipe_.h != INVALID_HANDLE_VALUE) { + stopping_ = true; + cancelOverlap_.h = Pipe_.h; + // This should wake up GetOverlappedResult + thread_->addWorkItem(&cancelOverlap_); + } + } + + virtual std::shared_ptr acceptImpl(); + + virtual HANDLE getPipeHandle() { return Pipe_.h; } + virtual HANDLE getWrtPipeHandle() { return INVALID_HANDLE_VALUE; } + virtual HANDLE getClientRdPipeHandle() { return INVALID_HANDLE_VALUE; } + virtual HANDLE getClientWrtPipeHandle() { return INVALID_HANDLE_VALUE; } + virtual HANDLE getNativeWaitHandle() { return listen_event_.h; } + +private: + bool createNamedPipe(const TAutoCrit &lockProof); + void initiateNamedConnect(const TAutoCrit &lockProof); + + TAutoOverlapThread thread_; + TOverlappedWorkItem connectOverlap_; + TOverlappedWorkItem cancelOverlap_; + + bool stopping_; + std::string pipename_; + uint32_t bufsize_; + uint32_t maxconns_; + TManualResetEvent listen_event_; + + TCriticalSection pipe_protect_; + // only read or write these variables underneath a locked pipe_protect_ + std::shared_ptr cached_client_; + TAutoHandle Pipe_; +}; + +HANDLE TPipeServer::getNativeWaitHandle() { + if (impl_) + return impl_->getNativeWaitHandle(); + return NULL; +} + +//---- Constructors ---- +TPipeServer::TPipeServer(const std::string& pipename, uint32_t bufsize) + : bufsize_(bufsize), isAnonymous_(false) { + setMaxConnections(TPIPE_SERVER_MAX_CONNS_DEFAULT); + setPipename(pipename); +} + +TPipeServer::TPipeServer(const std::string& pipename, uint32_t bufsize, uint32_t maxconnections) + : bufsize_(bufsize), isAnonymous_(false) { + setMaxConnections(maxconnections); + setPipename(pipename); +} + +TPipeServer::TPipeServer(const std::string& pipename) : bufsize_(1024), isAnonymous_(false) { + setMaxConnections(TPIPE_SERVER_MAX_CONNS_DEFAULT); + setPipename(pipename); +} + +TPipeServer::TPipeServer(int bufsize) : bufsize_(bufsize), isAnonymous_(true) { + setMaxConnections(1); + impl_.reset(new TAnonPipeServer); +} + +TPipeServer::TPipeServer() : bufsize_(1024), isAnonymous_(true) { + setMaxConnections(1); + impl_.reset(new TAnonPipeServer); +} + +//---- Destructor ---- +TPipeServer::~TPipeServer() {} + +//--------------------------------------------------------- +// Transport callbacks +//--------------------------------------------------------- +void TPipeServer::listen() { + if (isAnonymous_) + return; + impl_.reset(new TNamedPipeServer(pipename_, bufsize_, maxconns_)); +} + +shared_ptr TPipeServer::acceptImpl() { + return impl_->acceptImpl(); +} + +shared_ptr TAnonPipeServer::acceptImpl() { + // This 0-byte read serves merely as a blocking call. + byte buf; + DWORD br; + int fSuccess = ReadFile(PipeR_.h, // pipe handle + &buf, // buffer to receive reply + 0, // size of buffer + &br, // number of bytes read + NULL); // not overlapped + + if (!fSuccess && GetLastError() != ERROR_MORE_DATA) { + GlobalOutput.perror("TPipeServer unable to initiate pipe comms, GLE=", GetLastError()); + throw TTransportException(TTransportException::NOT_OPEN, + " TPipeServer unable to initiate pipe comms"); + } + shared_ptr client(new TPipe(PipeR_.h, PipeW_.h)); + return client; +} + +void TNamedPipeServer::initiateNamedConnect(const TAutoCrit &lockProof) { + if (stopping_) + return; + if (!createNamedPipe(lockProof)) { + GlobalOutput.perror("TPipeServer CreateNamedPipe failed, GLE=", GetLastError()); + throw TTransportException(TTransportException::NOT_OPEN, " TPipeServer CreateNamedPipe failed"); + } + + // The prior connection has been handled, so close the gate + ResetEvent(listen_event_.h); + connectOverlap_.reset(NULL, 0, listen_event_.h); + connectOverlap_.h = Pipe_.h; + thread_->addWorkItem(&connectOverlap_); + + // Wait for the client to connect; if it succeeds, the + // function returns a nonzero value. If the function returns + // zero, GetLastError should return ERROR_PIPE_CONNECTED. + if (connectOverlap_.success) { + GlobalOutput.printf("Client connected."); + cached_client_.reset(new TPipe(Pipe_)); + // make sure people know that a connection is ready + SetEvent(listen_event_.h); + return; + } + + DWORD dwErr = connectOverlap_.last_error; + switch (dwErr) { + case ERROR_PIPE_CONNECTED: + GlobalOutput.printf("Client connected."); + cached_client_.reset(new TPipe(Pipe_)); + // make sure people know that a connection is ready + SetEvent(listen_event_.h); + return; + case ERROR_IO_PENDING: + return; // acceptImpl will do the appropriate WaitForMultipleObjects + default: + GlobalOutput.perror("TPipeServer ConnectNamedPipe failed, GLE=", dwErr); + throw TTransportException(TTransportException::NOT_OPEN, + " TPipeServer ConnectNamedPipe failed"); + } +} + +shared_ptr TNamedPipeServer::acceptImpl() { + { + TAutoCrit lock(pipe_protect_); + if (cached_client_.get() != NULL) { + shared_ptr client; + // zero out cached_client, since we are about to return it. + client.swap(cached_client_); + + // kick off the next connection before returning + initiateNamedConnect(lock); + return client; // success! + } + } + + if (Pipe_.h == INVALID_HANDLE_VALUE) { + throw TTransportException(TTransportException::NOT_OPEN, + "TNamedPipeServer: someone called accept on a closed pipe server"); + } + + DWORD dwDummy = 0; + + // For the most part, Pipe_ should be protected with pipe_protect_. We can't + // reasonably do that here though without breaking interruptability. However, + // this should be safe, though I'm not happy about it. We only need to ensure + // that no one writes / modifies Pipe_.h while we are reading it. Well, the + // only two things that should be modifying Pipe_ are acceptImpl, the + // functions it calls, and the destructor. Those things shouldn't be run + // concurrently anyway. So this call is 'really' just a read that may happen + // concurrently with interrupt, and that should be fine. + if (GetOverlappedResult(Pipe_.h, &connectOverlap_.overlap, &dwDummy, TRUE)) { + TAutoCrit lock(pipe_protect_); + GlobalOutput.printf("Client connected."); + shared_ptr client(new TPipe(Pipe_)); + // kick off the next connection before returning + initiateNamedConnect(lock); + return client; // success! + } + // if we got here, then we are in an error / shutdown case + DWORD gle = GetLastError(); // save error before doing cleanup + GlobalOutput.perror("TPipeServer ConnectNamedPipe GLE=", gle); + if(gle == ERROR_OPERATION_ABORTED) { + TAutoCrit lock(pipe_protect_); // Needed to insure concurrent thread to be out of interrupt. + throw TTransportException(TTransportException::INTERRUPTED, "TPipeServer: server interupted"); + } + throw TTransportException(TTransportException::NOT_OPEN, "TPipeServer: client connection failed"); +} + +void TPipeServer::interrupt() { + if (impl_) + impl_->interrupt(); +} + +void TPipeServer::close() { + impl_.reset(); +} + +bool TNamedPipeServer::createNamedPipe(const TAutoCrit & /*lockProof*/) { + + // Windows - set security to allow non-elevated apps + // to access pipes created by elevated apps. + SID_IDENTIFIER_AUTHORITY SIDAuthWorld = SECURITY_WORLD_SID_AUTHORITY; + PSID everyone_sid = NULL; + AllocateAndInitializeSid( + &SIDAuthWorld, 1, SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0, &everyone_sid); + + EXPLICIT_ACCESS ea; + ZeroMemory(&ea, sizeof(EXPLICIT_ACCESS)); + ea.grfAccessPermissions = SPECIFIC_RIGHTS_ALL | STANDARD_RIGHTS_ALL; + ea.grfAccessMode = SET_ACCESS; + ea.grfInheritance = NO_INHERITANCE; + ea.Trustee.TrusteeForm = TRUSTEE_IS_SID; + ea.Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP; + ea.Trustee.ptstrName = static_cast(everyone_sid); + + PACL acl = NULL; + SetEntriesInAcl(1, &ea, NULL, &acl); + + PSECURITY_DESCRIPTOR sd = (PSECURITY_DESCRIPTOR)LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH); + if (!InitializeSecurityDescriptor(sd, SECURITY_DESCRIPTOR_REVISION)) { + auto lastError = GetLastError(); + LocalFree(sd); + LocalFree(acl); + GlobalOutput.perror("TPipeServer::InitializeSecurityDescriptor() GLE=", lastError); + throw TTransportException(TTransportException::NOT_OPEN, "InitializeSecurityDescriptor() failed", + lastError); + } + if (!SetSecurityDescriptorDacl(sd, TRUE, acl, FALSE)) { + auto lastError = GetLastError(); + LocalFree(sd); + LocalFree(acl); + GlobalOutput.perror("TPipeServer::SetSecurityDescriptorDacl() GLE=", lastError); + throw TTransportException(TTransportException::NOT_OPEN, + "SetSecurityDescriptorDacl() failed", lastError); + } + + SECURITY_ATTRIBUTES sa; + sa.nLength = sizeof(SECURITY_ATTRIBUTES); + sa.lpSecurityDescriptor = sd; + sa.bInheritHandle = FALSE; + + // Create an instance of the named pipe + TAutoHandle hPipe(CreateNamedPipeA(pipename_.c_str(), // pipe name + PIPE_ACCESS_DUPLEX | // read/write access + FILE_FLAG_OVERLAPPED, // async mode + PIPE_TYPE_BYTE | // byte type pipe + PIPE_READMODE_BYTE, // byte read mode + maxconns_, // max. instances + bufsize_, // output buffer size + bufsize_, // input buffer size + 0, // client time-out + &sa)); // security attributes + + auto lastError = GetLastError(); + LocalFree(sd); + LocalFree(acl); + FreeSid(everyone_sid); + + if (hPipe.h == INVALID_HANDLE_VALUE) { + Pipe_.reset(); + GlobalOutput.perror("TPipeServer::TCreateNamedPipe() GLE=", lastError); + throw TTransportException(TTransportException::NOT_OPEN, + "TCreateNamedPipe() failed", + lastError); + } + + Pipe_.reset(hPipe.release()); + return true; +} + +bool TAnonPipeServer::createAnonPipe() { + SECURITY_ATTRIBUTES sa; + SECURITY_DESCRIPTOR sd; // security information for pipes + + if (!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION)) + { + GlobalOutput.perror("TPipeServer InitializeSecurityDescriptor (anon) failed, GLE=", GetLastError()); + return false; + } + if (!SetSecurityDescriptorDacl(&sd, true, NULL, false)) + { + GlobalOutput.perror("TPipeServer SetSecurityDescriptorDacl (anon) failed, GLE=", + GetLastError()); + return false; + } + sa.lpSecurityDescriptor = &sd; + sa.nLength = sizeof(SECURITY_ATTRIBUTES); + sa.bInheritHandle = true; // allow passing handle to child + + HANDLE ClientAnonReadH, PipeW_H, ClientAnonWriteH, Pipe_H; + if (!CreatePipe(&ClientAnonReadH, &PipeW_H, &sa, 0)) // create stdin pipe + { + GlobalOutput.perror("TPipeServer CreatePipe (anon) failed, GLE=", GetLastError()); + return false; + } + if (!CreatePipe(&Pipe_H, &ClientAnonWriteH, &sa, 0)) // create stdout pipe + { + GlobalOutput.perror("TPipeServer CreatePipe (anon) failed, GLE=", GetLastError()); + CloseHandle(ClientAnonReadH); + CloseHandle(PipeW_H); + return false; + } + + ClientAnonRead_.reset(ClientAnonReadH); + ClientAnonWrite_.reset(ClientAnonWriteH); + PipeR_.reset(Pipe_H); + PipeW_.reset(PipeW_H); + + return true; +} + +//--------------------------------------------------------- +// Accessors +//--------------------------------------------------------- +std::string TPipeServer::getPipename() { + return pipename_; +} + +void TPipeServer::setPipename(const std::string& pipename) { + if (pipename.find("\\\\") == std::string::npos) + pipename_ = "\\\\.\\pipe\\" + pipename; + else + pipename_ = pipename; +} + +int TPipeServer::getBufferSize() { + return bufsize_; +} +void TPipeServer::setBufferSize(int bufsize) { + bufsize_ = bufsize; +} + +HANDLE TPipeServer::getPipeHandle() { + return impl_ ? impl_->getPipeHandle() : INVALID_HANDLE_VALUE; +} +HANDLE TPipeServer::getWrtPipeHandle() { + return impl_ ? impl_->getWrtPipeHandle() : INVALID_HANDLE_VALUE; +} +HANDLE TPipeServer::getClientRdPipeHandle() { + return impl_ ? impl_->getClientRdPipeHandle() : INVALID_HANDLE_VALUE; +} +HANDLE TPipeServer::getClientWrtPipeHandle() { + return impl_ ? impl_->getClientWrtPipeHandle() : INVALID_HANDLE_VALUE; +} + +bool TPipeServer::getAnonymous() { + return isAnonymous_; +} +void TPipeServer::setAnonymous(bool anon) { + isAnonymous_ = anon; +} + +void TPipeServer::setMaxConnections(uint32_t maxconnections) { + if (maxconnections == 0) + maxconns_ = 1; + else if (maxconnections > PIPE_UNLIMITED_INSTANCES) + maxconns_ = PIPE_UNLIMITED_INSTANCES; + else + maxconns_ = maxconnections; +} + +#endif //_WIN32 +} +} +} // apache::thrift::transport diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TPipeServer.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TPipeServer.h new file mode 100644 index 000000000..871b6afab --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TPipeServer.h @@ -0,0 +1,103 @@ +/* + * 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. + */ + +#ifndef _THRIFT_TRANSPORT_TSERVERWINPIPES_H_ +#define _THRIFT_TRANSPORT_TSERVERWINPIPES_H_ 1 + +#include +#include +#ifndef _WIN32 +#include +#endif +#ifdef _WIN32 +#include +#endif + +#define TPIPE_SERVER_MAX_CONNS_DEFAULT PIPE_UNLIMITED_INSTANCES + +namespace apache { +namespace thrift { +namespace transport { + +/** + * Windows Pipes implementation of TServerTransport. + * Don't destroy a TPipeServer at global scope, as that will cause a thread join + * during DLLMain. That also means that TServer's using TPipeServer shouldn't be at global + * scope. + */ +#ifdef _WIN32 +class TPipeServerImpl; +class TPipe; + +class TPipeServer : public TServerTransport { +public: + // Constructors + // Named Pipe - + TPipeServer(const std::string& pipename, uint32_t bufsize); + TPipeServer(const std::string& pipename, uint32_t bufsize, uint32_t maxconnections); + TPipeServer(const std::string& pipename); + // Anonymous pipe - + TPipeServer(int bufsize); + TPipeServer(); + + // Destructor + virtual ~TPipeServer(); + + // Standard transport callbacks + void interrupt() override; + void close() override; + void listen() override; + + // Accessors + std::string getPipename(); + void setPipename(const std::string& pipename); + int getBufferSize(); + void setBufferSize(int bufsize); + HANDLE getPipeHandle(); // Named Pipe R/W -or- Anonymous pipe Read handle + HANDLE getWrtPipeHandle(); + HANDLE getClientRdPipeHandle(); + HANDLE getClientWrtPipeHandle(); + bool getAnonymous(); + void setAnonymous(bool anon); + void setMaxConnections(uint32_t maxconnections); + + // this function is intended to be used in generic / template situations, + // so its name needs to be the same as TPipe's + HANDLE getNativeWaitHandle(); + +protected: + virtual std::shared_ptr acceptImpl(); + +private: + std::shared_ptr impl_; + + std::string pipename_; + uint32_t bufsize_; + uint32_t maxconns_; + bool isAnonymous_; +}; +#else //_WIN32 +//*NIX named pipe implementation uses domain socket +typedef TServerSocket TPipeServer; +#endif +} +} +} // apache::thrift::transport + +#endif // #ifndef _THRIFT_TRANSPORT_TSERVERWINPIPES_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TSSLServerSocket.cpp b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TSSLServerSocket.cpp new file mode 100644 index 000000000..b20c17408 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TSSLServerSocket.cpp @@ -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. + */ + +#include +#include +#include + +namespace apache { +namespace thrift { +namespace transport { + +/** + * SSL server socket implementation. + */ +TSSLServerSocket::TSSLServerSocket(int port, std::shared_ptr factory) + : TServerSocket(port), factory_(factory) { + factory_->server(true); +} + +TSSLServerSocket::TSSLServerSocket(const std::string& address, + int port, + std::shared_ptr factory) + : TServerSocket(address, port), factory_(factory) { + factory_->server(true); +} + +TSSLServerSocket::TSSLServerSocket(int port, + int sendTimeout, + int recvTimeout, + std::shared_ptr factory) + : TServerSocket(port, sendTimeout, recvTimeout), factory_(factory) { + factory_->server(true); +} + +std::shared_ptr TSSLServerSocket::createSocket(THRIFT_SOCKET client) { + if (interruptableChildren_) { + return factory_->createSocket(client, pChildInterruptSockReader_); + + } else { + return factory_->createSocket(client); + } +} +} +} +} diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TSSLServerSocket.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TSSLServerSocket.h new file mode 100644 index 000000000..44df43276 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TSSLServerSocket.h @@ -0,0 +1,76 @@ +/* + * 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. + */ + +#ifndef _THRIFT_TRANSPORT_TSSLSERVERSOCKET_H_ +#define _THRIFT_TRANSPORT_TSSLSERVERSOCKET_H_ 1 + +#include + +namespace apache { +namespace thrift { +namespace transport { + +class TSSLSocketFactory; + +/** + * Server socket that accepts SSL connections. + */ +class TSSLServerSocket : public TServerSocket { +public: + /** + * Constructor. Binds to all interfaces. + * + * @param port Listening port + * @param factory SSL socket factory implementation + */ + TSSLServerSocket(int port, std::shared_ptr factory); + + /** + * Constructor. Binds to the specified address. + * + * @param address Address to bind to + * @param port Listening port + * @param factory SSL socket factory implementation + */ + TSSLServerSocket(const std::string& address, + int port, + std::shared_ptr factory); + + /** + * Constructor. Binds to all interfaces. + * + * @param port Listening port + * @param sendTimeout Socket send timeout + * @param recvTimeout Socket receive timeout + * @param factory SSL socket factory implementation + */ + TSSLServerSocket(int port, + int sendTimeout, + int recvTimeout, + std::shared_ptr factory); + +protected: + std::shared_ptr createSocket(THRIFT_SOCKET socket) override; + std::shared_ptr factory_; +}; +} +} +} + +#endif diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TSSLSocket.cpp b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TSSLSocket.cpp new file mode 100644 index 000000000..b413002f1 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TSSLSocket.cpp @@ -0,0 +1,1120 @@ +/* + * 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. + */ + +#include + +#include +#include +#include +#include +#ifdef HAVE_ARPA_INET_H +#include +#endif +#include +#ifdef HAVE_SYS_SOCKET_H +#include +#endif +#ifdef HAVE_SYS_POLL_H +#include +#endif +#ifdef HAVE_FCNTL_H +#include +#endif + +#define OPENSSL_VERSION_NO_THREAD_ID_BEFORE 0x10000000L +#define OPENSSL_ENGINE_CLEANUP_REQUIRED_BEFORE 0x10100000L + +#include +#include +#if (OPENSSL_VERSION_NUMBER < OPENSSL_ENGINE_CLEANUP_REQUIRED_BEFORE) +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace apache::thrift::concurrency; +using std::string; + +struct CRYPTO_dynlock_value { + Mutex mutex; +}; + +namespace apache { +namespace thrift { +namespace transport { + +// OpenSSL initialization/cleanup + +static bool openSSLInitialized = false; +static boost::shared_array mutexes; + +static void callbackLocking(int mode, int n, const char*, int) { + if (mode & CRYPTO_LOCK) { + // assertion of (px != 0) here typically means that a TSSLSocket's lifetime + // exceeded the lifetime of the TSSLSocketFactory that created it, and the + // TSSLSocketFactory already ran cleanupOpenSSL(), which deleted "mutexes". + mutexes[n].lock(); + } else { + mutexes[n].unlock(); + } +} + +#if (OPENSSL_VERSION_NUMBER < OPENSSL_VERSION_NO_THREAD_ID_BEFORE) +static unsigned long callbackThreadID() { +#ifdef _WIN32 + return (unsigned long)GetCurrentThreadId(); +#else + return (unsigned long)pthread_self(); +#endif +} +#endif + +static CRYPTO_dynlock_value* dyn_create(const char*, int) { + return new CRYPTO_dynlock_value; +} + +static void dyn_lock(int mode, struct CRYPTO_dynlock_value* lock, const char*, int) { + if (lock != nullptr) { + if (mode & CRYPTO_LOCK) { + lock->mutex.lock(); + } else { + lock->mutex.unlock(); + } + } +} + +static void dyn_destroy(struct CRYPTO_dynlock_value* lock, const char*, int) { + delete lock; +} + +void initializeOpenSSL() { + if (openSSLInitialized) { + return; + } + openSSLInitialized = true; + SSL_library_init(); + SSL_load_error_strings(); + ERR_load_crypto_strings(); + + // static locking + // newer versions of OpenSSL changed CRYPTO_num_locks - see THRIFT-3878 +#ifdef CRYPTO_num_locks + mutexes = boost::shared_array(new Mutex[CRYPTO_num_locks()]); +#else + mutexes = boost::shared_array(new Mutex[ ::CRYPTO_num_locks()]); +#endif + +#if (OPENSSL_VERSION_NUMBER < OPENSSL_VERSION_NO_THREAD_ID_BEFORE) + CRYPTO_set_id_callback(callbackThreadID); +#endif + + CRYPTO_set_locking_callback(callbackLocking); + + // dynamic locking + CRYPTO_set_dynlock_create_callback(dyn_create); + CRYPTO_set_dynlock_lock_callback(dyn_lock); + CRYPTO_set_dynlock_destroy_callback(dyn_destroy); +} + +void cleanupOpenSSL() { + if (!openSSLInitialized) { + return; + } + openSSLInitialized = false; + + // https://wiki.openssl.org/index.php/Library_Initialization#Cleanup + // we purposefully do NOT call FIPS_mode_set(0) and leave it up to the enclosing application to manage FIPS entirely +#if (OPENSSL_VERSION_NUMBER < OPENSSL_ENGINE_CLEANUP_REQUIRED_BEFORE) + ENGINE_cleanup(); // https://www.openssl.org/docs/man1.1.0/crypto/ENGINE_cleanup.html - cleanup call is needed before 1.1.0 +#endif + CONF_modules_unload(1); + EVP_cleanup(); + CRYPTO_cleanup_all_ex_data(); + ERR_remove_state(0); + ERR_free_strings(); + + mutexes.reset(); +} + +static void buildErrors(string& message, int errno_copy = 0, int sslerrno = 0); +static bool matchName(const char* host, const char* pattern, int size); +static char uppercase(char c); + +// SSLContext implementation +SSLContext::SSLContext(const SSLProtocol& protocol) { + if (protocol == SSLTLS) { + ctx_ = SSL_CTX_new(SSLv23_method()); +#ifndef OPENSSL_NO_SSL3 + } else if (protocol == SSLv3) { + ctx_ = SSL_CTX_new(SSLv3_method()); +#endif + } else if (protocol == TLSv1_0) { + ctx_ = SSL_CTX_new(TLSv1_method()); + } else if (protocol == TLSv1_1) { + ctx_ = SSL_CTX_new(TLSv1_1_method()); + } else if (protocol == TLSv1_2) { + ctx_ = SSL_CTX_new(TLSv1_2_method()); + } else { + /// UNKNOWN PROTOCOL! + throw TSSLException("SSL_CTX_new: Unknown protocol"); + } + + if (ctx_ == nullptr) { + string errors; + buildErrors(errors); + throw TSSLException("SSL_CTX_new: " + errors); + } + SSL_CTX_set_mode(ctx_, SSL_MODE_AUTO_RETRY); + + // Disable horribly insecure SSLv2 and SSLv3 protocols but allow a handshake + // with older clients so they get a graceful denial. + if (protocol == SSLTLS) { + SSL_CTX_set_options(ctx_, SSL_OP_NO_SSLv2); + SSL_CTX_set_options(ctx_, SSL_OP_NO_SSLv3); // THRIFT-3164 + } +} + +SSLContext::~SSLContext() { + if (ctx_ != nullptr) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } +} + +SSL* SSLContext::createSSL() { + SSL* ssl = SSL_new(ctx_); + if (ssl == nullptr) { + string errors; + buildErrors(errors); + throw TSSLException("SSL_new: " + errors); + } + return ssl; +} + +// TSSLSocket implementation +TSSLSocket::TSSLSocket(std::shared_ptr ctx) + : TSocket(), server_(false), ssl_(nullptr), ctx_(ctx) { + init(); +} + +TSSLSocket::TSSLSocket(std::shared_ptr ctx, std::shared_ptr interruptListener) + : TSocket(), server_(false), ssl_(nullptr), ctx_(ctx) { + init(); + interruptListener_ = interruptListener; +} + +TSSLSocket::TSSLSocket(std::shared_ptr ctx, THRIFT_SOCKET socket) + : TSocket(socket), server_(false), ssl_(nullptr), ctx_(ctx) { + init(); +} + +TSSLSocket::TSSLSocket(std::shared_ptr ctx, THRIFT_SOCKET socket, std::shared_ptr interruptListener) + : TSocket(socket, interruptListener), server_(false), ssl_(nullptr), ctx_(ctx) { + init(); +} + +TSSLSocket::TSSLSocket(std::shared_ptr ctx, string host, int port) + : TSocket(host, port), server_(false), ssl_(nullptr), ctx_(ctx) { + init(); +} + +TSSLSocket::TSSLSocket(std::shared_ptr ctx, string host, int port, std::shared_ptr interruptListener) + : TSocket(host, port), server_(false), ssl_(nullptr), ctx_(ctx) { + init(); + interruptListener_ = interruptListener; +} + +TSSLSocket::~TSSLSocket() { + close(); +} + +bool TSSLSocket::hasPendingDataToRead() { + if (!isOpen()) { + return false; + } + initializeHandshake(); + if (!checkHandshake()) + throw TSSLException("TSSLSocket::hasPendingDataToRead: Handshake is not completed"); + // data may be available in SSL buffers (note: SSL_pending does not have a failure mode) + return SSL_pending(ssl_) > 0 || TSocket::hasPendingDataToRead(); +} + +void TSSLSocket::init() { + handshakeCompleted_ = false; + readRetryCount_ = 0; + eventSafe_ = false; +} + +bool TSSLSocket::isOpen() const { + if (ssl_ == nullptr || !TSocket::isOpen()) { + return false; + } + int shutdown = SSL_get_shutdown(ssl_); + // "!!" is squelching C4800 "forcing bool -> true or false" performance warning + bool shutdownReceived = !!(shutdown & SSL_RECEIVED_SHUTDOWN); + bool shutdownSent = !!(shutdown & SSL_SENT_SHUTDOWN); + if (shutdownReceived && shutdownSent) { + return false; + } + return true; +} + +/* + * Note: This method is not libevent safe. +*/ +bool TSSLSocket::peek() { + if (!isOpen()) { + return false; + } + initializeHandshake(); + if (!checkHandshake()) + throw TSSLException("SSL_peek: Handshake is not completed"); + int rc; + do { + uint8_t byte; + rc = SSL_peek(ssl_, &byte, 1); + if (rc < 0) { + int errno_copy = THRIFT_GET_SOCKET_ERROR; + int error = SSL_get_error(ssl_, rc); + switch (error) { + case SSL_ERROR_SYSCALL: + if ((errno_copy != THRIFT_EINTR) + && (errno_copy != THRIFT_EAGAIN)) { + break; + } + // fallthrough + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + // in the case of SSL_ERROR_SYSCALL we want to wait for an read event again + waitForEvent(error != SSL_ERROR_WANT_WRITE); + continue; + default:;// do nothing + } + string errors; + buildErrors(errors, errno_copy, error); + throw TSSLException("SSL_peek: " + errors); + } else if (rc == 0) { + ERR_clear_error(); + break; + } else { + break; + } + } while (true); + return (rc > 0); +} + +void TSSLSocket::open() { + if (isOpen() || server()) { + throw TTransportException(TTransportException::BAD_ARGS); + } + TSocket::open(); +} + +/* + * Note: This method is not libevent safe. +*/ +void TSSLSocket::close() { + if (ssl_ != nullptr) { + try { + int rc; + int errno_copy = 0; + int error = 0; + + do { + rc = SSL_shutdown(ssl_); + if (rc <= 0) { + errno_copy = THRIFT_GET_SOCKET_ERROR; + error = SSL_get_error(ssl_, rc); + switch (error) { + case SSL_ERROR_SYSCALL: + if ((errno_copy != THRIFT_EINTR) + && (errno_copy != THRIFT_EAGAIN)) { + break; + } + // fallthrough + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + // in the case of SSL_ERROR_SYSCALL we want to wait for an write/read event again + waitForEvent(error == SSL_ERROR_WANT_READ); + rc = 2; + default:;// do nothing + } + } + } while (rc == 2); + + if (rc < 0) { + string errors; + buildErrors(errors, errno_copy, error); + GlobalOutput(("SSL_shutdown: " + errors).c_str()); + } + } catch (TTransportException& te) { + // Don't emit an exception because this method is called by the + // destructor. There's also not much that a user can do to recover, so + // just clean up as much as possible without throwing, similar to the rc + // < 0 case above. + GlobalOutput.printf("SSL_shutdown: %s", te.what()); + } + SSL_free(ssl_); + ssl_ = nullptr; + handshakeCompleted_ = false; + ERR_remove_state(0); + } + TSocket::close(); +} + +/* + * Returns number of bytes read in SSL Socket. + * If eventSafe is set, and it may returns 0 bytes then read method + * needs to be called again until it is successfull or it throws + * exception incase of failure. +*/ +uint32_t TSSLSocket::read(uint8_t* buf, uint32_t len) { + initializeHandshake(); + if (!checkHandshake()) + throw TTransportException(TTransportException::UNKNOWN, "retry again"); + int32_t bytes = 0; + while (readRetryCount_ < maxRecvRetries_) { + bytes = SSL_read(ssl_, buf, len); + int32_t errno_copy = THRIFT_GET_SOCKET_ERROR; + int32_t error = SSL_get_error(ssl_, bytes); + readRetryCount_++; + if (error == SSL_ERROR_NONE) { + readRetryCount_ = 0; + break; + } + unsigned int waitEventReturn; + bool breakout = false; + switch (error) { + case SSL_ERROR_ZERO_RETURN: + throw TTransportException(TTransportException::END_OF_FILE, "client disconnected"); + + case SSL_ERROR_SYSCALL: + if (errno_copy == 0 && ERR_peek_error() == 0) { + breakout = true; + break; + } + if ((errno_copy != THRIFT_EINTR) + && (errno_copy != THRIFT_EAGAIN)) { + break; + } + if (readRetryCount_ >= maxRecvRetries_) { + // THRIFT_EINTR needs to be handled manually and we can tolerate + // a certain number + break; + } + // fallthrough + + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + if (isLibeventSafe()) { + if (readRetryCount_ < maxRecvRetries_) { + // THRIFT_EINTR needs to be handled manually and we can tolerate + // a certain number + throw TTransportException(TTransportException::UNKNOWN, "retry again"); + } + throw TTransportException(TTransportException::INTERNAL_ERROR, "too much recv retries"); + } + // in the case of SSL_ERROR_SYSCALL we want to wait for an read event again + else if ((waitEventReturn = waitForEvent(error != SSL_ERROR_WANT_WRITE)) == TSSL_EINTR ) { + // repeat operation + if (readRetryCount_ < maxRecvRetries_) { + // THRIFT_EINTR needs to be handled manually and we can tolerate + // a certain number + continue; + } + throw TTransportException(TTransportException::INTERNAL_ERROR, "too much recv retries"); + } + else if (waitEventReturn == TSSL_DATA) { + // in case of SSL and huge thrift packets, there may be a number of + // socket operations, before any data becomes available by SSL_read(). + // Therefore the number of retries should not be increased and + // the operation should be repeated. + readRetryCount_--; + continue; + } + throw TTransportException(TTransportException::INTERNAL_ERROR, "unkown waitForEvent return value"); + default:;// do nothing + } + if (breakout) { + break; + } + string errors; + buildErrors(errors, errno_copy, error); + throw TSSLException("SSL_read: " + errors); + } + return bytes; +} + +void TSSLSocket::write(const uint8_t* buf, uint32_t len) { + initializeHandshake(); + if (!checkHandshake()) + return; + // loop in case SSL_MODE_ENABLE_PARTIAL_WRITE is set in SSL_CTX. + uint32_t written = 0; + while (written < len) { + ERR_clear_error(); + int32_t bytes = SSL_write(ssl_, &buf[written], len - written); + if (bytes <= 0) { + int errno_copy = THRIFT_GET_SOCKET_ERROR; + int error = SSL_get_error(ssl_, bytes); + switch (error) { + case SSL_ERROR_SYSCALL: + if ((errno_copy != THRIFT_EINTR) + && (errno_copy != THRIFT_EAGAIN)) { + break; + } + // fallthrough + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + if (isLibeventSafe()) { + return; + } + else { + // in the case of SSL_ERROR_SYSCALL we want to wait for an write event again + waitForEvent(error == SSL_ERROR_WANT_READ); + continue; + } + default:;// do nothing + } + string errors; + buildErrors(errors, errno_copy, error); + throw TSSLException("SSL_write: " + errors); + } + written += bytes; + } +} + +/* + * Returns number of bytes written in SSL Socket. + * If eventSafe is set, and it may returns 0 bytes then write method + * needs to be called again until it is successfull or it throws + * exception incase of failure. +*/ +uint32_t TSSLSocket::write_partial(const uint8_t* buf, uint32_t len) { + initializeHandshake(); + if (!checkHandshake()) + return 0; + // loop in case SSL_MODE_ENABLE_PARTIAL_WRITE is set in SSL_CTX. + uint32_t written = 0; + while (written < len) { + ERR_clear_error(); + int32_t bytes = SSL_write(ssl_, &buf[written], len - written); + if (bytes <= 0) { + int errno_copy = THRIFT_GET_SOCKET_ERROR; + int error = SSL_get_error(ssl_, bytes); + switch (error) { + case SSL_ERROR_SYSCALL: + if ((errno_copy != THRIFT_EINTR) + && (errno_copy != THRIFT_EAGAIN)) { + break; + } + // fallthrough + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + if (isLibeventSafe()) { + return 0; + } + else { + // in the case of SSL_ERROR_SYSCALL we want to wait for an write event again + waitForEvent(error == SSL_ERROR_WANT_READ); + continue; + } + default:;// do nothing + } + string errors; + buildErrors(errors, errno_copy, error); + throw TSSLException("SSL_write: " + errors); + } + written += bytes; + } + return written; +} + +void TSSLSocket::flush() { + // Don't throw exception if not open. Thrift servers close socket twice. + if (ssl_ == nullptr) { + return; + } + initializeHandshake(); + if (!checkHandshake()) + throw TSSLException("BIO_flush: Handshake is not completed"); + BIO* bio = SSL_get_wbio(ssl_); + if (bio == nullptr) { + throw TSSLException("SSL_get_wbio returns NULL"); + } + if (BIO_flush(bio) != 1) { + int errno_copy = THRIFT_GET_SOCKET_ERROR; + string errors; + buildErrors(errors, errno_copy); + throw TSSLException("BIO_flush: " + errors); + } +} + +void TSSLSocket::initializeHandshakeParams() { + // set underlying socket to non-blocking + int flags; + if ((flags = THRIFT_FCNTL(socket_, THRIFT_F_GETFL, 0)) < 0 + || THRIFT_FCNTL(socket_, THRIFT_F_SETFL, flags | THRIFT_O_NONBLOCK) < 0) { + GlobalOutput.perror("thriftServerEventHandler: set THRIFT_O_NONBLOCK (THRIFT_FCNTL) ", + THRIFT_GET_SOCKET_ERROR); + ::THRIFT_CLOSESOCKET(socket_); + return; + } + ssl_ = ctx_->createSSL(); + + SSL_set_fd(ssl_, static_cast(socket_)); +} + +bool TSSLSocket::checkHandshake() { + return handshakeCompleted_; +} + +void TSSLSocket::initializeHandshake() { + if (!TSocket::isOpen()) { + throw TTransportException(TTransportException::NOT_OPEN); + } + if (checkHandshake()) { + return; + } + + if (ssl_ == nullptr) { + initializeHandshakeParams(); + } + + int rc; + int errno_copy = 0; + int error = 0; + if (server()) { + do { + rc = SSL_accept(ssl_); + if (rc <= 0) { + errno_copy = THRIFT_GET_SOCKET_ERROR; + error = SSL_get_error(ssl_, rc); + switch (error) { + case SSL_ERROR_SYSCALL: + if ((errno_copy != THRIFT_EINTR) + && (errno_copy != THRIFT_EAGAIN)) { + break; + } + // fallthrough + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + if (isLibeventSafe()) { + return; + } + else { + // repeat operation + // in the case of SSL_ERROR_SYSCALL we want to wait for an write/read event again + waitForEvent(error == SSL_ERROR_WANT_READ); + rc = 2; + } + default:;// do nothing + } + } + } while (rc == 2); + } else { + // OpenSSL < 0.9.8f does not have SSL_set_tlsext_host_name() + #if defined(SSL_set_tlsext_host_name) + // set the SNI hostname + SSL_set_tlsext_host_name(ssl_, getHost().c_str()); + #endif + do { + rc = SSL_connect(ssl_); + if (rc <= 0) { + errno_copy = THRIFT_GET_SOCKET_ERROR; + error = SSL_get_error(ssl_, rc); + switch (error) { + case SSL_ERROR_SYSCALL: + if ((errno_copy != THRIFT_EINTR) + && (errno_copy != THRIFT_EAGAIN)) { + break; + } + // fallthrough + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + if (isLibeventSafe()) { + return; + } + else { + // repeat operation + // in the case of SSL_ERROR_SYSCALL we want to wait for an write/read event again + waitForEvent(error == SSL_ERROR_WANT_READ); + rc = 2; + } + default:;// do nothing + } + } + } while (rc == 2); + } + if (rc <= 0) { + string fname(server() ? "SSL_accept" : "SSL_connect"); + string errors; + buildErrors(errors, errno_copy, error); + throw TSSLException(fname + ": " + errors); + } + authorize(); + handshakeCompleted_ = true; +} + +void TSSLSocket::authorize() { + int rc = SSL_get_verify_result(ssl_); + if (rc != X509_V_OK) { // verify authentication result + throw TSSLException(string("SSL_get_verify_result(), ") + X509_verify_cert_error_string(rc)); + } + + X509* cert = SSL_get_peer_certificate(ssl_); + if (cert == nullptr) { + // certificate is not present + if (SSL_get_verify_mode(ssl_) & SSL_VERIFY_FAIL_IF_NO_PEER_CERT) { + throw TSSLException("authorize: required certificate not present"); + } + // certificate was optional: didn't intend to authorize remote + if (server() && access_ != nullptr) { + throw TSSLException("authorize: certificate required for authorization"); + } + return; + } + // certificate is present + if (access_ == nullptr) { + X509_free(cert); + return; + } + // both certificate and access manager are present + + string host; + sockaddr_storage sa; + socklen_t saLength = sizeof(sa); + + if (getpeername(socket_, (sockaddr*)&sa, &saLength) != 0) { + sa.ss_family = AF_UNSPEC; + } + + AccessManager::Decision decision = access_->verify(sa); + + if (decision != AccessManager::SKIP) { + X509_free(cert); + if (decision != AccessManager::ALLOW) { + throw TSSLException("authorize: access denied based on remote IP"); + } + return; + } + + // extract subjectAlternativeName + auto* alternatives + = (STACK_OF(GENERAL_NAME)*)X509_get_ext_d2i(cert, NID_subject_alt_name, nullptr, nullptr); + if (alternatives != nullptr) { + const int count = sk_GENERAL_NAME_num(alternatives); + for (int i = 0; decision == AccessManager::SKIP && i < count; i++) { + const GENERAL_NAME* name = sk_GENERAL_NAME_value(alternatives, i); + if (name == nullptr) { + continue; + } + char* data = (char*)ASN1_STRING_data(name->d.ia5); + int length = ASN1_STRING_length(name->d.ia5); + switch (name->type) { + case GEN_DNS: + if (host.empty()) { + host = (server() ? getPeerHost() : getHost()); + } + decision = access_->verify(host, data, length); + break; + case GEN_IPADD: + decision = access_->verify(sa, data, length); + break; + } + } + sk_GENERAL_NAME_pop_free(alternatives, GENERAL_NAME_free); + } + + if (decision != AccessManager::SKIP) { + X509_free(cert); + if (decision != AccessManager::ALLOW) { + throw TSSLException("authorize: access denied"); + } + return; + } + + // extract commonName + X509_NAME* name = X509_get_subject_name(cert); + if (name != nullptr) { + X509_NAME_ENTRY* entry; + unsigned char* utf8; + int last = -1; + while (decision == AccessManager::SKIP) { + last = X509_NAME_get_index_by_NID(name, NID_commonName, last); + if (last == -1) + break; + entry = X509_NAME_get_entry(name, last); + if (entry == nullptr) + continue; + ASN1_STRING* common = X509_NAME_ENTRY_get_data(entry); + int size = ASN1_STRING_to_UTF8(&utf8, common); + if (host.empty()) { + host = (server() ? getPeerHost() : getHost()); + } + decision = access_->verify(host, (char*)utf8, size); + OPENSSL_free(utf8); + } + } + X509_free(cert); + if (decision != AccessManager::ALLOW) { + throw TSSLException("authorize: cannot authorize peer"); + } +} + +/* + * Note: This method is not libevent safe. +*/ +unsigned int TSSLSocket::waitForEvent(bool wantRead) { + int fdSocket; + BIO* bio; + + if (wantRead) { + bio = SSL_get_rbio(ssl_); + } else { + bio = SSL_get_wbio(ssl_); + } + + if (bio == nullptr) { + throw TSSLException("SSL_get_?bio returned NULL"); + } + + if (BIO_get_fd(bio, &fdSocket) <= 0) { + throw TSSLException("BIO_get_fd failed"); + } + + struct THRIFT_POLLFD fds[2]; + memset(fds, 0, sizeof(fds)); + fds[0].fd = fdSocket; + // use POLLIN also on write operations too, this is needed for operations + // which requires read and write on the socket. + fds[0].events = wantRead ? THRIFT_POLLIN : THRIFT_POLLIN | THRIFT_POLLOUT; + + if (interruptListener_) { + fds[1].fd = *(interruptListener_.get()); + fds[1].events = THRIFT_POLLIN; + } + + int timeout = -1; + if (wantRead && recvTimeout_) { + timeout = recvTimeout_; + } + if (!wantRead && sendTimeout_) { + timeout = sendTimeout_; + } + + int ret = THRIFT_POLL(fds, interruptListener_ ? 2 : 1, timeout); + + if (ret < 0) { + // error cases + if (THRIFT_GET_SOCKET_ERROR == THRIFT_EINTR) { + return TSSL_EINTR; // repeat operation + } + int errno_copy = THRIFT_GET_SOCKET_ERROR; + GlobalOutput.perror("TSSLSocket::read THRIFT_POLL() ", errno_copy); + throw TTransportException(TTransportException::UNKNOWN, "Unknown", errno_copy); + } else if (ret > 0){ + if (fds[1].revents & THRIFT_POLLIN) { + throw TTransportException(TTransportException::INTERRUPTED, "Interrupted"); + } + return TSSL_DATA; + } else { + throw TTransportException(TTransportException::TIMED_OUT, "THRIFT_POLL (timed out)"); + } +} + +// TSSLSocketFactory implementation +uint64_t TSSLSocketFactory::count_ = 0; +Mutex TSSLSocketFactory::mutex_; +bool TSSLSocketFactory::manualOpenSSLInitialization_ = false; + +TSSLSocketFactory::TSSLSocketFactory(SSLProtocol protocol) : server_(false) { + Guard guard(mutex_); + if (count_ == 0) { + if (!manualOpenSSLInitialization_) { + initializeOpenSSL(); + } + randomize(); + } + count_++; + ctx_ = std::make_shared(protocol); +} + +TSSLSocketFactory::~TSSLSocketFactory() { + Guard guard(mutex_); + ctx_.reset(); + count_--; + if (count_ == 0 && !manualOpenSSLInitialization_) { + cleanupOpenSSL(); + } +} + +std::shared_ptr TSSLSocketFactory::createSocket() { + std::shared_ptr ssl(new TSSLSocket(ctx_)); + setup(ssl); + return ssl; +} + +std::shared_ptr TSSLSocketFactory::createSocket(std::shared_ptr interruptListener) { + std::shared_ptr ssl(new TSSLSocket(ctx_, interruptListener)); + setup(ssl); + return ssl; +} + +std::shared_ptr TSSLSocketFactory::createSocket(THRIFT_SOCKET socket) { + std::shared_ptr ssl(new TSSLSocket(ctx_, socket)); + setup(ssl); + return ssl; +} + +std::shared_ptr TSSLSocketFactory::createSocket(THRIFT_SOCKET socket, std::shared_ptr interruptListener) { + std::shared_ptr ssl(new TSSLSocket(ctx_, socket, interruptListener)); + setup(ssl); + return ssl; +} + +std::shared_ptr TSSLSocketFactory::createSocket(const string& host, int port) { + std::shared_ptr ssl(new TSSLSocket(ctx_, host, port)); + setup(ssl); + return ssl; +} + +std::shared_ptr TSSLSocketFactory::createSocket(const string& host, int port, std::shared_ptr interruptListener) { + std::shared_ptr ssl(new TSSLSocket(ctx_, host, port, interruptListener)); + setup(ssl); + return ssl; +} + + +void TSSLSocketFactory::setup(std::shared_ptr ssl) { + ssl->server(server()); + if (access_ == nullptr && !server()) { + access_ = std::shared_ptr(new DefaultClientAccessManager); + } + if (access_ != nullptr) { + ssl->access(access_); + } +} + +void TSSLSocketFactory::ciphers(const string& enable) { + int rc = SSL_CTX_set_cipher_list(ctx_->get(), enable.c_str()); + if (ERR_peek_error() != 0) { + string errors; + buildErrors(errors); + throw TSSLException("SSL_CTX_set_cipher_list: " + errors); + } + if (rc == 0) { + throw TSSLException("None of specified ciphers are supported"); + } +} + +void TSSLSocketFactory::authenticate(bool required) { + int mode; + if (required) { + mode = SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT | SSL_VERIFY_CLIENT_ONCE; + } else { + mode = SSL_VERIFY_NONE; + } + SSL_CTX_set_verify(ctx_->get(), mode, nullptr); +} + +void TSSLSocketFactory::loadCertificate(const char* path, const char* format) { + if (path == nullptr || format == nullptr) { + throw TTransportException(TTransportException::BAD_ARGS, + "loadCertificateChain: either or is NULL"); + } + if (strcmp(format, "PEM") == 0) { + if (SSL_CTX_use_certificate_chain_file(ctx_->get(), path) == 0) { + int errno_copy = THRIFT_GET_SOCKET_ERROR; + string errors; + buildErrors(errors, errno_copy); + throw TSSLException("SSL_CTX_use_certificate_chain_file: " + errors); + } + } else { + throw TSSLException("Unsupported certificate format: " + string(format)); + } +} + +void TSSLSocketFactory::loadPrivateKey(const char* path, const char* format) { + if (path == nullptr || format == nullptr) { + throw TTransportException(TTransportException::BAD_ARGS, + "loadPrivateKey: either or is NULL"); + } + if (strcmp(format, "PEM") == 0) { + if (SSL_CTX_use_PrivateKey_file(ctx_->get(), path, SSL_FILETYPE_PEM) == 0) { + int errno_copy = THRIFT_GET_SOCKET_ERROR; + string errors; + buildErrors(errors, errno_copy); + throw TSSLException("SSL_CTX_use_PrivateKey_file: " + errors); + } + } +} + +void TSSLSocketFactory::loadTrustedCertificates(const char* path, const char* capath) { + if (path == nullptr) { + throw TTransportException(TTransportException::BAD_ARGS, + "loadTrustedCertificates: is NULL"); + } + if (SSL_CTX_load_verify_locations(ctx_->get(), path, capath) == 0) { + int errno_copy = THRIFT_GET_SOCKET_ERROR; + string errors; + buildErrors(errors, errno_copy); + throw TSSLException("SSL_CTX_load_verify_locations: " + errors); + } +} + +void TSSLSocketFactory::randomize() { + RAND_poll(); +} + +void TSSLSocketFactory::overrideDefaultPasswordCallback() { + SSL_CTX_set_default_passwd_cb(ctx_->get(), passwordCallback); + SSL_CTX_set_default_passwd_cb_userdata(ctx_->get(), this); +} + +int TSSLSocketFactory::passwordCallback(char* password, int size, int, void* data) { + auto* factory = (TSSLSocketFactory*)data; + string userPassword; + factory->getPassword(userPassword, size); + int length = static_cast(userPassword.size()); + if (length > size) { + length = size; + } + strncpy(password, userPassword.c_str(), length); + userPassword.assign(userPassword.size(), '*'); + return length; +} + +// extract error messages from error queue +void buildErrors(string& errors, int errno_copy, int sslerrno) { + unsigned long errorCode; + char message[256]; + + errors.reserve(512); + while ((errorCode = ERR_get_error()) != 0) { + if (!errors.empty()) { + errors += "; "; + } + const char* reason = ERR_reason_error_string(errorCode); + if (reason == nullptr) { + THRIFT_SNPRINTF(message, sizeof(message) - 1, "SSL error # %lu", errorCode); + reason = message; + } + errors += reason; + } + if (errors.empty()) { + if (errno_copy != 0) { + errors += TOutput::strerror_s(errno_copy); + } + } + if (errors.empty()) { + errors = "error code: " + to_string(errno_copy); + } + if (sslerrno) { + errors += " (SSL_error_code = " + to_string(sslerrno) + ")"; + if (sslerrno == SSL_ERROR_SYSCALL) { + char buf[4096]; + int err; + while ((err = ERR_get_error()) != 0) { + errors += " "; + errors += ERR_error_string(err, buf); + } + } + } +} + +/** + * Default implementation of AccessManager + */ +Decision DefaultClientAccessManager::verify(const sockaddr_storage& sa) noexcept { + (void)sa; + return SKIP; +} + +Decision DefaultClientAccessManager::verify(const string& host, + const char* name, + int size) noexcept { + if (host.empty() || name == nullptr || size <= 0) { + return SKIP; + } + return (matchName(host.c_str(), name, size) ? ALLOW : SKIP); +} + +Decision DefaultClientAccessManager::verify(const sockaddr_storage& sa, + const char* data, + int size) noexcept { + bool match = false; + if (sa.ss_family == AF_INET && size == sizeof(in_addr)) { + match = (memcmp(&((sockaddr_in*)&sa)->sin_addr, data, size) == 0); + } else if (sa.ss_family == AF_INET6 && size == sizeof(in6_addr)) { + match = (memcmp(&((sockaddr_in6*)&sa)->sin6_addr, data, size) == 0); + } + return (match ? ALLOW : SKIP); +} + +/** + * Match a name with a pattern. The pattern may include wildcard. A single + * wildcard "*" can match up to one component in the domain name. + * + * @param host Host name, typically the name of the remote host + * @param pattern Name retrieved from certificate + * @param size Size of "pattern" + * @return True, if "host" matches "pattern". False otherwise. + */ +bool matchName(const char* host, const char* pattern, int size) { + bool match = false; + int i = 0, j = 0; + while (i < size && host[j] != '\0') { + if (uppercase(pattern[i]) == uppercase(host[j])) { + i++; + j++; + continue; + } + if (pattern[i] == '*') { + while (host[j] != '.' && host[j] != '\0') { + j++; + } + i++; + continue; + } + break; + } + if (i == size && host[j] == '\0') { + match = true; + } + return match; +} + +// This is to work around the Turkish locale issue, i.e., +// toupper('i') != toupper('I') if locale is "tr_TR" +char uppercase(char c) { + if ('a' <= c && c <= 'z') { + return c + ('A' - 'a'); + } + return c; +} +} +} +} diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TSSLSocket.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TSSLSocket.h new file mode 100644 index 000000000..87a960147 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TSSLSocket.h @@ -0,0 +1,436 @@ +/* + * 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. + */ + +#ifndef _THRIFT_TRANSPORT_TSSLSOCKET_H_ +#define _THRIFT_TRANSPORT_TSSLSOCKET_H_ 1 + +// Put this first to avoid WIN32 build failure +#include + +#include +#include +#include + +namespace apache { +namespace thrift { +namespace transport { + +class AccessManager; +class SSLContext; + +enum SSLProtocol { + SSLTLS = 0, // Supports SSLv2 and SSLv3 handshake but only negotiates at TLSv1_0 or later. +//SSLv2 = 1, // HORRIBLY INSECURE! + SSLv3 = 2, // Supports SSLv3 only - also horribly insecure! + TLSv1_0 = 3, // Supports TLSv1_0 or later. + TLSv1_1 = 4, // Supports TLSv1_1 or later. + TLSv1_2 = 5, // Supports TLSv1_2 or later. + LATEST = TLSv1_2 +}; + +#define TSSL_EINTR 0 +#define TSSL_DATA 1 + +/** + * Initialize OpenSSL library. This function, or some other + * equivalent function to initialize OpenSSL, must be called before + * TSSLSocket is used. If you set TSSLSocketFactory to use manual + * OpenSSL initialization, you should call this function or otherwise + * ensure OpenSSL is initialized yourself. + */ +void initializeOpenSSL(); +/** + * Cleanup OpenSSL library. This function should be called to clean + * up OpenSSL after use of OpenSSL functionality is finished. If you + * set TSSLSocketFactory to use manual OpenSSL initialization, you + * should call this function yourself or ensure that whatever + * initialized OpenSSL cleans it up too. + */ +void cleanupOpenSSL(); + +/** + * OpenSSL implementation for SSL socket interface. + */ +class TSSLSocket : public TSocket { +public: + ~TSSLSocket() override; + /** + * TTransport interface. + */ + bool isOpen() const override; + bool peek() override; + void open() override; + void close() override; + bool hasPendingDataToRead() override; + uint32_t read(uint8_t* buf, uint32_t len) override; + void write(const uint8_t* buf, uint32_t len) override; + uint32_t write_partial(const uint8_t* buf, uint32_t len) override; + void flush() override; + /** + * Set whether to use client or server side SSL handshake protocol. + * + * @param flag Use server side handshake protocol if true. + */ + void server(bool flag) { server_ = flag; } + /** + * Determine whether the SSL socket is server or client mode. + */ + bool server() const { return server_; } + /** + * Set AccessManager. + * + * @param manager Instance of AccessManager + */ + virtual void access(std::shared_ptr manager) { access_ = manager; } + /** + * Set eventSafe flag if libevent is used. + */ + void setLibeventSafe() { eventSafe_ = true; } + /** + * Determines whether SSL Socket is libevent safe or not. + */ + bool isLibeventSafe() const { return eventSafe_; } + +protected: + /** + * Constructor. + */ + TSSLSocket(std::shared_ptr ctx); + /** + * Constructor with an interrupt signal. + */ + TSSLSocket(std::shared_ptr ctx, std::shared_ptr interruptListener); + /** + * Constructor, create an instance of TSSLSocket given an existing socket. + * + * @param socket An existing socket + */ + TSSLSocket(std::shared_ptr ctx, THRIFT_SOCKET socket); + /** + * Constructor, create an instance of TSSLSocket given an existing socket that can be interrupted. + * + * @param socket An existing socket + */ + TSSLSocket(std::shared_ptr ctx, THRIFT_SOCKET socket, std::shared_ptr interruptListener); + /** + * Constructor. + * + * @param host Remote host name + * @param port Remote port number + */ + TSSLSocket(std::shared_ptr ctx, std::string host, int port); + /** + * Constructor with an interrupt signal. + * + * @param host Remote host name + * @param port Remote port number + */ + TSSLSocket(std::shared_ptr ctx, std::string host, int port, std::shared_ptr interruptListener); + /** + * Authorize peer access after SSL handshake completes. + */ + virtual void authorize(); + /** + * Initiate SSL handshake if not already initiated. + */ + void initializeHandshake(); + /** + * Initiate SSL handshake params. + */ + void initializeHandshakeParams(); + /** + * Check if SSL handshake is completed or not. + */ + bool checkHandshake(); + /** + * Waits for an socket or shutdown event. + * + * @throw TTransportException::INTERRUPTED if interrupted is signaled. + * + * @return TSSL_EINTR if EINTR happened on the underlying socket + * TSSL_DATA if data is available on the socket. + */ + unsigned int waitForEvent(bool wantRead); + + bool server_; + SSL* ssl_; + std::shared_ptr ctx_; + std::shared_ptr access_; + friend class TSSLSocketFactory; + +private: + bool handshakeCompleted_; + int readRetryCount_; + bool eventSafe_; + + void init(); +}; + +/** + * SSL socket factory. SSL sockets should be created via SSL factory. + * The factory will automatically initialize and cleanup openssl as long as + * there is a TSSLSocketFactory instantiated, and as long as the static + * boolean manualOpenSSLInitialization_ is set to false, the default. + * + * If you would like to initialize and cleanup openssl yourself, set + * manualOpenSSLInitialization_ to true and TSSLSocketFactory will no + * longer be responsible for openssl initialization and teardown. + * + * It is the responsibility of the code using TSSLSocketFactory to + * ensure that the factory lifetime exceeds the lifetime of any sockets + * it might create. If this is not guaranteed, a socket may call into + * openssl after the socket factory has cleaned up openssl! This + * guarantee is unnecessary if manualOpenSSLInitialization_ is true, + * however, since it would be up to the consuming application instead. + */ +class TSSLSocketFactory { +public: + /** + * Constructor/Destructor + * + * @param protocol The SSL/TLS protocol to use. + */ + TSSLSocketFactory(SSLProtocol protocol = SSLTLS); + virtual ~TSSLSocketFactory(); + /** + * Create an instance of TSSLSocket with a fresh new socket. + */ + virtual std::shared_ptr createSocket(); + /** + * Create an instance of TSSLSocket with a fresh new socket, which is interruptable. + */ + virtual std::shared_ptr createSocket(std::shared_ptr interruptListener); + /** + * Create an instance of TSSLSocket with the given socket. + * + * @param socket An existing socket. + */ + virtual std::shared_ptr createSocket(THRIFT_SOCKET socket); + /** + * Create an instance of TSSLSocket with the given socket which is interruptable. + * + * @param socket An existing socket. + */ + virtual std::shared_ptr createSocket(THRIFT_SOCKET socket, std::shared_ptr interruptListener); + /** + * Create an instance of TSSLSocket. + * + * @param host Remote host to be connected to + * @param port Remote port to be connected to + */ + virtual std::shared_ptr createSocket(const std::string& host, int port); + /** + * Create an instance of TSSLSocket. + * + * @param host Remote host to be connected to + * @param port Remote port to be connected to + */ + virtual std::shared_ptr createSocket(const std::string& host, int port, std::shared_ptr interruptListener); + /** + * Set ciphers to be used in SSL handshake process. + * + * @param ciphers A list of ciphers + */ + virtual void ciphers(const std::string& enable); + /** + * Enable/Disable authentication. + * + * @param required Require peer to present valid certificate if true + */ + virtual void authenticate(bool required); + /** + * Load server certificate. + * + * @param path Path to the certificate file + * @param format Certificate file format + */ + virtual void loadCertificate(const char* path, const char* format = "PEM"); + /** + * Load private key. + * + * @param path Path to the private key file + * @param format Private key file format + */ + virtual void loadPrivateKey(const char* path, const char* format = "PEM"); + /** + * Load trusted certificates from specified file. + * + * @param path Path to trusted certificate file + */ + virtual void loadTrustedCertificates(const char* path, const char* capath = nullptr); + /** + * Default randomize method. + */ + virtual void randomize(); + /** + * Override default OpenSSL password callback with getPassword(). + */ + void overrideDefaultPasswordCallback(); + /** + * Set/Unset server mode. + * + * @param flag Server mode if true + */ + virtual void server(bool flag) { server_ = flag; } + /** + * Determine whether the socket is in server or client mode. + * + * @return true, if server mode, or, false, if client mode + */ + virtual bool server() const { return server_; } + /** + * Set AccessManager. + * + * @param manager The AccessManager instance + */ + virtual void access(std::shared_ptr manager) { access_ = manager; } + static void setManualOpenSSLInitialization(bool manualOpenSSLInitialization) { + manualOpenSSLInitialization_ = manualOpenSSLInitialization; + } + +protected: + std::shared_ptr ctx_; + + /** + * Override this method for custom password callback. It may be called + * multiple times at any time during a session as necessary. + * + * @param password Pass collected password to OpenSSL + * @param size Maximum length of password including NULL character + */ + virtual void getPassword(std::string& /* password */, int /* size */) {} + +private: + bool server_; + std::shared_ptr access_; + static concurrency::Mutex mutex_; + static uint64_t count_; + THRIFT_EXPORT static bool manualOpenSSLInitialization_; + void setup(std::shared_ptr ssl); + static int passwordCallback(char* password, int size, int, void* data); +}; + +/** + * SSL exception. + */ +class TSSLException : public TTransportException { +public: + TSSLException(const std::string& message) + : TTransportException(TTransportException::INTERNAL_ERROR, message) {} + + const char* what() const noexcept override { + if (message_.empty()) { + return "TSSLException"; + } else { + return message_.c_str(); + } + } +}; + +/** + * Wrap OpenSSL SSL_CTX into a class. + */ +class SSLContext { +public: + SSLContext(const SSLProtocol& protocol = SSLTLS); + virtual ~SSLContext(); + SSL* createSSL(); + SSL_CTX* get() { return ctx_; } + +private: + SSL_CTX* ctx_; +}; + +/** + * Callback interface for access control. It's meant to verify the remote host. + * It's constructed when application starts and set to TSSLSocketFactory + * instance. It's passed onto all TSSLSocket instances created by this factory + * object. + */ +class AccessManager { +public: + enum Decision { + DENY = -1, // deny access + SKIP = 0, // cannot make decision, move on to next (if any) + ALLOW = 1 // allow access + }; + /** + * Destructor + */ + virtual ~AccessManager() = default; + /** + * Determine whether the peer should be granted access or not. It's called + * once after the SSL handshake completes successfully, before peer certificate + * is examined. + * + * If a valid decision (ALLOW or DENY) is returned, the peer certificate is + * not to be verified. + * + * @param sa Peer IP address + * @return True if the peer is trusted, false otherwise + */ + virtual Decision verify(const sockaddr_storage& /* sa */) noexcept { return DENY; } + /** + * Determine whether the peer should be granted access or not. It's called + * every time a DNS subjectAltName/common name is extracted from peer's + * certificate. + * + * @param host Client mode: host name returned by TSocket::getHost() + * Server mode: host name returned by TSocket::getPeerHost() + * @param name SubjectAltName or common name extracted from peer certificate + * @param size Length of name + * @return True if the peer is trusted, false otherwise + * + * Note: The "name" parameter may be UTF8 encoded. + */ + virtual Decision verify(const std::string& /* host */, + const char* /* name */, + int /* size */) noexcept { + return DENY; + } + /** + * Determine whether the peer should be granted access or not. It's called + * every time an IP subjectAltName is extracted from peer's certificate. + * + * @param sa Peer IP address retrieved from the underlying socket + * @param data IP address extracted from certificate + * @param size Length of the IP address + * @return True if the peer is trusted, false otherwise + */ + virtual Decision verify(const sockaddr_storage& /* sa */, + const char* /* data */, + int /* size */) noexcept { + return DENY; + } +}; + +typedef AccessManager::Decision Decision; + +class DefaultClientAccessManager : public AccessManager { +public: + // AccessManager interface + Decision verify(const sockaddr_storage& sa) noexcept override; + Decision verify(const std::string& host, const char* name, int size) noexcept override; + Decision verify(const sockaddr_storage& sa, const char* data, int size) noexcept override; +}; +} +} +} + +#endif diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TServerSocket.cpp b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TServerSocket.cpp new file mode 100644 index 000000000..ece05445a --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TServerSocket.cpp @@ -0,0 +1,698 @@ +/* + * 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. + */ + +#include + +#include +#include +#include +#include +#ifdef HAVE_SYS_SOCKET_H +#include +#endif +#ifdef HAVE_SYS_UN_H +#include +#endif +#ifdef HAVE_SYS_POLL_H +#include +#endif +#ifdef HAVE_NETINET_IN_H +#include +#include +#endif +#ifdef HAVE_NETDB_H +#include +#endif +#include +#ifdef HAVE_UNISTD_H +#include +#endif + +#include +#include +#include + +#ifndef AF_LOCAL +#define AF_LOCAL AF_UNIX +#endif + +#ifndef SOCKOPT_CAST_T +#ifndef _WIN32 +#define SOCKOPT_CAST_T void +#else +#define SOCKOPT_CAST_T char +#endif // _WIN32 +#endif + +template +inline const SOCKOPT_CAST_T* const_cast_sockopt(const T* v) { + return reinterpret_cast(v); +} + +template +inline SOCKOPT_CAST_T* cast_sockopt(T* v) { + return reinterpret_cast(v); +} + +void destroyer_of_fine_sockets(THRIFT_SOCKET* ssock) { + ::THRIFT_CLOSESOCKET(*ssock); + delete ssock; +} + +using std::string; + +namespace apache { +namespace thrift { +namespace transport { + +using std::shared_ptr; + +TGetAddrInfoWrapper::TGetAddrInfoWrapper(const char* node, + const char* service, + const struct addrinfo* hints) + : node_(node), service_(service), hints_(hints), res_(nullptr) {} + +TGetAddrInfoWrapper::~TGetAddrInfoWrapper() { + if (this->res_ != nullptr) + freeaddrinfo(this->res_); +} + +int TGetAddrInfoWrapper::init() { + if (this->res_ == nullptr) + return getaddrinfo(this->node_, this->service_, this->hints_, &(this->res_)); + return 0; +} + +const struct addrinfo* TGetAddrInfoWrapper::res() { + return this->res_; +} + +TServerSocket::TServerSocket(int port) + : interruptableChildren_(true), + port_(port), + serverSocket_(THRIFT_INVALID_SOCKET), + acceptBacklog_(DEFAULT_BACKLOG), + sendTimeout_(0), + recvTimeout_(0), + accTimeout_(-1), + retryLimit_(0), + retryDelay_(0), + tcpSendBuffer_(0), + tcpRecvBuffer_(0), + keepAlive_(false), + listening_(false), + interruptSockWriter_(THRIFT_INVALID_SOCKET), + interruptSockReader_(THRIFT_INVALID_SOCKET), + childInterruptSockWriter_(THRIFT_INVALID_SOCKET) { +} + +TServerSocket::TServerSocket(int port, int sendTimeout, int recvTimeout) + : interruptableChildren_(true), + port_(port), + serverSocket_(THRIFT_INVALID_SOCKET), + acceptBacklog_(DEFAULT_BACKLOG), + sendTimeout_(sendTimeout), + recvTimeout_(recvTimeout), + accTimeout_(-1), + retryLimit_(0), + retryDelay_(0), + tcpSendBuffer_(0), + tcpRecvBuffer_(0), + keepAlive_(false), + listening_(false), + interruptSockWriter_(THRIFT_INVALID_SOCKET), + interruptSockReader_(THRIFT_INVALID_SOCKET), + childInterruptSockWriter_(THRIFT_INVALID_SOCKET) { +} + +TServerSocket::TServerSocket(const string& address, int port) + : interruptableChildren_(true), + port_(port), + address_(address), + serverSocket_(THRIFT_INVALID_SOCKET), + acceptBacklog_(DEFAULT_BACKLOG), + sendTimeout_(0), + recvTimeout_(0), + accTimeout_(-1), + retryLimit_(0), + retryDelay_(0), + tcpSendBuffer_(0), + tcpRecvBuffer_(0), + keepAlive_(false), + listening_(false), + interruptSockWriter_(THRIFT_INVALID_SOCKET), + interruptSockReader_(THRIFT_INVALID_SOCKET), + childInterruptSockWriter_(THRIFT_INVALID_SOCKET) { +} + +TServerSocket::TServerSocket(const string& path) + : interruptableChildren_(true), + port_(0), + path_(path), + serverSocket_(THRIFT_INVALID_SOCKET), + acceptBacklog_(DEFAULT_BACKLOG), + sendTimeout_(0), + recvTimeout_(0), + accTimeout_(-1), + retryLimit_(0), + retryDelay_(0), + tcpSendBuffer_(0), + tcpRecvBuffer_(0), + keepAlive_(false), + listening_(false), + interruptSockWriter_(THRIFT_INVALID_SOCKET), + interruptSockReader_(THRIFT_INVALID_SOCKET), + childInterruptSockWriter_(THRIFT_INVALID_SOCKET) { +} + +TServerSocket::~TServerSocket() { + close(); +} + +void TServerSocket::setSendTimeout(int sendTimeout) { + sendTimeout_ = sendTimeout; +} + +void TServerSocket::setRecvTimeout(int recvTimeout) { + recvTimeout_ = recvTimeout; +} + +void TServerSocket::setAcceptTimeout(int accTimeout) { + accTimeout_ = accTimeout; +} + +void TServerSocket::setAcceptBacklog(int accBacklog) { + acceptBacklog_ = accBacklog; +} + +void TServerSocket::setRetryLimit(int retryLimit) { + retryLimit_ = retryLimit; +} + +void TServerSocket::setRetryDelay(int retryDelay) { + retryDelay_ = retryDelay; +} + +void TServerSocket::setTcpSendBuffer(int tcpSendBuffer) { + tcpSendBuffer_ = tcpSendBuffer; +} + +void TServerSocket::setTcpRecvBuffer(int tcpRecvBuffer) { + tcpRecvBuffer_ = tcpRecvBuffer; +} + +void TServerSocket::setInterruptableChildren(bool enable) { + if (listening_) { + throw std::logic_error("setInterruptableChildren cannot be called after listen()"); + } + interruptableChildren_ = enable; +} + +void TServerSocket::listen() { + listening_ = true; +#ifdef _WIN32 + TWinsockSingleton::create(); +#endif // _WIN32 + THRIFT_SOCKET sv[2]; + // Create the socket pair used to interrupt + if (-1 == THRIFT_SOCKETPAIR(AF_LOCAL, SOCK_STREAM, 0, sv)) { + GlobalOutput.perror("TServerSocket::listen() socketpair() interrupt", THRIFT_GET_SOCKET_ERROR); + interruptSockWriter_ = THRIFT_INVALID_SOCKET; + interruptSockReader_ = THRIFT_INVALID_SOCKET; + } else { + interruptSockWriter_ = sv[1]; + interruptSockReader_ = sv[0]; + } + + // Create the socket pair used to interrupt all clients + if (-1 == THRIFT_SOCKETPAIR(AF_LOCAL, SOCK_STREAM, 0, sv)) { + GlobalOutput.perror("TServerSocket::listen() socketpair() childInterrupt", + THRIFT_GET_SOCKET_ERROR); + childInterruptSockWriter_ = THRIFT_INVALID_SOCKET; + pChildInterruptSockReader_.reset(); + } else { + childInterruptSockWriter_ = sv[1]; + pChildInterruptSockReader_ + = std::shared_ptr(new THRIFT_SOCKET(sv[0]), destroyer_of_fine_sockets); + } + + // Validate port number + if (port_ < 0 || port_ > 0xFFFF) { + throw TTransportException(TTransportException::BAD_ARGS, "Specified port is invalid"); + } + + const struct addrinfo *res; + int error; + char port[sizeof("65535")]; + THRIFT_SNPRINTF(port, sizeof(port), "%d", port_); + + struct addrinfo hints; + std::memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG; + + // If address is not specified use wildcard address (NULL) + TGetAddrInfoWrapper info(address_.empty() ? nullptr : &address_[0], port, &hints); + + error = info.init(); + if (error) { + GlobalOutput.printf("getaddrinfo %d: %s", error, THRIFT_GAI_STRERROR(error)); + close(); + throw TTransportException(TTransportException::NOT_OPEN, + "Could not resolve host for server socket."); + } + + // Pick the ipv6 address first since ipv4 addresses can be mapped + // into ipv6 space. + for (res = info.res(); res; res = res->ai_next) { + if (res->ai_family == AF_INET6 || res->ai_next == nullptr) + break; + } + + if (!path_.empty()) { + serverSocket_ = socket(PF_UNIX, SOCK_STREAM, IPPROTO_IP); + } else if (res != nullptr) { + serverSocket_ = socket(res->ai_family, res->ai_socktype, res->ai_protocol); + } + + if (serverSocket_ == THRIFT_INVALID_SOCKET) { + int errno_copy = THRIFT_GET_SOCKET_ERROR; + GlobalOutput.perror("TServerSocket::listen() socket() ", errno_copy); + close(); + throw TTransportException(TTransportException::NOT_OPEN, + "Could not create server socket.", + errno_copy); + } + + // Set THRIFT_NO_SOCKET_CACHING to prevent 2MSL delay on accept + int one = 1; + if (-1 == setsockopt(serverSocket_, + SOL_SOCKET, + THRIFT_NO_SOCKET_CACHING, + cast_sockopt(&one), + sizeof(one))) { +// ignore errors coming out of this setsockopt on Windows. This is because +// SO_EXCLUSIVEADDRUSE requires admin privileges on WinXP, but we don't +// want to force servers to be an admin. +#ifndef _WIN32 + int errno_copy = THRIFT_GET_SOCKET_ERROR; + GlobalOutput.perror("TServerSocket::listen() setsockopt() THRIFT_NO_SOCKET_CACHING ", + errno_copy); + close(); + throw TTransportException(TTransportException::NOT_OPEN, + "Could not set THRIFT_NO_SOCKET_CACHING", + errno_copy); +#endif + } + + // Set TCP buffer sizes + if (tcpSendBuffer_ > 0) { + if (-1 == setsockopt(serverSocket_, + SOL_SOCKET, + SO_SNDBUF, + cast_sockopt(&tcpSendBuffer_), + sizeof(tcpSendBuffer_))) { + int errno_copy = THRIFT_GET_SOCKET_ERROR; + GlobalOutput.perror("TServerSocket::listen() setsockopt() SO_SNDBUF ", errno_copy); + close(); + throw TTransportException(TTransportException::NOT_OPEN, + "Could not set SO_SNDBUF", + errno_copy); + } + } + + if (tcpRecvBuffer_ > 0) { + if (-1 == setsockopt(serverSocket_, + SOL_SOCKET, + SO_RCVBUF, + cast_sockopt(&tcpRecvBuffer_), + sizeof(tcpRecvBuffer_))) { + int errno_copy = THRIFT_GET_SOCKET_ERROR; + GlobalOutput.perror("TServerSocket::listen() setsockopt() SO_RCVBUF ", errno_copy); + close(); + throw TTransportException(TTransportException::NOT_OPEN, + "Could not set SO_RCVBUF", + errno_copy); + } + } + +// Defer accept +#ifdef TCP_DEFER_ACCEPT + if (path_.empty()) { + if (-1 == setsockopt(serverSocket_, IPPROTO_TCP, TCP_DEFER_ACCEPT, &one, sizeof(one))) { + int errno_copy = THRIFT_GET_SOCKET_ERROR; + GlobalOutput.perror("TServerSocket::listen() setsockopt() TCP_DEFER_ACCEPT ", errno_copy); + close(); + throw TTransportException(TTransportException::NOT_OPEN, + "Could not set TCP_DEFER_ACCEPT", + errno_copy); + } + } +#endif // #ifdef TCP_DEFER_ACCEPT + +#ifdef IPV6_V6ONLY + if (res->ai_family == AF_INET6 && path_.empty()) { + int zero = 0; + if (-1 == setsockopt(serverSocket_, + IPPROTO_IPV6, + IPV6_V6ONLY, + cast_sockopt(&zero), + sizeof(zero))) { + GlobalOutput.perror("TServerSocket::listen() IPV6_V6ONLY ", THRIFT_GET_SOCKET_ERROR); + } + } +#endif // #ifdef IPV6_V6ONLY + + // Turn linger off, don't want to block on calls to close + struct linger ling = {0, 0}; + if (-1 == setsockopt(serverSocket_, SOL_SOCKET, SO_LINGER, cast_sockopt(&ling), sizeof(ling))) { + int errno_copy = THRIFT_GET_SOCKET_ERROR; + GlobalOutput.perror("TServerSocket::listen() setsockopt() SO_LINGER ", errno_copy); + close(); + throw TTransportException(TTransportException::NOT_OPEN, "Could not set SO_LINGER", errno_copy); + } + + // Unix Sockets do not need that + if (path_.empty()) { + // TCP Nodelay, speed over bandwidth + if (-1 + == setsockopt(serverSocket_, IPPROTO_TCP, TCP_NODELAY, cast_sockopt(&one), sizeof(one))) { + int errno_copy = THRIFT_GET_SOCKET_ERROR; + GlobalOutput.perror("TServerSocket::listen() setsockopt() TCP_NODELAY ", errno_copy); + close(); + throw TTransportException(TTransportException::NOT_OPEN, + "Could not set TCP_NODELAY", + errno_copy); + } + } + + // Set NONBLOCK on the accept socket + int flags = THRIFT_FCNTL(serverSocket_, THRIFT_F_GETFL, 0); + if (flags == -1) { + int errno_copy = THRIFT_GET_SOCKET_ERROR; + GlobalOutput.perror("TServerSocket::listen() THRIFT_FCNTL() THRIFT_F_GETFL ", errno_copy); + close(); + throw TTransportException(TTransportException::NOT_OPEN, + "THRIFT_FCNTL() THRIFT_F_GETFL failed", + errno_copy); + } + + if (-1 == THRIFT_FCNTL(serverSocket_, THRIFT_F_SETFL, flags | THRIFT_O_NONBLOCK)) { + int errno_copy = THRIFT_GET_SOCKET_ERROR; + GlobalOutput.perror("TServerSocket::listen() THRIFT_FCNTL() THRIFT_O_NONBLOCK ", errno_copy); + close(); + throw TTransportException(TTransportException::NOT_OPEN, + "THRIFT_FCNTL() THRIFT_F_SETFL THRIFT_O_NONBLOCK failed", + errno_copy); + } + + // prepare the port information + // we may want to try to bind more than once, since THRIFT_NO_SOCKET_CACHING doesn't + // always seem to work. The client can configure the retry variables. + int retries = 0; + int errno_copy = 0; + + if (!path_.empty()) { + +#ifndef _WIN32 + + // Unix Domain Socket + size_t len = path_.size() + 1; + if (len > sizeof(((sockaddr_un*)nullptr)->sun_path)) { + errno_copy = THRIFT_GET_SOCKET_ERROR; + GlobalOutput.perror("TSocket::listen() Unix Domain socket path too long", errno_copy); + throw TTransportException(TTransportException::NOT_OPEN, + "Unix Domain socket path too long", + errno_copy); + } + + struct sockaddr_un address; + address.sun_family = AF_UNIX; + memcpy(address.sun_path, path_.c_str(), len); + + auto structlen = static_cast(sizeof(address)); + + if (!address.sun_path[0]) { // abstract namespace socket +#ifdef __linux__ + // sun_path is not null-terminated in this case and structlen determines its length + structlen -= sizeof(address.sun_path) - len; +#else + GlobalOutput.perror("TSocket::open() Abstract Namespace Domain sockets only supported on linux: ", -99); + throw TTransportException(TTransportException::NOT_OPEN, + " Abstract Namespace Domain socket path not supported"); +#endif + } + + do { + if (0 == ::bind(serverSocket_, (struct sockaddr*)&address, structlen)) { + break; + } + errno_copy = THRIFT_GET_SOCKET_ERROR; + // use short circuit evaluation here to only sleep if we need to + } while ((retries++ < retryLimit_) && (THRIFT_SLEEP_SEC(retryDelay_) == 0)); +#else + GlobalOutput.perror("TSocket::open() Unix Domain socket path not supported on windows", -99); + throw TTransportException(TTransportException::NOT_OPEN, + " Unix Domain socket path not supported"); +#endif + } else { + do { + if (0 == ::bind(serverSocket_, res->ai_addr, static_cast(res->ai_addrlen))) { + break; + } + errno_copy = THRIFT_GET_SOCKET_ERROR; + // use short circuit evaluation here to only sleep if we need to + } while ((retries++ < retryLimit_) && (THRIFT_SLEEP_SEC(retryDelay_) == 0)); + + // retrieve bind info + if (port_ == 0 && retries <= retryLimit_) { + struct sockaddr_storage sa; + socklen_t len = sizeof(sa); + std::memset(&sa, 0, len); + if (::getsockname(serverSocket_, reinterpret_cast(&sa), &len) < 0) { + errno_copy = THRIFT_GET_SOCKET_ERROR; + GlobalOutput.perror("TServerSocket::getPort() getsockname() ", errno_copy); + } else { + if (sa.ss_family == AF_INET6) { + const auto* sin = reinterpret_cast(&sa); + port_ = ntohs(sin->sin6_port); + } else { + const auto* sin = reinterpret_cast(&sa); + port_ = ntohs(sin->sin_port); + } + } + } + } + + // throw an error if we failed to bind properly + if (retries > retryLimit_) { + char errbuf[1024]; + if (!path_.empty()) { + THRIFT_SNPRINTF(errbuf, sizeof(errbuf), "TServerSocket::listen() PATH %s", path_.c_str()); + } else { + THRIFT_SNPRINTF(errbuf, sizeof(errbuf), "TServerSocket::listen() BIND %d", port_); + } + GlobalOutput(errbuf); + close(); + throw TTransportException(TTransportException::NOT_OPEN, + "Could not bind", + errno_copy); + } + + if (listenCallback_) + listenCallback_(serverSocket_); + + // Call listen + if (-1 == ::listen(serverSocket_, acceptBacklog_)) { + errno_copy = THRIFT_GET_SOCKET_ERROR; + GlobalOutput.perror("TServerSocket::listen() listen() ", errno_copy); + close(); + throw TTransportException(TTransportException::NOT_OPEN, "Could not listen", errno_copy); + } + + // The socket is now listening! +} + +int TServerSocket::getPort() { + return port_; +} + +shared_ptr TServerSocket::acceptImpl() { + if (serverSocket_ == THRIFT_INVALID_SOCKET) { + throw TTransportException(TTransportException::NOT_OPEN, "TServerSocket not listening"); + } + + struct THRIFT_POLLFD fds[2]; + + int maxEintrs = 5; + int numEintrs = 0; + + while (true) { + std::memset(fds, 0, sizeof(fds)); + fds[0].fd = serverSocket_; + fds[0].events = THRIFT_POLLIN; + if (interruptSockReader_ != THRIFT_INVALID_SOCKET) { + fds[1].fd = interruptSockReader_; + fds[1].events = THRIFT_POLLIN; + } + /* + TODO: if THRIFT_EINTR is received, we'll restart the timeout. + To be accurate, we need to fix this in the future. + */ + int ret = THRIFT_POLL(fds, 2, accTimeout_); + + if (ret < 0) { + // error cases + if (THRIFT_GET_SOCKET_ERROR == THRIFT_EINTR && (numEintrs++ < maxEintrs)) { + // THRIFT_EINTR needs to be handled manually and we can tolerate + // a certain number + continue; + } + int errno_copy = THRIFT_GET_SOCKET_ERROR; + GlobalOutput.perror("TServerSocket::acceptImpl() THRIFT_POLL() ", errno_copy); + throw TTransportException(TTransportException::UNKNOWN, "Unknown", errno_copy); + } else if (ret > 0) { + // Check for an interrupt signal + if (interruptSockReader_ != THRIFT_INVALID_SOCKET && (fds[1].revents & THRIFT_POLLIN)) { + int8_t buf; + if (-1 == recv(interruptSockReader_, cast_sockopt(&buf), sizeof(int8_t), 0)) { + GlobalOutput.perror("TServerSocket::acceptImpl() recv() interrupt ", + THRIFT_GET_SOCKET_ERROR); + } + throw TTransportException(TTransportException::INTERRUPTED); + } + + // Check for the actual server socket being ready + if (fds[0].revents & THRIFT_POLLIN) { + break; + } + } else { + GlobalOutput("TServerSocket::acceptImpl() THRIFT_POLL 0"); + throw TTransportException(TTransportException::UNKNOWN); + } + } + + struct sockaddr_storage clientAddress; + int size = sizeof(clientAddress); + THRIFT_SOCKET clientSocket + = ::accept(serverSocket_, (struct sockaddr*)&clientAddress, (socklen_t*)&size); + + if (clientSocket == THRIFT_INVALID_SOCKET) { + int errno_copy = THRIFT_GET_SOCKET_ERROR; + GlobalOutput.perror("TServerSocket::acceptImpl() ::accept() ", errno_copy); + throw TTransportException(TTransportException::UNKNOWN, "accept()", errno_copy); + } + + // Make sure client socket is blocking + int flags = THRIFT_FCNTL(clientSocket, THRIFT_F_GETFL, 0); + if (flags == -1) { + int errno_copy = THRIFT_GET_SOCKET_ERROR; + ::THRIFT_CLOSESOCKET(clientSocket); + GlobalOutput.perror("TServerSocket::acceptImpl() THRIFT_FCNTL() THRIFT_F_GETFL ", errno_copy); + throw TTransportException(TTransportException::UNKNOWN, + "THRIFT_FCNTL(THRIFT_F_GETFL)", + errno_copy); + } + + if (-1 == THRIFT_FCNTL(clientSocket, THRIFT_F_SETFL, flags & ~THRIFT_O_NONBLOCK)) { + int errno_copy = THRIFT_GET_SOCKET_ERROR; + ::THRIFT_CLOSESOCKET(clientSocket); + GlobalOutput + .perror("TServerSocket::acceptImpl() THRIFT_FCNTL() THRIFT_F_SETFL ~THRIFT_O_NONBLOCK ", + errno_copy); + throw TTransportException(TTransportException::UNKNOWN, + "THRIFT_FCNTL(THRIFT_F_SETFL)", + errno_copy); + } + + shared_ptr client = createSocket(clientSocket); + if (sendTimeout_ > 0) { + client->setSendTimeout(sendTimeout_); + } + if (recvTimeout_ > 0) { + client->setRecvTimeout(recvTimeout_); + } + if (keepAlive_) { + client->setKeepAlive(keepAlive_); + } + client->setCachedAddress((sockaddr*)&clientAddress, size); + + if (acceptCallback_) + acceptCallback_(clientSocket); + + return client; +} + +shared_ptr TServerSocket::createSocket(THRIFT_SOCKET clientSocket) { + if (interruptableChildren_) { + return std::make_shared(clientSocket, pChildInterruptSockReader_); + } else { + return std::make_shared(clientSocket); + } +} + +void TServerSocket::notify(THRIFT_SOCKET notifySocket) { + if (notifySocket != THRIFT_INVALID_SOCKET) { + int8_t byte = 0; + if (-1 == send(notifySocket, cast_sockopt(&byte), sizeof(int8_t), 0)) { + GlobalOutput.perror("TServerSocket::notify() send() ", THRIFT_GET_SOCKET_ERROR); + } + } +} + +void TServerSocket::interrupt() { + concurrency::Guard g(rwMutex_); + if (interruptSockWriter_ != THRIFT_INVALID_SOCKET) { + notify(interruptSockWriter_); + } +} + +void TServerSocket::interruptChildren() { + concurrency::Guard g(rwMutex_); + if (childInterruptSockWriter_ != THRIFT_INVALID_SOCKET) { + notify(childInterruptSockWriter_); + } +} + +void TServerSocket::close() { + concurrency::Guard g(rwMutex_); + if (serverSocket_ != THRIFT_INVALID_SOCKET) { + shutdown(serverSocket_, THRIFT_SHUT_RDWR); + ::THRIFT_CLOSESOCKET(serverSocket_); + } + if (interruptSockWriter_ != THRIFT_INVALID_SOCKET) { + ::THRIFT_CLOSESOCKET(interruptSockWriter_); + } + if (interruptSockReader_ != THRIFT_INVALID_SOCKET) { + ::THRIFT_CLOSESOCKET(interruptSockReader_); + } + if (childInterruptSockWriter_ != THRIFT_INVALID_SOCKET) { + ::THRIFT_CLOSESOCKET(childInterruptSockWriter_); + } + serverSocket_ = THRIFT_INVALID_SOCKET; + interruptSockWriter_ = THRIFT_INVALID_SOCKET; + interruptSockReader_ = THRIFT_INVALID_SOCKET; + childInterruptSockWriter_ = THRIFT_INVALID_SOCKET; + pChildInterruptSockReader_.reset(); + listening_ = false; +} +} +} +} // apache::thrift::transport diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TServerSocket.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TServerSocket.h new file mode 100644 index 000000000..d64096803 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TServerSocket.h @@ -0,0 +1,184 @@ +/* + * 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. + */ + +#ifndef _THRIFT_TRANSPORT_TSERVERSOCKET_H_ +#define _THRIFT_TRANSPORT_TSERVERSOCKET_H_ 1 + +#include +#include +#include + +#include +#ifdef HAVE_SYS_SOCKET_H +#include +#endif +#ifdef HAVE_NETDB_H +#include +#endif + +namespace apache { +namespace thrift { +namespace transport { + +class TSocket; + +class TGetAddrInfoWrapper { +public: + TGetAddrInfoWrapper(const char* node, const char* service, const struct addrinfo* hints); + + virtual ~TGetAddrInfoWrapper(); + + int init(); + const struct addrinfo* res(); + +private: + const char* node_; + const char* service_; + const struct addrinfo* hints_; + struct addrinfo* res_; +}; + +/** + * Server socket implementation of TServerTransport. Wrapper around a unix + * socket listen and accept calls. + * + */ +class TServerSocket : public TServerTransport { +public: + typedef std::function socket_func_t; + + const static int DEFAULT_BACKLOG = 1024; + + /** + * Constructor. + * + * @param port Port number to bind to + */ + TServerSocket(int port); + + /** + * Constructor. + * + * @param port Port number to bind to + * @param sendTimeout Socket send timeout + * @param recvTimeout Socket receive timeout + */ + TServerSocket(int port, int sendTimeout, int recvTimeout); + + /** + * Constructor. + * + * @param address Address to bind to + * @param port Port number to bind to + */ + TServerSocket(const std::string& address, int port); + + /** + * Constructor used for unix sockets. + * + * @param path Pathname for unix socket. + */ + TServerSocket(const std::string& path); + + ~TServerSocket() override; + + void setSendTimeout(int sendTimeout); + void setRecvTimeout(int recvTimeout); + + void setAcceptTimeout(int accTimeout); + void setAcceptBacklog(int accBacklog); + + void setRetryLimit(int retryLimit); + void setRetryDelay(int retryDelay); + + void setKeepAlive(bool keepAlive) { keepAlive_ = keepAlive; } + + void setTcpSendBuffer(int tcpSendBuffer); + void setTcpRecvBuffer(int tcpRecvBuffer); + + // listenCallback gets called just before listen, and after all Thrift + // setsockopt calls have been made. If you have custom setsockopt + // things that need to happen on the listening socket, this is the place to do it. + void setListenCallback(const socket_func_t& listenCallback) { listenCallback_ = listenCallback; } + + // acceptCallback gets called after each accept call, on the newly created socket. + // It is called after all Thrift setsockopt calls have been made. If you have + // custom setsockopt things that need to happen on the accepted + // socket, this is the place to do it. + void setAcceptCallback(const socket_func_t& acceptCallback) { acceptCallback_ = acceptCallback; } + + // When enabled (the default), new children TSockets will be constructed so + // they can be interrupted by TServerTransport::interruptChildren(). + // This is more expensive in terms of system calls (poll + recv) however + // ensures a connected client cannot interfere with TServer::stop(). + // + // When disabled, TSocket children do not incur an additional poll() call. + // Server-side reads are more efficient, however a client can interfere with + // the server's ability to shutdown properly by staying connected. + // + // Must be called before listen(); mode cannot be switched after that. + // \throws std::logic_error if listen() has been called + void setInterruptableChildren(bool enable); + + THRIFT_SOCKET getSocketFD() override { return serverSocket_; } + + int getPort(); + + void listen() override; + void interrupt() override; + void interruptChildren() override; + void close() override; + +protected: + std::shared_ptr acceptImpl() override; + virtual std::shared_ptr createSocket(THRIFT_SOCKET client); + bool interruptableChildren_; + std::shared_ptr pChildInterruptSockReader_; // if interruptableChildren_ this is shared with child TSockets + +private: + void notify(THRIFT_SOCKET notifySock); + + int port_; + std::string address_; + std::string path_; + THRIFT_SOCKET serverSocket_; + int acceptBacklog_; + int sendTimeout_; + int recvTimeout_; + int accTimeout_; + int retryLimit_; + int retryDelay_; + int tcpSendBuffer_; + int tcpRecvBuffer_; + bool keepAlive_; + bool listening_; + + concurrency::Mutex rwMutex_; // thread-safe interrupt + THRIFT_SOCKET interruptSockWriter_; // is notified on interrupt() + THRIFT_SOCKET interruptSockReader_; // is used in select/poll with serverSocket_ for interruptability + THRIFT_SOCKET childInterruptSockWriter_; // is notified on interruptChildren() + + socket_func_t listenCallback_; + socket_func_t acceptCallback_; +}; +} +} +} // apache::thrift::transport + +#endif // #ifndef _THRIFT_TRANSPORT_TSERVERSOCKET_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TServerTransport.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TServerTransport.h new file mode 100644 index 000000000..f465bb38a --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TServerTransport.h @@ -0,0 +1,113 @@ +/* + * 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. + */ + +#ifndef _THRIFT_TRANSPORT_TSERVERTRANSPORT_H_ +#define _THRIFT_TRANSPORT_TSERVERTRANSPORT_H_ 1 + +#include +#include + +namespace apache { +namespace thrift { +namespace transport { + +/** + * Server transport framework. A server needs to have some facility for + * creating base transports to read/write from. The server is expected + * to keep track of TTransport children that it creates for purposes of + * controlling their lifetime. + */ +class TServerTransport { +public: + virtual ~TServerTransport() = default; + + /** + * Starts the server transport listening for new connections. Prior to this + * call most transports will not return anything when accept is called. + * + * @throws TTransportException if we were unable to listen + */ + virtual void listen() {} + + /** + * Gets a new dynamically allocated transport object and passes it to the + * caller. Note that it is the explicit duty of the caller to free the + * allocated object. The returned TTransport object must always be in the + * opened state. NULL should never be returned, instead an Exception should + * always be thrown. + * + * @return A new TTransport object + * @throws TTransportException if there is an error + */ + std::shared_ptr accept() { + std::shared_ptr result = acceptImpl(); + if (!result) { + throw TTransportException("accept() may not return NULL"); + } + return result; + } + + /** + * For "smart" TServerTransport implementations that work in a multi + * threaded context this can be used to break out of an accept() call. + * It is expected that the transport will throw a TTransportException + * with the INTERRUPTED error code. + * + * This will not make an attempt to interrupt any TTransport children. + */ + virtual void interrupt() {} + + /** + * This will interrupt the children created by the server transport. + * allowing them to break out of any blocking data reception call. + * It is expected that the children will throw a TTransportException + * with the INTERRUPTED error code. + */ + virtual void interruptChildren() {} + + /** + * Utility method + * + * @return server socket file descriptor + * @throw TTransportException If an error occurs + */ + + virtual THRIFT_SOCKET getSocketFD() { return -1; } + + /** + * Closes this transport such that future calls to accept will do nothing. + */ + virtual void close() = 0; + +protected: + TServerTransport() = default; + + /** + * Subclasses should implement this function for accept. + * + * @return A newly allocated TTransport object + * @throw TTransportException If an error occurs + */ + virtual std::shared_ptr acceptImpl() = 0; +}; +} +} +} // apache::thrift::transport + +#endif // #ifndef _THRIFT_TRANSPORT_TSERVERTRANSPORT_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TShortReadTransport.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TShortReadTransport.h new file mode 100644 index 000000000..185c78dc7 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TShortReadTransport.h @@ -0,0 +1,82 @@ +/* + * 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. + */ + +#ifndef _THRIFT_TRANSPORT_TSHORTREADTRANSPORT_H_ +#define _THRIFT_TRANSPORT_TSHORTREADTRANSPORT_H_ 1 + +#include + +#include +#include + +namespace apache { +namespace thrift { +namespace transport { +namespace test { + +/** + * This class is only meant for testing. It wraps another transport. + * Calls to read are passed through with some probability. Otherwise, + * the read amount is randomly reduced before being passed through. + * + */ +class TShortReadTransport : public TVirtualTransport { +public: + TShortReadTransport(std::shared_ptr transport, double full_prob) + : transport_(transport), fullProb_(full_prob) {} + + bool isOpen() const override { return transport_->isOpen(); } + + bool peek() override { return transport_->peek(); } + + void open() override { transport_->open(); } + + void close() override { transport_->close(); } + + uint32_t read(uint8_t* buf, uint32_t len) { + if (len == 0) { + return 0; + } + + if (rand() / (double)RAND_MAX >= fullProb_) { + len = 1 + rand() % len; + } + return transport_->read(buf, len); + } + + void write(const uint8_t* buf, uint32_t len) { transport_->write(buf, len); } + + void flush() override { transport_->flush(); } + + const uint8_t* borrow(uint8_t* buf, uint32_t* len) { return transport_->borrow(buf, len); } + + void consume(uint32_t len) { return transport_->consume(len); } + + std::shared_ptr getUnderlyingTransport() { return transport_; } + +protected: + std::shared_ptr transport_; + double fullProb_; +}; +} +} +} +} // apache::thrift::transport::test + +#endif // #ifndef _THRIFT_TRANSPORT_TSHORTREADTRANSPORT_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TSimpleFileTransport.cpp b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TSimpleFileTransport.cpp new file mode 100644 index 000000000..4b1399e14 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TSimpleFileTransport.cpp @@ -0,0 +1,67 @@ +/* + * 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. + */ + +#include + +#include + +#include +#ifdef HAVE_SYS_STAT_H +#include +#endif +#include + +#ifdef _WIN32 +#include +#endif + +namespace apache { +namespace thrift { +namespace transport { + +TSimpleFileTransport::TSimpleFileTransport(const std::string& path, bool read, bool write) + : TFDTransport(-1, TFDTransport::CLOSE_ON_DESTROY) { + int flags = 0; + if (read && write) { + flags = O_RDWR; + } else if (read) { + flags = O_RDONLY; + } else if (write) { + flags = O_WRONLY; + } else { + throw TTransportException("Neither READ nor WRITE specified"); + } + if (write) { + flags |= O_CREAT | O_APPEND; + } +#ifndef _WIN32 + mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; +#else + int mode = _S_IREAD | _S_IWRITE; +#endif + int fd = ::THRIFT_OPEN(path.c_str(), flags, mode); + if (fd < 0) { + throw TTransportException("failed to open file for writing: " + path); + } + setFD(fd); + open(); +} +} +} +} // apache::thrift::transport diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TSimpleFileTransport.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TSimpleFileTransport.h new file mode 100644 index 000000000..32e18974d --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TSimpleFileTransport.h @@ -0,0 +1,42 @@ +/* + * 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. + */ + +#ifndef _THRIFT_TRANSPORT_TSIMPLEFILETRANSPORT_H_ +#define _THRIFT_TRANSPORT_TSIMPLEFILETRANSPORT_H_ 1 + +#include + +namespace apache { +namespace thrift { +namespace transport { + +/** + * Dead-simple wrapper around a file. + * + * Writeable files are opened with O_CREAT and O_APPEND + */ +class TSimpleFileTransport : public TFDTransport { +public: + TSimpleFileTransport(const std::string& path, bool read = true, bool write = false); +}; +} +} +} // apache::thrift::transport + +#endif // _THRIFT_TRANSPORT_TSIMPLEFILETRANSPORT_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TSocket.cpp b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TSocket.cpp new file mode 100644 index 000000000..977834d48 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TSocket.cpp @@ -0,0 +1,959 @@ +/* + * 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. + */ + +#include + +#include +#include +#ifdef HAVE_SYS_IOCTL_H +#include +#endif +#ifdef HAVE_SYS_SOCKET_H +#include +#endif +#ifdef HAVE_SYS_UN_H +#include +#endif +#ifdef HAVE_SYS_POLL_H +#include +#endif +#include +#ifdef HAVE_NETINET_IN_H +#include +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif +#include + +#include +#include +#include +#include + +#ifndef SOCKOPT_CAST_T +#ifndef _WIN32 +#define SOCKOPT_CAST_T void +#else +#define SOCKOPT_CAST_T char +#endif // _WIN32 +#endif + +template +inline const SOCKOPT_CAST_T* const_cast_sockopt(const T* v) { + return reinterpret_cast(v); +} + +template +inline SOCKOPT_CAST_T* cast_sockopt(T* v) { + return reinterpret_cast(v); +} + +using std::string; + +namespace apache { +namespace thrift { +namespace transport { + +/** + * TSocket implementation. + * + */ + +TSocket::TSocket(const string& host, int port) + : host_(host), + port_(port), + socket_(THRIFT_INVALID_SOCKET), + peerPort_(0), + connTimeout_(0), + sendTimeout_(0), + recvTimeout_(0), + keepAlive_(false), + lingerOn_(1), + lingerVal_(0), + noDelay_(1), + maxRecvRetries_(5) { +} + +TSocket::TSocket(const string& path) + : port_(0), + path_(path), + socket_(THRIFT_INVALID_SOCKET), + peerPort_(0), + connTimeout_(0), + sendTimeout_(0), + recvTimeout_(0), + keepAlive_(false), + lingerOn_(1), + lingerVal_(0), + noDelay_(1), + maxRecvRetries_(5) { + cachedPeerAddr_.ipv4.sin_family = AF_UNSPEC; +} + +TSocket::TSocket() + : port_(0), + socket_(THRIFT_INVALID_SOCKET), + peerPort_(0), + connTimeout_(0), + sendTimeout_(0), + recvTimeout_(0), + keepAlive_(false), + lingerOn_(1), + lingerVal_(0), + noDelay_(1), + maxRecvRetries_(5) { + cachedPeerAddr_.ipv4.sin_family = AF_UNSPEC; +} + +TSocket::TSocket(THRIFT_SOCKET socket) + : port_(0), + socket_(socket), + peerPort_(0), + connTimeout_(0), + sendTimeout_(0), + recvTimeout_(0), + keepAlive_(false), + lingerOn_(1), + lingerVal_(0), + noDelay_(1), + maxRecvRetries_(5) { + cachedPeerAddr_.ipv4.sin_family = AF_UNSPEC; +#ifdef SO_NOSIGPIPE + { + int one = 1; + setsockopt(socket_, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(one)); + } +#endif +} + +TSocket::TSocket(THRIFT_SOCKET socket, std::shared_ptr interruptListener) + : port_(0), + socket_(socket), + peerPort_(0), + interruptListener_(interruptListener), + connTimeout_(0), + sendTimeout_(0), + recvTimeout_(0), + keepAlive_(false), + lingerOn_(1), + lingerVal_(0), + noDelay_(1), + maxRecvRetries_(5) { + cachedPeerAddr_.ipv4.sin_family = AF_UNSPEC; +#ifdef SO_NOSIGPIPE + { + int one = 1; + setsockopt(socket_, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(one)); + } +#endif +} + +TSocket::~TSocket() { + close(); +} + +bool TSocket::hasPendingDataToRead() { + if (!isOpen()) { + return false; + } + + int32_t retries = 0; + THRIFT_IOCTL_SOCKET_NUM_BYTES_TYPE numBytesAvailable; +try_again: + int r = THRIFT_IOCTL_SOCKET(socket_, FIONREAD, &numBytesAvailable); + if (r == -1) { + int errno_copy = THRIFT_GET_SOCKET_ERROR; + if (errno_copy == THRIFT_EINTR && (retries++ < maxRecvRetries_)) { + goto try_again; + } + GlobalOutput.perror("TSocket::hasPendingDataToRead() THRIFT_IOCTL_SOCKET() " + getSocketInfo(), errno_copy); + throw TTransportException(TTransportException::UNKNOWN, "Unknown", errno_copy); + } + return numBytesAvailable > 0; +} + +bool TSocket::isOpen() const { + return (socket_ != THRIFT_INVALID_SOCKET); +} + +bool TSocket::peek() { + if (!isOpen()) { + return false; + } + if (interruptListener_) { + for (int retries = 0;;) { + struct THRIFT_POLLFD fds[2]; + std::memset(fds, 0, sizeof(fds)); + fds[0].fd = socket_; + fds[0].events = THRIFT_POLLIN; + fds[1].fd = *(interruptListener_.get()); + fds[1].events = THRIFT_POLLIN; + int ret = THRIFT_POLL(fds, 2, (recvTimeout_ == 0) ? -1 : recvTimeout_); + int errno_copy = THRIFT_GET_SOCKET_ERROR; + if (ret < 0) { + // error cases + if (errno_copy == THRIFT_EINTR && (retries++ < maxRecvRetries_)) { + continue; + } + GlobalOutput.perror("TSocket::peek() THRIFT_POLL() ", errno_copy); + throw TTransportException(TTransportException::UNKNOWN, "Unknown", errno_copy); + } else if (ret > 0) { + // Check the interruptListener + if (fds[1].revents & THRIFT_POLLIN) { + return false; + } + // There must be data or a disconnection, fall through to the PEEK + break; + } else { + // timeout + return false; + } + } + } + + // Check to see if data is available or if the remote side closed + uint8_t buf; + int r = static_cast(recv(socket_, cast_sockopt(&buf), 1, MSG_PEEK)); + if (r == -1) { + int errno_copy = THRIFT_GET_SOCKET_ERROR; +#if defined __FreeBSD__ || defined __MACH__ + /* shigin: + * freebsd returns -1 and THRIFT_ECONNRESET if socket was closed by + * the other side + */ + if (errno_copy == THRIFT_ECONNRESET) { + return false; + } +#endif + GlobalOutput.perror("TSocket::peek() recv() " + getSocketInfo(), errno_copy); + throw TTransportException(TTransportException::UNKNOWN, "recv()", errno_copy); + } + return (r > 0); +} + +void TSocket::openConnection(struct addrinfo* res) { + + if (isOpen()) { + return; + } + + if (!path_.empty()) { + socket_ = socket(PF_UNIX, SOCK_STREAM, IPPROTO_IP); + } else { + socket_ = socket(res->ai_family, res->ai_socktype, res->ai_protocol); + } + + if (socket_ == THRIFT_INVALID_SOCKET) { + int errno_copy = THRIFT_GET_SOCKET_ERROR; + GlobalOutput.perror("TSocket::open() socket() " + getSocketInfo(), errno_copy); + throw TTransportException(TTransportException::NOT_OPEN, "socket()", errno_copy); + } + + // Send timeout + if (sendTimeout_ > 0) { + setSendTimeout(sendTimeout_); + } + + // Recv timeout + if (recvTimeout_ > 0) { + setRecvTimeout(recvTimeout_); + } + + if (keepAlive_) { + setKeepAlive(keepAlive_); + } + + // Linger + setLinger(lingerOn_, lingerVal_); + + // No delay + setNoDelay(noDelay_); + +#ifdef SO_NOSIGPIPE + { + int one = 1; + setsockopt(socket_, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(one)); + } +#endif + +// Uses a low min RTO if asked to. +#ifdef TCP_LOW_MIN_RTO + if (getUseLowMinRto()) { + int one = 1; + setsockopt(socket_, IPPROTO_TCP, TCP_LOW_MIN_RTO, &one, sizeof(one)); + } +#endif + + // Set the socket to be non blocking for connect if a timeout exists + int flags = THRIFT_FCNTL(socket_, THRIFT_F_GETFL, 0); + if (connTimeout_ > 0) { + if (-1 == THRIFT_FCNTL(socket_, THRIFT_F_SETFL, flags | THRIFT_O_NONBLOCK)) { + int errno_copy = THRIFT_GET_SOCKET_ERROR; + GlobalOutput.perror("TSocket::open() THRIFT_FCNTL() " + getSocketInfo(), errno_copy); + throw TTransportException(TTransportException::NOT_OPEN, "THRIFT_FCNTL() failed", errno_copy); + } + } else { + if (-1 == THRIFT_FCNTL(socket_, THRIFT_F_SETFL, flags & ~THRIFT_O_NONBLOCK)) { + int errno_copy = THRIFT_GET_SOCKET_ERROR; + GlobalOutput.perror("TSocket::open() THRIFT_FCNTL " + getSocketInfo(), errno_copy); + throw TTransportException(TTransportException::NOT_OPEN, "THRIFT_FCNTL() failed", errno_copy); + } + } + + // Connect the socket + int ret; + if (!path_.empty()) { + +#ifndef _WIN32 + size_t len = path_.size() + 1; + if (len > sizeof(((sockaddr_un*)nullptr)->sun_path)) { + int errno_copy = THRIFT_GET_SOCKET_ERROR; + GlobalOutput.perror("TSocket::open() Unix Domain socket path too long", errno_copy); + throw TTransportException(TTransportException::NOT_OPEN, " Unix Domain socket path too long"); + } + + struct sockaddr_un address; + address.sun_family = AF_UNIX; + memcpy(address.sun_path, path_.c_str(), len); + + auto structlen = static_cast(sizeof(address)); + + if (!address.sun_path[0]) { // abstract namespace socket +#ifdef __linux__ + // sun_path is not null-terminated in this case and structlen determines its length + structlen -= sizeof(address.sun_path) - len; +#else + GlobalOutput.perror("TSocket::open() Abstract Namespace Domain sockets only supported on linux: ", -99); + throw TTransportException(TTransportException::NOT_OPEN, + " Abstract Namespace Domain socket path not supported"); +#endif + } + + ret = connect(socket_, (struct sockaddr*)&address, structlen); +#else + GlobalOutput.perror("TSocket::open() Unix Domain socket path not supported on windows", -99); + throw TTransportException(TTransportException::NOT_OPEN, + " Unix Domain socket path not supported"); +#endif + + } else { + ret = connect(socket_, res->ai_addr, static_cast(res->ai_addrlen)); + } + + // success case + if (ret == 0) { + goto done; + } + + if ((THRIFT_GET_SOCKET_ERROR != THRIFT_EINPROGRESS) + && (THRIFT_GET_SOCKET_ERROR != THRIFT_EWOULDBLOCK)) { + int errno_copy = THRIFT_GET_SOCKET_ERROR; + GlobalOutput.perror("TSocket::open() connect() " + getSocketInfo(), errno_copy); + throw TTransportException(TTransportException::NOT_OPEN, "connect() failed", errno_copy); + } + + struct THRIFT_POLLFD fds[1]; + std::memset(fds, 0, sizeof(fds)); + fds[0].fd = socket_; + fds[0].events = THRIFT_POLLOUT; + ret = THRIFT_POLL(fds, 1, connTimeout_); + + if (ret > 0) { + // Ensure the socket is connected and that there are no errors set + int val; + socklen_t lon; + lon = sizeof(int); + int ret2 = getsockopt(socket_, SOL_SOCKET, SO_ERROR, cast_sockopt(&val), &lon); + if (ret2 == -1) { + int errno_copy = THRIFT_GET_SOCKET_ERROR; + GlobalOutput.perror("TSocket::open() getsockopt() " + getSocketInfo(), errno_copy); + throw TTransportException(TTransportException::NOT_OPEN, "getsockopt()", errno_copy); + } + // no errors on socket, go to town + if (val == 0) { + goto done; + } + GlobalOutput.perror("TSocket::open() error on socket (after THRIFT_POLL) " + getSocketInfo(), + val); + throw TTransportException(TTransportException::NOT_OPEN, "socket open() error", val); + } else if (ret == 0) { + // socket timed out + string errStr = "TSocket::open() timed out " + getSocketInfo(); + GlobalOutput(errStr.c_str()); + throw TTransportException(TTransportException::NOT_OPEN, "open() timed out"); + } else { + // error on THRIFT_POLL() + int errno_copy = THRIFT_GET_SOCKET_ERROR; + GlobalOutput.perror("TSocket::open() THRIFT_POLL() " + getSocketInfo(), errno_copy); + throw TTransportException(TTransportException::NOT_OPEN, "THRIFT_POLL() failed", errno_copy); + } + +done: + // Set socket back to normal mode (blocking) + if (-1 == THRIFT_FCNTL(socket_, THRIFT_F_SETFL, flags)) { + int errno_copy = THRIFT_GET_SOCKET_ERROR; + GlobalOutput.perror("TSocket::open() THRIFT_FCNTL " + getSocketInfo(), errno_copy); + throw TTransportException(TTransportException::NOT_OPEN, "THRIFT_FCNTL() failed", errno_copy); + } + + if (path_.empty()) { + setCachedAddress(res->ai_addr, static_cast(res->ai_addrlen)); + } +} + +void TSocket::open() { + if (isOpen()) { + return; + } + if (!path_.empty()) { + unix_open(); + } else { + local_open(); + } +} + +void TSocket::unix_open() { + if (!path_.empty()) { + // Unix Domain SOcket does not need addrinfo struct, so we pass NULL + openConnection(nullptr); + } +} + +void TSocket::local_open() { + +#ifdef _WIN32 + TWinsockSingleton::create(); +#endif // _WIN32 + + if (isOpen()) { + return; + } + + // Validate port number + if (port_ < 0 || port_ > 0xFFFF) { + throw TTransportException(TTransportException::BAD_ARGS, "Specified port is invalid"); + } + + struct addrinfo hints, *res, *res0; + res = nullptr; + res0 = nullptr; + int error; + char port[sizeof("65535")]; + std::memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG; + sprintf(port, "%d", port_); + + error = getaddrinfo(host_.c_str(), port, &hints, &res0); + +#ifdef _WIN32 + if (error == WSANO_DATA) { + hints.ai_flags &= ~AI_ADDRCONFIG; + error = getaddrinfo(host_.c_str(), port, &hints, &res0); + } +#endif + + if (error) { + string errStr = "TSocket::open() getaddrinfo() " + getSocketInfo() + + string(THRIFT_GAI_STRERROR(error)); + GlobalOutput(errStr.c_str()); + close(); + throw TTransportException(TTransportException::NOT_OPEN, + "Could not resolve host for client socket."); + } + + // Cycle through all the returned addresses until one + // connects or push the exception up. + for (res = res0; res; res = res->ai_next) { + try { + openConnection(res); + break; + } catch (TTransportException&) { + if (res->ai_next) { + close(); + } else { + close(); + freeaddrinfo(res0); // cleanup on failure + throw; + } + } + } + + // Free address structure memory + freeaddrinfo(res0); +} + +void TSocket::close() { + if (socket_ != THRIFT_INVALID_SOCKET) { + shutdown(socket_, THRIFT_SHUT_RDWR); + ::THRIFT_CLOSESOCKET(socket_); + } + socket_ = THRIFT_INVALID_SOCKET; +} + +void TSocket::setSocketFD(THRIFT_SOCKET socket) { + if (socket_ != THRIFT_INVALID_SOCKET) { + close(); + } + socket_ = socket; +} + +uint32_t TSocket::read(uint8_t* buf, uint32_t len) { + if (socket_ == THRIFT_INVALID_SOCKET) { + throw TTransportException(TTransportException::NOT_OPEN, "Called read on non-open socket"); + } + + int32_t retries = 0; + + // THRIFT_EAGAIN can be signalled both when a timeout has occurred and when + // the system is out of resources (an awesome undocumented feature). + // The following is an approximation of the time interval under which + // THRIFT_EAGAIN is taken to indicate an out of resources error. + uint32_t eagainThresholdMicros = 0; + if (recvTimeout_) { + // if a readTimeout is specified along with a max number of recv retries, then + // the threshold will ensure that the read timeout is not exceeded even in the + // case of resource errors + eagainThresholdMicros = (recvTimeout_ * 1000) / ((maxRecvRetries_ > 0) ? maxRecvRetries_ : 2); + } + +try_again: + // Read from the socket + struct timeval begin; + if (recvTimeout_ > 0) { + THRIFT_GETTIMEOFDAY(&begin, nullptr); + } else { + // if there is no read timeout we don't need the TOD to determine whether + // an THRIFT_EAGAIN is due to a timeout or an out-of-resource condition. + begin.tv_sec = begin.tv_usec = 0; + } + + int got = 0; + + if (interruptListener_) { + struct THRIFT_POLLFD fds[2]; + std::memset(fds, 0, sizeof(fds)); + fds[0].fd = socket_; + fds[0].events = THRIFT_POLLIN; + fds[1].fd = *(interruptListener_.get()); + fds[1].events = THRIFT_POLLIN; + + int ret = THRIFT_POLL(fds, 2, (recvTimeout_ == 0) ? -1 : recvTimeout_); + int errno_copy = THRIFT_GET_SOCKET_ERROR; + if (ret < 0) { + // error cases + if (errno_copy == THRIFT_EINTR && (retries++ < maxRecvRetries_)) { + goto try_again; + } + GlobalOutput.perror("TSocket::read() THRIFT_POLL() ", errno_copy); + throw TTransportException(TTransportException::UNKNOWN, "Unknown", errno_copy); + } else if (ret > 0) { + // Check the interruptListener + if (fds[1].revents & THRIFT_POLLIN) { + throw TTransportException(TTransportException::INTERRUPTED, "Interrupted"); + } + } else /* ret == 0 */ { + throw TTransportException(TTransportException::TIMED_OUT, "THRIFT_EAGAIN (timed out)"); + } + + // falling through means there is something to recv and it cannot block + } + + got = static_cast(recv(socket_, cast_sockopt(buf), len, 0)); + // THRIFT_GETTIMEOFDAY can change THRIFT_GET_SOCKET_ERROR + int errno_copy = THRIFT_GET_SOCKET_ERROR; + + // Check for error on read + if (got < 0) { + if (errno_copy == THRIFT_EAGAIN) { + // if no timeout we can assume that resource exhaustion has occurred. + if (recvTimeout_ == 0) { + throw TTransportException(TTransportException::TIMED_OUT, + "THRIFT_EAGAIN (unavailable resources)"); + } + // check if this is the lack of resources or timeout case + struct timeval end; + THRIFT_GETTIMEOFDAY(&end, nullptr); + auto readElapsedMicros = static_cast(((end.tv_sec - begin.tv_sec) * 1000 * 1000) + + (end.tv_usec - begin.tv_usec)); + + if (!eagainThresholdMicros || (readElapsedMicros < eagainThresholdMicros)) { + if (retries++ < maxRecvRetries_) { + THRIFT_SLEEP_USEC(50); + goto try_again; + } else { + throw TTransportException(TTransportException::TIMED_OUT, + "THRIFT_EAGAIN (unavailable resources)"); + } + } else { + // infer that timeout has been hit + throw TTransportException(TTransportException::TIMED_OUT, "THRIFT_EAGAIN (timed out)"); + } + } + + // If interrupted, try again + if (errno_copy == THRIFT_EINTR && retries++ < maxRecvRetries_) { + goto try_again; + } + + if (errno_copy == THRIFT_ECONNRESET) { + return 0; + } + + // This ish isn't open + if (errno_copy == THRIFT_ENOTCONN) { + throw TTransportException(TTransportException::NOT_OPEN, "THRIFT_ENOTCONN"); + } + + // Timed out! + if (errno_copy == THRIFT_ETIMEDOUT) { + throw TTransportException(TTransportException::TIMED_OUT, "THRIFT_ETIMEDOUT"); + } + + // Now it's not a try again case, but a real probblez + GlobalOutput.perror("TSocket::read() recv() " + getSocketInfo(), errno_copy); + + // Some other error, whatevz + throw TTransportException(TTransportException::UNKNOWN, "Unknown", errno_copy); + } + + return got; +} + +void TSocket::write(const uint8_t* buf, uint32_t len) { + uint32_t sent = 0; + + while (sent < len) { + uint32_t b = write_partial(buf + sent, len - sent); + if (b == 0) { + // This should only happen if the timeout set with SO_SNDTIMEO expired. + // Raise an exception. + throw TTransportException(TTransportException::TIMED_OUT, "send timeout expired"); + } + sent += b; + } +} + +uint32_t TSocket::write_partial(const uint8_t* buf, uint32_t len) { + if (socket_ == THRIFT_INVALID_SOCKET) { + throw TTransportException(TTransportException::NOT_OPEN, "Called write on non-open socket"); + } + + uint32_t sent = 0; + + int flags = 0; +#ifdef MSG_NOSIGNAL + // Note the use of MSG_NOSIGNAL to suppress SIGPIPE errors, instead we + // check for the THRIFT_EPIPE return condition and close the socket in that case + flags |= MSG_NOSIGNAL; +#endif // ifdef MSG_NOSIGNAL + + int b = static_cast(send(socket_, const_cast_sockopt(buf + sent), len - sent, flags)); + + if (b < 0) { + if (THRIFT_GET_SOCKET_ERROR == THRIFT_EWOULDBLOCK || THRIFT_GET_SOCKET_ERROR == THRIFT_EAGAIN) { + return 0; + } + // Fail on a send error + int errno_copy = THRIFT_GET_SOCKET_ERROR; + GlobalOutput.perror("TSocket::write_partial() send() " + getSocketInfo(), errno_copy); + + if (errno_copy == THRIFT_EPIPE || errno_copy == THRIFT_ECONNRESET + || errno_copy == THRIFT_ENOTCONN) { + throw TTransportException(TTransportException::NOT_OPEN, "write() send()", errno_copy); + } + + throw TTransportException(TTransportException::UNKNOWN, "write() send()", errno_copy); + } + + // Fail on blocked send + if (b == 0) { + throw TTransportException(TTransportException::NOT_OPEN, "Socket send returned 0."); + } + return b; +} + +std::string TSocket::getHost() { + return host_; +} + +int TSocket::getPort() { + return port_; +} + +void TSocket::setHost(string host) { + host_ = host; +} + +void TSocket::setPort(int port) { + port_ = port; +} + +void TSocket::setLinger(bool on, int linger) { + lingerOn_ = on; + lingerVal_ = linger; + if (socket_ == THRIFT_INVALID_SOCKET) { + return; + } + +#ifndef _WIN32 + struct linger l = {(lingerOn_ ? 1 : 0), lingerVal_}; +#else + struct linger l = {static_cast(lingerOn_ ? 1 : 0), static_cast(lingerVal_)}; +#endif + + int ret = setsockopt(socket_, SOL_SOCKET, SO_LINGER, cast_sockopt(&l), sizeof(l)); + if (ret == -1) { + int errno_copy + = THRIFT_GET_SOCKET_ERROR; // Copy THRIFT_GET_SOCKET_ERROR because we're allocating memory. + GlobalOutput.perror("TSocket::setLinger() setsockopt() " + getSocketInfo(), errno_copy); + } +} + +void TSocket::setNoDelay(bool noDelay) { + noDelay_ = noDelay; + if (socket_ == THRIFT_INVALID_SOCKET || !path_.empty()) { + return; + } + + // Set socket to NODELAY + int v = noDelay_ ? 1 : 0; + int ret = setsockopt(socket_, IPPROTO_TCP, TCP_NODELAY, cast_sockopt(&v), sizeof(v)); + if (ret == -1) { + int errno_copy + = THRIFT_GET_SOCKET_ERROR; // Copy THRIFT_GET_SOCKET_ERROR because we're allocating memory. + GlobalOutput.perror("TSocket::setNoDelay() setsockopt() " + getSocketInfo(), errno_copy); + } +} + +void TSocket::setConnTimeout(int ms) { + connTimeout_ = ms; +} + +void setGenericTimeout(THRIFT_SOCKET s, int timeout_ms, int optname) { + if (timeout_ms < 0) { + char errBuf[512]; + sprintf(errBuf, "TSocket::setGenericTimeout with negative input: %d\n", timeout_ms); + GlobalOutput(errBuf); + return; + } + + if (s == THRIFT_INVALID_SOCKET) { + return; + } + +#ifdef _WIN32 + DWORD platform_time = static_cast(timeout_ms); +#else + struct timeval platform_time = {(int)(timeout_ms / 1000), (int)((timeout_ms % 1000) * 1000)}; +#endif + + int ret = setsockopt(s, SOL_SOCKET, optname, cast_sockopt(&platform_time), sizeof(platform_time)); + if (ret == -1) { + int errno_copy + = THRIFT_GET_SOCKET_ERROR; // Copy THRIFT_GET_SOCKET_ERROR because we're allocating memory. + GlobalOutput.perror("TSocket::setGenericTimeout() setsockopt() ", errno_copy); + } +} + +void TSocket::setRecvTimeout(int ms) { + setGenericTimeout(socket_, ms, SO_RCVTIMEO); + recvTimeout_ = ms; +} + +void TSocket::setSendTimeout(int ms) { + setGenericTimeout(socket_, ms, SO_SNDTIMEO); + sendTimeout_ = ms; +} + +void TSocket::setKeepAlive(bool keepAlive) { + keepAlive_ = keepAlive; + + if (socket_ == THRIFT_INVALID_SOCKET) { + return; + } + + int value = keepAlive_; + int ret + = setsockopt(socket_, SOL_SOCKET, SO_KEEPALIVE, const_cast_sockopt(&value), sizeof(value)); + + if (ret == -1) { + int errno_copy + = THRIFT_GET_SOCKET_ERROR; // Copy THRIFT_GET_SOCKET_ERROR because we're allocating memory. + GlobalOutput.perror("TSocket::setKeepAlive() setsockopt() " + getSocketInfo(), errno_copy); + } +} + +void TSocket::setMaxRecvRetries(int maxRecvRetries) { + maxRecvRetries_ = maxRecvRetries; +} + +string TSocket::getSocketInfo() const { + std::ostringstream oss; + if (path_.empty()) { + if (host_.empty() || port_ == 0) { + oss << ""; + } else { + oss << ""; + } + } else { + oss << ""; + } + return oss.str(); +} + +std::string TSocket::getPeerHost() const { + if (peerHost_.empty() && path_.empty()) { + struct sockaddr_storage addr; + struct sockaddr* addrPtr; + socklen_t addrLen; + + if (socket_ == THRIFT_INVALID_SOCKET) { + return host_; + } + + addrPtr = getCachedAddress(&addrLen); + + if (addrPtr == nullptr) { + addrLen = sizeof(addr); + if (getpeername(socket_, (sockaddr*)&addr, &addrLen) != 0) { + return peerHost_; + } + addrPtr = (sockaddr*)&addr; + + const_cast(*this).setCachedAddress(addrPtr, addrLen); + } + + char clienthost[NI_MAXHOST]; + char clientservice[NI_MAXSERV]; + + getnameinfo((sockaddr*)addrPtr, + addrLen, + clienthost, + sizeof(clienthost), + clientservice, + sizeof(clientservice), + 0); + + peerHost_ = clienthost; + } + return peerHost_; +} + +std::string TSocket::getPeerAddress() const { + if (peerAddress_.empty() && path_.empty()) { + struct sockaddr_storage addr; + struct sockaddr* addrPtr; + socklen_t addrLen; + + if (socket_ == THRIFT_INVALID_SOCKET) { + return peerAddress_; + } + + addrPtr = getCachedAddress(&addrLen); + + if (addrPtr == nullptr) { + addrLen = sizeof(addr); + if (getpeername(socket_, (sockaddr*)&addr, &addrLen) != 0) { + return peerAddress_; + } + addrPtr = (sockaddr*)&addr; + + const_cast(*this).setCachedAddress(addrPtr, addrLen); + } + + char clienthost[NI_MAXHOST]; + char clientservice[NI_MAXSERV]; + + getnameinfo(addrPtr, + addrLen, + clienthost, + sizeof(clienthost), + clientservice, + sizeof(clientservice), + NI_NUMERICHOST | NI_NUMERICSERV); + + peerAddress_ = clienthost; + peerPort_ = std::atoi(clientservice); + } + return peerAddress_; +} + +int TSocket::getPeerPort() const { + getPeerAddress(); + return peerPort_; +} + +void TSocket::setCachedAddress(const sockaddr* addr, socklen_t len) { + if (!path_.empty()) { + return; + } + + switch (addr->sa_family) { + case AF_INET: + if (len == sizeof(sockaddr_in)) { + memcpy((void*)&cachedPeerAddr_.ipv4, (void*)addr, len); + } + break; + + case AF_INET6: + if (len == sizeof(sockaddr_in6)) { + memcpy((void*)&cachedPeerAddr_.ipv6, (void*)addr, len); + } + break; + } + peerAddress_.clear(); + peerHost_.clear(); +} + +sockaddr* TSocket::getCachedAddress(socklen_t* len) const { + switch (cachedPeerAddr_.ipv4.sin_family) { + case AF_INET: + *len = sizeof(sockaddr_in); + return (sockaddr*)&cachedPeerAddr_.ipv4; + + case AF_INET6: + *len = sizeof(sockaddr_in6); + return (sockaddr*)&cachedPeerAddr_.ipv6; + + default: + return nullptr; + } +} + +bool TSocket::useLowMinRto_ = false; +void TSocket::setUseLowMinRto(bool useLowMinRto) { + useLowMinRto_ = useLowMinRto; +} +bool TSocket::getUseLowMinRto() { + return useLowMinRto_; +} + +const std::string TSocket::getOrigin() const { + std::ostringstream oss; + oss << getPeerHost() << ":" << getPeerPort(); + return oss.str(); +} +} +} +} // apache::thrift::transport diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TSocket.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TSocket.h new file mode 100644 index 000000000..b0e8ade83 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TSocket.h @@ -0,0 +1,353 @@ +/* + * 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. + */ + +#ifndef _THRIFT_TRANSPORT_TSOCKET_H_ +#define _THRIFT_TRANSPORT_TSOCKET_H_ 1 + +#include + +#include +#include +#include +#include + +#ifdef HAVE_ARPA_INET_H +#include +#endif +#ifdef HAVE_SYS_TIME_H +#include +#endif +#ifdef HAVE_NETDB_H +#include +#endif + +namespace apache { +namespace thrift { +namespace transport { + +/** + * TCP Socket implementation of the TTransport interface. + * + */ +class TSocket : public TVirtualTransport { +public: + /** + * Constructs a new socket. Note that this does NOT actually connect the + * socket. + * + */ + TSocket(); + + /** + * Constructs a new socket. Note that this does NOT actually connect the + * socket. + * + * @param host An IP address or hostname to connect to + * @param port The port to connect on + */ + TSocket(const std::string& host, int port); + + /** + * Constructs a new Unix domain socket. + * Note that this does NOT actually connect the socket. + * + * @param path The Unix domain socket e.g. "/tmp/ThriftTest.binary.thrift" + */ + TSocket(const std::string& path); + + /** + * Destroyes the socket object, closing it if necessary. + */ + ~TSocket() override; + + /** + * Whether the socket is alive. + * + * @return Is the socket alive? + */ + bool isOpen() const override; + + /** + * Checks whether there is more data available in the socket to read. + * + * This call blocks until at least one byte is available or the socket is closed. + */ + bool peek() override; + + /** + * Creates and opens the UNIX socket. + * + * @throws TTransportException If the socket could not connect + */ + void open() override; + + /** + * Shuts down communications on the socket. + */ + void close() override; + + /** + * Determines whether there is pending data to read or not. + * + * This call does not block. + * \throws TTransportException of types: + * NOT_OPEN means the socket has been closed + * UNKNOWN means something unexpected happened + * \returns true if there is pending data to read, false otherwise + */ + virtual bool hasPendingDataToRead(); + + /** + * Reads from the underlying socket. + * \returns the number of bytes read or 0 indicates EOF + * \throws TTransportException of types: + * INTERRUPTED means the socket was interrupted + * out of a blocking call + * NOT_OPEN means the socket has been closed + * TIMED_OUT means the receive timeout expired + * UNKNOWN means something unexpected happened + */ + virtual uint32_t read(uint8_t* buf, uint32_t len); + + /** + * Writes to the underlying socket. Loops until done or fail. + */ + virtual void write(const uint8_t* buf, uint32_t len); + + /** + * Writes to the underlying socket. Does single send() and returns result. + */ + virtual uint32_t write_partial(const uint8_t* buf, uint32_t len); + + /** + * Get the host that the socket is connected to + * + * @return string host identifier + */ + std::string getHost(); + + /** + * Get the port that the socket is connected to + * + * @return int port number + */ + int getPort(); + + /** + * Set the host that socket will connect to + * + * @param host host identifier + */ + void setHost(std::string host); + + /** + * Set the port that socket will connect to + * + * @param port port number + */ + void setPort(int port); + + /** + * Controls whether the linger option is set on the socket. + * + * @param on Whether SO_LINGER is on + * @param linger If linger is active, the number of seconds to linger for + */ + void setLinger(bool on, int linger); + + /** + * Whether to enable/disable Nagle's algorithm. + * + * @param noDelay Whether or not to disable the algorithm. + * @return + */ + void setNoDelay(bool noDelay); + + /** + * Set the connect timeout + */ + void setConnTimeout(int ms); + + /** + * Set the receive timeout + */ + void setRecvTimeout(int ms); + + /** + * Set the send timeout + */ + void setSendTimeout(int ms); + + /** + * Set the max number of recv retries in case of an THRIFT_EAGAIN + * error + */ + void setMaxRecvRetries(int maxRecvRetries); + + /** + * Set SO_KEEPALIVE + */ + void setKeepAlive(bool keepAlive); + + /** + * Get socket information formatted as a string + */ + std::string getSocketInfo() const; + + /** + * Returns the DNS name of the host to which the socket is connected + */ + std::string getPeerHost() const; + + /** + * Returns the address of the host to which the socket is connected + */ + std::string getPeerAddress() const; + + /** + * Returns the port of the host to which the socket is connected + **/ + int getPeerPort() const; + + /** + * Returns the underlying socket file descriptor. + */ + THRIFT_SOCKET getSocketFD() { return socket_; } + + /** + * (Re-)initialize a TSocket for the supplied descriptor. This is only + * intended for use by TNonblockingServer -- other use may result in + * unfortunate surprises. + * + * @param fd the descriptor for an already-connected socket + */ + void setSocketFD(THRIFT_SOCKET fd); + + /* + * Returns a cached copy of the peer address. + */ + sockaddr* getCachedAddress(socklen_t* len) const; + + /** + * Sets whether to use a low minimum TCP retransmission timeout. + */ + static void setUseLowMinRto(bool useLowMinRto); + + /** + * Gets whether to use a low minimum TCP retransmission timeout. + */ + static bool getUseLowMinRto(); + + /** + * Get the origin the socket is connected to + * + * @return string peer host identifier and port + */ + const std::string getOrigin() const override; + + /** + * Constructor to create socket from file descriptor. + */ + TSocket(THRIFT_SOCKET socket); + + /** + * Constructor to create socket from file descriptor that + * can be interrupted safely. + */ + TSocket(THRIFT_SOCKET socket, std::shared_ptr interruptListener); + + /** + * Set a cache of the peer address (used when trivially available: e.g. + * accept() or connect()). Only caches IPV4 and IPV6; unset for others. + */ + void setCachedAddress(const sockaddr* addr, socklen_t len); + +protected: + /** connect, called by open */ + void openConnection(struct addrinfo* res); + + /** Host to connect to */ + std::string host_; + + /** Port number to connect on */ + int port_; + + /** UNIX domain socket path */ + std::string path_; + + /** Underlying socket handle */ + THRIFT_SOCKET socket_; + + /** Peer hostname */ + mutable std::string peerHost_; + + /** Peer address */ + mutable std::string peerAddress_; + + /** Peer port */ + mutable int peerPort_; + + /** + * A shared socket pointer that will interrupt a blocking read if data + * becomes available on it + */ + std::shared_ptr interruptListener_; + + /** Connect timeout in ms */ + int connTimeout_; + + /** Send timeout in ms */ + int sendTimeout_; + + /** Recv timeout in ms */ + int recvTimeout_; + + /** Keep alive on */ + bool keepAlive_; + + /** Linger on */ + bool lingerOn_; + + /** Linger val */ + int lingerVal_; + + /** Nodelay */ + bool noDelay_; + + /** Recv EGAIN retries */ + int maxRecvRetries_; + + /** Cached peer address */ + union { + sockaddr_in ipv4; + sockaddr_in6 ipv6; + } cachedPeerAddr_; + + /** Whether to use low minimum TCP retransmission timeout */ + static bool useLowMinRto_; + +private: + void unix_open(); + void local_open(); +}; +} +} +} // apache::thrift::transport + +#endif // #ifndef _THRIFT_TRANSPORT_TSOCKET_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TSocketPool.cpp b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TSocketPool.cpp new file mode 100644 index 000000000..b6e79d3f6 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TSocketPool.cpp @@ -0,0 +1,266 @@ +/* + * 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. + */ + +#include + +#include +#include +#include +#if __cplusplus >= 201703L +#include +#endif + +#include + +using std::pair; +using std::string; +using std::vector; + +namespace apache { +namespace thrift { +namespace transport { + +using std::shared_ptr; + +/** + * TSocketPoolServer implementation + * + */ +TSocketPoolServer::TSocketPoolServer() + : host_(""), port_(0), socket_(THRIFT_INVALID_SOCKET), lastFailTime_(0), consecutiveFailures_(0) { +} + +/** + * Constructor for TSocketPool server + */ +TSocketPoolServer::TSocketPoolServer(const string& host, int port) + : host_(host), + port_(port), + socket_(THRIFT_INVALID_SOCKET), + lastFailTime_(0), + consecutiveFailures_(0) { +} + +/** + * TSocketPool implementation. + * + */ + +TSocketPool::TSocketPool() + : TSocket(), + numRetries_(1), + retryInterval_(60), + maxConsecutiveFailures_(1), + randomize_(true), + alwaysTryLast_(true) { +} + +TSocketPool::TSocketPool(const vector& hosts, const vector& ports) + : TSocket(), + numRetries_(1), + retryInterval_(60), + maxConsecutiveFailures_(1), + randomize_(true), + alwaysTryLast_(true) { + if (hosts.size() != ports.size()) { + GlobalOutput("TSocketPool::TSocketPool: hosts.size != ports.size"); + throw TTransportException(TTransportException::BAD_ARGS); + } + + for (unsigned int i = 0; i < hosts.size(); ++i) { + addServer(hosts[i], ports[i]); + } +} + +TSocketPool::TSocketPool(const vector >& servers) + : TSocket(), + numRetries_(1), + retryInterval_(60), + maxConsecutiveFailures_(1), + randomize_(true), + alwaysTryLast_(true) { + for (const auto & server : servers) { + addServer(server.first, server.second); + } +} + +TSocketPool::TSocketPool(const vector >& servers) + : TSocket(), + servers_(servers), + numRetries_(1), + retryInterval_(60), + maxConsecutiveFailures_(1), + randomize_(true), + alwaysTryLast_(true) { +} + +TSocketPool::TSocketPool(const string& host, int port) + : TSocket(), + numRetries_(1), + retryInterval_(60), + maxConsecutiveFailures_(1), + randomize_(true), + alwaysTryLast_(true) { + addServer(host, port); +} + +TSocketPool::~TSocketPool() { + vector >::const_iterator iter = servers_.begin(); + vector >::const_iterator iterEnd = servers_.end(); + for (; iter != iterEnd; ++iter) { + setCurrentServer(*iter); + TSocketPool::close(); + } +} + +void TSocketPool::addServer(const string& host, int port) { + servers_.push_back(std::make_shared(host, port)); +} + +void TSocketPool::addServer(shared_ptr& server) { + if (server) { + servers_.push_back(server); + } +} + +void TSocketPool::setServers(const vector >& servers) { + servers_ = servers; +} + +void TSocketPool::getServers(vector >& servers) { + servers = servers_; +} + +void TSocketPool::setNumRetries(int numRetries) { + numRetries_ = numRetries; +} + +void TSocketPool::setRetryInterval(int retryInterval) { + retryInterval_ = retryInterval; +} + +void TSocketPool::setMaxConsecutiveFailures(int maxConsecutiveFailures) { + maxConsecutiveFailures_ = maxConsecutiveFailures; +} + +void TSocketPool::setRandomize(bool randomize) { + randomize_ = randomize; +} + +void TSocketPool::setAlwaysTryLast(bool alwaysTryLast) { + alwaysTryLast_ = alwaysTryLast; +} + +void TSocketPool::setCurrentServer(const shared_ptr& server) { + currentServer_ = server; + host_ = server->host_; + port_ = server->port_; + socket_ = server->socket_; +} + +/** + * This function throws an exception if socket open fails. When socket + * opens fails, the socket in the current server is reset. + */ +/* TODO: without apc we ignore a lot of functionality from the php version */ +void TSocketPool::open() { + + size_t numServers = servers_.size(); + if (numServers == 0) { + socket_ = THRIFT_INVALID_SOCKET; + throw TTransportException(TTransportException::NOT_OPEN); + } + + if (isOpen()) { + return; + } + + if (randomize_ && numServers > 1) { +#if __cplusplus >= 201703L + std::random_device rng; + std::mt19937 urng(rng()); + std::shuffle(servers_.begin(), servers_.end(), urng); +#else + std::random_shuffle(servers_.begin(), servers_.end()); +#endif + } + + for (size_t i = 0; i < numServers; ++i) { + + shared_ptr& server = servers_[i]; + // Impersonate the server socket + setCurrentServer(server); + + if (isOpen()) { + // already open means we're done + return; + } + + bool retryIntervalPassed = (server->lastFailTime_ == 0); + bool isLastServer = alwaysTryLast_ ? (i == (numServers - 1)) : false; + + if (server->lastFailTime_ > 0) { + // The server was marked as down, so check if enough time has elapsed to retry + time_t elapsedTime = time(nullptr) - server->lastFailTime_; + if (elapsedTime > retryInterval_) { + retryIntervalPassed = true; + } + } + + if (retryIntervalPassed || isLastServer) { + for (int j = 0; j < numRetries_; ++j) { + try { + TSocket::open(); + } catch (const TException &e) { + string errStr = "TSocketPool::open failed " + getSocketInfo() + ": " + e.what(); + GlobalOutput(errStr.c_str()); + socket_ = THRIFT_INVALID_SOCKET; + continue; + } + + // Copy over the opened socket so that we can keep it persistent + server->socket_ = socket_; + // reset lastFailTime_ is required + server->lastFailTime_ = 0; + // success + return; + } + + ++server->consecutiveFailures_; + if (server->consecutiveFailures_ > maxConsecutiveFailures_) { + // Mark server as down + server->consecutiveFailures_ = 0; + server->lastFailTime_ = time(nullptr); + } + } + } + + GlobalOutput("TSocketPool::open: all connections failed"); + throw TTransportException(TTransportException::NOT_OPEN); +} + +void TSocketPool::close() { + TSocket::close(); + if (currentServer_) { + currentServer_->socket_ = THRIFT_INVALID_SOCKET; + } +} +} +} +} // apache::thrift::transport diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TSocketPool.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TSocketPool.h new file mode 100644 index 000000000..97a2b9063 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TSocketPool.h @@ -0,0 +1,195 @@ +/* + * 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. + */ + +#ifndef _THRIFT_TRANSPORT_TSOCKETPOOL_H_ +#define _THRIFT_TRANSPORT_TSOCKETPOOL_H_ 1 + +#include +#include + +namespace apache { +namespace thrift { +namespace transport { + +/** + * Class to hold server information for TSocketPool + * + */ +class TSocketPoolServer { + +public: + /** + * Default constructor for server info + */ + TSocketPoolServer(); + + /** + * Constructor for TSocketPool server + */ + TSocketPoolServer(const std::string& host, int port); + + // Host name + std::string host_; + + // Port to connect on + int port_; + + // Socket for the server + THRIFT_SOCKET socket_; + + // Last time connecting to this server failed + time_t lastFailTime_; + + // Number of consecutive times connecting to this server failed + int consecutiveFailures_; +}; + +/** + * TCP Socket implementation of the TTransport interface. + * + */ +class TSocketPool : public TSocket { + +public: + /** + * Socket pool constructor + */ + TSocketPool(); + + /** + * Socket pool constructor + * + * @param hosts list of host names + * @param ports list of port names + */ + TSocketPool(const std::vector& hosts, const std::vector& ports); + + /** + * Socket pool constructor + * + * @param servers list of pairs of host name and port + */ + TSocketPool(const std::vector >& servers); + + /** + * Socket pool constructor + * + * @param servers list of TSocketPoolServers + */ + TSocketPool(const std::vector >& servers); + + /** + * Socket pool constructor + * + * @param host single host + * @param port single port + */ + TSocketPool(const std::string& host, int port); + + /** + * Destroyes the socket object, closing it if necessary. + */ + ~TSocketPool() override; + + /** + * Add a server to the pool + */ + void addServer(const std::string& host, int port); + + /** + * Add a server to the pool + */ + void addServer(std::shared_ptr& server); + + /** + * Set list of servers in this pool + */ + void setServers(const std::vector >& servers); + + /** + * Get list of servers in this pool + */ + void getServers(std::vector >& servers); + + /** + * Sets how many times to keep retrying a host in the connect function. + */ + void setNumRetries(int numRetries); + + /** + * Sets how long to wait until retrying a host if it was marked down + */ + void setRetryInterval(int retryInterval); + + /** + * Sets how many times to keep retrying a host before marking it as down. + */ + void setMaxConsecutiveFailures(int maxConsecutiveFailures); + + /** + * Turns randomization in connect order on or off. + */ + void setRandomize(bool randomize); + + /** + * Whether to always try the last server. + */ + void setAlwaysTryLast(bool alwaysTryLast); + + /** + * Creates and opens the UNIX socket. + */ + void open() override; + + /* + * Closes the UNIX socket + */ + void close() override; + +protected: + void setCurrentServer(const std::shared_ptr& server); + + /** List of servers to connect to */ + std::vector > servers_; + + /** Current server */ + std::shared_ptr currentServer_; + + /** How many times to retry each host in connect */ + int numRetries_; + + /** Retry interval in seconds, how long to not try a host if it has been + * marked as down. + */ + time_t retryInterval_; + + /** Max consecutive failures before marking a host down. */ + int maxConsecutiveFailures_; + + /** Try hosts in order? or Randomized? */ + bool randomize_; + + /** Always try last host, even if marked down? */ + bool alwaysTryLast_; +}; +} +} +} // apache::thrift::transport + +#endif // #ifndef _THRIFT_TRANSPORT_TSOCKETPOOL_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TTransport.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TTransport.h new file mode 100644 index 000000000..891bfe151 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TTransport.h @@ -0,0 +1,271 @@ +/* + * 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. + */ + +#ifndef _THRIFT_TRANSPORT_TTRANSPORT_H_ +#define _THRIFT_TRANSPORT_TTRANSPORT_H_ 1 + +#include +#include +#include +#include + +namespace apache { +namespace thrift { +namespace transport { + +/** + * Helper template to hoist readAll implementation out of TTransport + */ +template +uint32_t readAll(Transport_& trans, uint8_t* buf, uint32_t len) { + uint32_t have = 0; + uint32_t get = 0; + + while (have < len) { + get = trans.read(buf + have, len - have); + if (get <= 0) { + throw TTransportException(TTransportException::END_OF_FILE, "No more data to read."); + } + have += get; + } + + return have; +} + +/** + * Generic interface for a method of transporting data. A TTransport may be + * capable of either reading or writing, but not necessarily both. + * + */ +class TTransport { +public: + /** + * Virtual deconstructor. + */ + virtual ~TTransport() = default; + + /** + * Whether this transport is open. + */ + virtual bool isOpen() const { return false; } + + /** + * Tests whether there is more data to read or if the remote side is + * still open. By default this is true whenever the transport is open, + * but implementations should add logic to test for this condition where + * possible (i.e. on a socket). + * This is used by a server to check if it should listen for another + * request. + */ + virtual bool peek() { return isOpen(); } + + /** + * Opens the transport for communications. + * + * @return bool Whether the transport was successfully opened + * @throws TTransportException if opening failed + */ + virtual void open() { + throw TTransportException(TTransportException::NOT_OPEN, "Cannot open base TTransport."); + } + + /** + * Closes the transport. + */ + virtual void close() { + throw TTransportException(TTransportException::NOT_OPEN, "Cannot close base TTransport."); + } + + /** + * Attempt to read up to the specified number of bytes into the string. + * + * @param buf Reference to the location to write the data + * @param len How many bytes to read + * @return How many bytes were actually read + * @throws TTransportException If an error occurs + */ + uint32_t read(uint8_t* buf, uint32_t len) { + T_VIRTUAL_CALL(); + return read_virt(buf, len); + } + virtual uint32_t read_virt(uint8_t* /* buf */, uint32_t /* len */) { + throw TTransportException(TTransportException::NOT_OPEN, "Base TTransport cannot read."); + } + + /** + * Reads the given amount of data in its entirety no matter what. + * + * @param s Reference to location for read data + * @param len How many bytes to read + * @return How many bytes read, which must be equal to size + * @throws TTransportException If insufficient data was read + */ + uint32_t readAll(uint8_t* buf, uint32_t len) { + T_VIRTUAL_CALL(); + return readAll_virt(buf, len); + } + virtual uint32_t readAll_virt(uint8_t* buf, uint32_t len) { + return apache::thrift::transport::readAll(*this, buf, len); + } + + /** + * Called when read is completed. + * This can be over-ridden to perform a transport-specific action + * e.g. logging the request to a file + * + * @return number of bytes read if available, 0 otherwise. + */ + virtual uint32_t readEnd() { + // default behaviour is to do nothing + return 0; + } + + /** + * Writes the string in its entirety to the buffer. + * + * Note: You must call flush() to ensure the data is actually written, + * and available to be read back in the future. Destroying a TTransport + * object does not automatically flush pending data--if you destroy a + * TTransport object with written but unflushed data, that data may be + * discarded. + * + * @param buf The data to write out + * @throws TTransportException if an error occurs + */ + void write(const uint8_t* buf, uint32_t len) { + T_VIRTUAL_CALL(); + write_virt(buf, len); + } + virtual void write_virt(const uint8_t* /* buf */, uint32_t /* len */) { + throw TTransportException(TTransportException::NOT_OPEN, "Base TTransport cannot write."); + } + + /** + * Called when write is completed. + * This can be over-ridden to perform a transport-specific action + * at the end of a request. + * + * @return number of bytes written if available, 0 otherwise + */ + virtual uint32_t writeEnd() { + // default behaviour is to do nothing + return 0; + } + + /** + * Flushes any pending data to be written. Typically used with buffered + * transport mechanisms. + * + * @throws TTransportException if an error occurs + */ + virtual void flush() { + // default behaviour is to do nothing + } + + /** + * Attempts to return a pointer to \c len bytes, possibly copied into \c buf. + * Does not consume the bytes read (i.e.: a later read will return the same + * data). This method is meant to support protocols that need to read + * variable-length fields. They can attempt to borrow the maximum amount of + * data that they will need, then consume (see next method) what they + * actually use. Some transports will not support this method and others + * will fail occasionally, so protocols must be prepared to use read if + * borrow fails. + * + * @oaram buf A buffer where the data can be stored if needed. + * If borrow doesn't return buf, then the contents of + * buf after the call are undefined. This parameter may be + * NULL to indicate that the caller is not supplying storage, + * but would like a pointer into an internal buffer, if + * available. + * @param len *len should initially contain the number of bytes to borrow. + * If borrow succeeds, *len will contain the number of bytes + * available in the returned pointer. This will be at least + * what was requested, but may be more if borrow returns + * a pointer to an internal buffer, rather than buf. + * If borrow fails, the contents of *len are undefined. + * @return If the borrow succeeds, return a pointer to the borrowed data. + * This might be equal to \c buf, or it might be a pointer into + * the transport's internal buffers. + * @throws TTransportException if an error occurs + */ + const uint8_t* borrow(uint8_t* buf, uint32_t* len) { + T_VIRTUAL_CALL(); + return borrow_virt(buf, len); + } + virtual const uint8_t* borrow_virt(uint8_t* /* buf */, uint32_t* /* len */) { return nullptr; } + + /** + * Remove len bytes from the transport. This should always follow a borrow + * of at least len bytes, and should always succeed. + * TODO(dreiss): Is there any transport that could borrow but fail to + * consume, or that would require a buffer to dump the consumed data? + * + * @param len How many bytes to consume + * @throws TTransportException If an error occurs + */ + void consume(uint32_t len) { + T_VIRTUAL_CALL(); + consume_virt(len); + } + virtual void consume_virt(uint32_t /* len */) { + throw TTransportException(TTransportException::NOT_OPEN, "Base TTransport cannot consume."); + } + + /** + * Returns the origin of the transports call. The value depends on the + * transport used. An IP based transport for example will return the + * IP address of the client making the request. + * If the transport doesn't know the origin Unknown is returned. + * + * The returned value can be used in a log message for example + */ + virtual const std::string getOrigin() const { return "Unknown"; } + +protected: + /** + * Simple constructor. + */ + TTransport() = default; +}; + +/** + * Generic factory class to make an input and output transport out of a + * source transport. Commonly used inside servers to make input and output + * streams out of raw clients. + * + */ +class TTransportFactory { +public: + TTransportFactory() = default; + + virtual ~TTransportFactory() = default; + + /** + * Default implementation does nothing, just returns the transport given. + */ + virtual std::shared_ptr getTransport(std::shared_ptr trans) { + return trans; + } +}; +} +} +} // apache::thrift::transport + +#endif // #ifndef _THRIFT_TRANSPORT_TTRANSPORT_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TTransportException.cpp b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TTransportException.cpp new file mode 100644 index 000000000..a527317e0 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TTransportException.cpp @@ -0,0 +1,59 @@ +/* + * 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. + */ + +#include +#include + +#include + +using std::string; + +namespace apache { +namespace thrift { +namespace transport { + +const char* TTransportException::what() const noexcept { + if (message_.empty()) { + switch (type_) { + case UNKNOWN: + return "TTransportException: Unknown transport exception"; + case NOT_OPEN: + return "TTransportException: Transport not open"; + case TIMED_OUT: + return "TTransportException: Timed out"; + case END_OF_FILE: + return "TTransportException: End of file"; + case INTERRUPTED: + return "TTransportException: Interrupted"; + case BAD_ARGS: + return "TTransportException: Invalid arguments"; + case CORRUPTED_DATA: + return "TTransportException: Corrupted Data"; + case INTERNAL_ERROR: + return "TTransportException: Internal error"; + default: + return "TTransportException: (Invalid exception type)"; + } + } else { + return message_.c_str(); + } +} +} +} +} // apache::thrift::transport diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TTransportException.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TTransportException.h new file mode 100644 index 000000000..38b75211f --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TTransportException.h @@ -0,0 +1,106 @@ +/* + * 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. + */ + +#ifndef _THRIFT_TRANSPORT_TTRANSPORTEXCEPTION_H_ +#define _THRIFT_TRANSPORT_TTRANSPORTEXCEPTION_H_ 1 + +#include +#include +#include + +namespace apache { +namespace thrift { +namespace transport { + +/** + * Class to encapsulate all the possible types of transport errors that may + * occur in various transport systems. This provides a sort of generic + * wrapper around the vague UNIX E_ error codes that lets a common code + * base of error handling to be used for various types of transports, i.e. + * pipes etc. + * + */ +class TTransportException : public apache::thrift::TException { +public: + /** + * Error codes for the various types of exceptions. + */ + enum TTransportExceptionType { + UNKNOWN = 0, + NOT_OPEN = 1, + TIMED_OUT = 2, + END_OF_FILE = 3, + INTERRUPTED = 4, + BAD_ARGS = 5, + CORRUPTED_DATA = 6, + INTERNAL_ERROR = 7 + }; + + TTransportException() : apache::thrift::TException(), type_(UNKNOWN) {} + + TTransportException(TTransportExceptionType type) : apache::thrift::TException(), type_(type) {} + + TTransportException(const std::string& message) + : apache::thrift::TException(message), type_(UNKNOWN) {} + + TTransportException(TTransportExceptionType type, const std::string& message) + : apache::thrift::TException(message), type_(type) {} + + TTransportException(TTransportExceptionType type, const std::string& message, int errno_copy) + : apache::thrift::TException(message + ": " + TOutput::strerror_s(errno_copy)), type_(type) {} + + ~TTransportException() noexcept override = default; + + /** + * Returns an error code that provides information about the type of error + * that has occurred. + * + * @return Error code + */ + TTransportExceptionType getType() const noexcept { return type_; } + + const char* what() const noexcept override; + +protected: + /** Just like strerror_r but returns a C++ string object. */ + std::string strerror_s(int errno_copy); + + /** Error code */ + TTransportExceptionType type_; +}; + +/** + * Legacy code in transport implementations have overflow issues + * that need to be enforced. + */ +template To safe_numeric_cast(From i) { + try { + return boost::numeric_cast(i); + } + catch (const std::bad_cast& bc) { + throw TTransportException(TTransportException::CORRUPTED_DATA, + bc.what()); + } +} + +} +} +} // apache::thrift::transport + +#endif // #ifndef _THRIFT_TRANSPORT_TTRANSPORTEXCEPTION_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TTransportUtils.cpp b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TTransportUtils.cpp new file mode 100644 index 000000000..69372f3e2 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TTransportUtils.cpp @@ -0,0 +1,189 @@ +/* + * 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. + */ + +#include + +using std::string; + +namespace apache { +namespace thrift { +namespace transport { + +uint32_t TPipedTransport::read(uint8_t* buf, uint32_t len) { + uint32_t need = len; + + // We don't have enough data yet + if (rLen_ - rPos_ < need) { + // Copy out whatever we have + if (rLen_ - rPos_ > 0) { + memcpy(buf, rBuf_ + rPos_, rLen_ - rPos_); + need -= rLen_ - rPos_; + buf += rLen_ - rPos_; + rPos_ = rLen_; + } + + // Double the size of the underlying buffer if it is full + if (rLen_ == rBufSize_) { + rBufSize_ *= 2; + auto *tmpBuf = (uint8_t*)std::realloc(rBuf_, sizeof(uint8_t) * rBufSize_); + if (tmpBuf == nullptr) { + throw std::bad_alloc(); + } + rBuf_ = tmpBuf; + } + + // try to fill up the buffer + rLen_ += srcTrans_->read(rBuf_ + rPos_, rBufSize_ - rPos_); + } + + // Hand over whatever we have + uint32_t give = need; + if (rLen_ - rPos_ < give) { + give = rLen_ - rPos_; + } + if (give > 0) { + memcpy(buf, rBuf_ + rPos_, give); + rPos_ += give; + need -= give; + } + + return (len - need); +} + +void TPipedTransport::write(const uint8_t* buf, uint32_t len) { + if (len == 0) { + return; + } + + // Make the buffer as big as it needs to be + if ((len + wLen_) >= wBufSize_) { + uint32_t newBufSize = wBufSize_ * 2; + while ((len + wLen_) >= newBufSize) { + newBufSize *= 2; + } + auto *tmpBuf= (uint8_t*)std::realloc(wBuf_, sizeof(uint8_t) * newBufSize); + if (tmpBuf == nullptr) { + throw std::bad_alloc(); + } + wBuf_ = tmpBuf; + + wBufSize_ = newBufSize; + } + + // Copy into the buffer + memcpy(wBuf_ + wLen_, buf, len); + wLen_ += len; +} + +void TPipedTransport::flush() { + // Write out any data waiting in the write buffer + if (wLen_ > 0) { + srcTrans_->write(wBuf_, wLen_); + wLen_ = 0; + } + + // Flush the underlying transport + srcTrans_->flush(); +} + +TPipedFileReaderTransport::TPipedFileReaderTransport( + std::shared_ptr srcTrans, + std::shared_ptr dstTrans) + : TPipedTransport(srcTrans, dstTrans), srcTrans_(srcTrans) { +} + +TPipedFileReaderTransport::~TPipedFileReaderTransport() = default; + +bool TPipedFileReaderTransport::isOpen() const { + return TPipedTransport::isOpen(); +} + +bool TPipedFileReaderTransport::peek() { + return TPipedTransport::peek(); +} + +void TPipedFileReaderTransport::open() { + TPipedTransport::open(); +} + +void TPipedFileReaderTransport::close() { + TPipedTransport::close(); +} + +uint32_t TPipedFileReaderTransport::read(uint8_t* buf, uint32_t len) { + return TPipedTransport::read(buf, len); +} + +uint32_t TPipedFileReaderTransport::readAll(uint8_t* buf, uint32_t len) { + uint32_t have = 0; + uint32_t get = 0; + + while (have < len) { + get = read(buf + have, len - have); + if (get <= 0) { + throw TEOFException(); + } + have += get; + } + + return have; +} + +uint32_t TPipedFileReaderTransport::readEnd() { + return TPipedTransport::readEnd(); +} + +void TPipedFileReaderTransport::write(const uint8_t* buf, uint32_t len) { + TPipedTransport::write(buf, len); +} + +uint32_t TPipedFileReaderTransport::writeEnd() { + return TPipedTransport::writeEnd(); +} + +void TPipedFileReaderTransport::flush() { + TPipedTransport::flush(); +} + +int32_t TPipedFileReaderTransport::getReadTimeout() { + return srcTrans_->getReadTimeout(); +} + +void TPipedFileReaderTransport::setReadTimeout(int32_t readTimeout) { + srcTrans_->setReadTimeout(readTimeout); +} + +uint32_t TPipedFileReaderTransport::getNumChunks() { + return srcTrans_->getNumChunks(); +} + +uint32_t TPipedFileReaderTransport::getCurChunk() { + return srcTrans_->getCurChunk(); +} + +void TPipedFileReaderTransport::seekToChunk(int32_t chunk) { + srcTrans_->seekToChunk(chunk); +} + +void TPipedFileReaderTransport::seekToEnd() { + srcTrans_->seekToEnd(); +} +} +} +} // apache::thrift::transport diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TTransportUtils.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TTransportUtils.h new file mode 100644 index 000000000..28c93d2a1 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TTransportUtils.h @@ -0,0 +1,314 @@ +/* + * 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. + */ + +#ifndef _THRIFT_TRANSPORT_TTRANSPORTUTILS_H_ +#define _THRIFT_TRANSPORT_TTRANSPORTUTILS_H_ 1 + +#include +#include +#include +#include +#include +// Include the buffered transports that used to be defined here. +#include +#include + +namespace apache { +namespace thrift { +namespace transport { + +/** + * The null transport is a dummy transport that doesn't actually do anything. + * It's sort of an analogy to /dev/null, you can never read anything from it + * and it will let you write anything you want to it, though it won't actually + * go anywhere. + * + */ +class TNullTransport : public TVirtualTransport { +public: + TNullTransport() = default; + + ~TNullTransport() override = default; + + bool isOpen() const override { return true; } + + void open() override {} + + void write(const uint8_t* /* buf */, uint32_t /* len */) { return; } +}; + +/** + * TPipedTransport. This transport allows piping of a request from one + * transport to another either when readEnd() or writeEnd(). The typical + * use case for this is to log a request or a reply to disk. + * The underlying buffer expands to a keep a copy of the entire + * request/response. + * + */ +class TPipedTransport : virtual public TTransport { +public: + TPipedTransport(std::shared_ptr srcTrans, std::shared_ptr dstTrans) + : srcTrans_(srcTrans), + dstTrans_(dstTrans), + rBufSize_(512), + rPos_(0), + rLen_(0), + wBufSize_(512), + wLen_(0) { + + // default is to to pipe the request when readEnd() is called + pipeOnRead_ = true; + pipeOnWrite_ = false; + + rBuf_ = (uint8_t*)std::malloc(sizeof(uint8_t) * rBufSize_); + if (rBuf_ == nullptr) { + throw std::bad_alloc(); + } + wBuf_ = (uint8_t*)std::malloc(sizeof(uint8_t) * wBufSize_); + if (wBuf_ == nullptr) { + throw std::bad_alloc(); + } + } + + TPipedTransport(std::shared_ptr srcTrans, + std::shared_ptr dstTrans, + uint32_t sz) + : srcTrans_(srcTrans), + dstTrans_(dstTrans), + rBufSize_(512), + rPos_(0), + rLen_(0), + wBufSize_(sz), + wLen_(0) { + + rBuf_ = (uint8_t*)std::malloc(sizeof(uint8_t) * rBufSize_); + if (rBuf_ == nullptr) { + throw std::bad_alloc(); + } + wBuf_ = (uint8_t*)std::malloc(sizeof(uint8_t) * wBufSize_); + if (wBuf_ == nullptr) { + throw std::bad_alloc(); + } + } + + ~TPipedTransport() override { + std::free(rBuf_); + std::free(wBuf_); + } + + bool isOpen() const override { return srcTrans_->isOpen(); } + + bool peek() override { + if (rPos_ >= rLen_) { + // Double the size of the underlying buffer if it is full + if (rLen_ == rBufSize_) { + rBufSize_ *= 2; + auto * tmpBuf = (uint8_t*)std::realloc(rBuf_, sizeof(uint8_t) * rBufSize_); + if (tmpBuf == nullptr) { + throw std::bad_alloc(); + } + rBuf_ = tmpBuf; + } + + // try to fill up the buffer + rLen_ += srcTrans_->read(rBuf_ + rPos_, rBufSize_ - rPos_); + } + return (rLen_ > rPos_); + } + + void open() override { srcTrans_->open(); } + + void close() override { srcTrans_->close(); } + + void setPipeOnRead(bool pipeVal) { pipeOnRead_ = pipeVal; } + + void setPipeOnWrite(bool pipeVal) { pipeOnWrite_ = pipeVal; } + + uint32_t read(uint8_t* buf, uint32_t len); + + uint32_t readEnd() override { + + if (pipeOnRead_) { + dstTrans_->write(rBuf_, rPos_); + dstTrans_->flush(); + } + + srcTrans_->readEnd(); + + // If requests are being pipelined, copy down our read-ahead data, + // then reset our state. + int read_ahead = rLen_ - rPos_; + uint32_t bytes = rPos_; + memcpy(rBuf_, rBuf_ + rPos_, read_ahead); + rPos_ = 0; + rLen_ = read_ahead; + + return bytes; + } + + void write(const uint8_t* buf, uint32_t len); + + uint32_t writeEnd() override { + if (pipeOnWrite_) { + dstTrans_->write(wBuf_, wLen_); + dstTrans_->flush(); + } + return wLen_; + } + + void flush() override; + + std::shared_ptr getTargetTransport() { return dstTrans_; } + + /* + * Override TTransport *_virt() functions to invoke our implementations. + * We cannot use TVirtualTransport to provide these, since we need to inherit + * virtually from TTransport. + */ + uint32_t read_virt(uint8_t* buf, uint32_t len) override { return this->read(buf, len); } + void write_virt(const uint8_t* buf, uint32_t len) override { this->write(buf, len); } + +protected: + std::shared_ptr srcTrans_; + std::shared_ptr dstTrans_; + + uint8_t* rBuf_; + uint32_t rBufSize_; + uint32_t rPos_; + uint32_t rLen_; + + uint8_t* wBuf_; + uint32_t wBufSize_; + uint32_t wLen_; + + bool pipeOnRead_; + bool pipeOnWrite_; +}; + +/** + * Wraps a transport into a pipedTransport instance. + * + */ +class TPipedTransportFactory : public TTransportFactory { +public: + TPipedTransportFactory() = default; + TPipedTransportFactory(std::shared_ptr dstTrans) { + initializeTargetTransport(dstTrans); + } + ~TPipedTransportFactory() override = default; + + /** + * Wraps the base transport into a piped transport. + */ + std::shared_ptr getTransport(std::shared_ptr srcTrans) override { + return std::shared_ptr(new TPipedTransport(srcTrans, dstTrans_)); + } + + virtual void initializeTargetTransport(std::shared_ptr dstTrans) { + if (dstTrans_.get() == nullptr) { + dstTrans_ = dstTrans; + } else { + throw TException("Target transport already initialized"); + } + } + +protected: + std::shared_ptr dstTrans_; +}; + +/** + * TPipedFileTransport. This is just like a TTransport, except that + * it is a templatized class, so that clients who rely on a specific + * TTransport can still access the original transport. + * + */ +class TPipedFileReaderTransport : public TPipedTransport, public TFileReaderTransport { +public: + TPipedFileReaderTransport(std::shared_ptr srcTrans, + std::shared_ptr dstTrans); + + ~TPipedFileReaderTransport() override; + + // TTransport functions + bool isOpen() const override; + bool peek() override; + void open() override; + void close() override; + uint32_t read(uint8_t* buf, uint32_t len); + uint32_t readAll(uint8_t* buf, uint32_t len); + uint32_t readEnd() override; + void write(const uint8_t* buf, uint32_t len); + uint32_t writeEnd() override; + void flush() override; + + // TFileReaderTransport functions + int32_t getReadTimeout() override; + void setReadTimeout(int32_t readTimeout) override; + uint32_t getNumChunks() override; + uint32_t getCurChunk() override; + void seekToChunk(int32_t chunk) override; + void seekToEnd() override; + + /* + * Override TTransport *_virt() functions to invoke our implementations. + * We cannot use TVirtualTransport to provide these, since we need to inherit + * virtually from TTransport. + */ + uint32_t read_virt(uint8_t* buf, uint32_t len) override { return this->read(buf, len); } + uint32_t readAll_virt(uint8_t* buf, uint32_t len) override { return this->readAll(buf, len); } + void write_virt(const uint8_t* buf, uint32_t len) override { this->write(buf, len); } + +protected: + // shouldn't be used + TPipedFileReaderTransport(); + std::shared_ptr srcTrans_; +}; + +/** + * Creates a TPipedFileReaderTransport from a filepath and a destination transport + * + */ +class TPipedFileReaderTransportFactory : public TPipedTransportFactory { +public: + TPipedFileReaderTransportFactory() = default; + TPipedFileReaderTransportFactory(std::shared_ptr dstTrans) + : TPipedTransportFactory(dstTrans) {} + ~TPipedFileReaderTransportFactory() override = default; + + std::shared_ptr getTransport(std::shared_ptr srcTrans) override { + std::shared_ptr pFileReaderTransport + = std::dynamic_pointer_cast(srcTrans); + if (pFileReaderTransport.get() != nullptr) { + return getFileReaderTransport(pFileReaderTransport); + } else { + return std::shared_ptr(); + } + } + + std::shared_ptr getFileReaderTransport( + std::shared_ptr srcTrans) { + return std::shared_ptr( + new TPipedFileReaderTransport(srcTrans, dstTrans_)); + } +}; +} +} +} // apache::thrift::transport + +#endif // #ifndef _THRIFT_TRANSPORT_TTRANSPORTUTILS_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TVirtualTransport.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TVirtualTransport.h new file mode 100644 index 000000000..0a0485742 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TVirtualTransport.h @@ -0,0 +1,140 @@ +/* + * 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. + */ + +#ifndef _THRIFT_TRANSPORT_TVIRTUALTRANSPORT_H_ +#define _THRIFT_TRANSPORT_TVIRTUALTRANSPORT_H_ 1 + +#include + +namespace apache { +namespace thrift { +namespace transport { + +/** + * Helper class that provides default implementations of TTransport methods. + * + * This class provides default implementations of read(), readAll(), write(), + * borrow() and consume(). + * + * In the TTransport base class, each of these methods simply invokes its + * virtual counterpart. This class overrides them to always perform the + * default behavior, without a virtual function call. + * + * The primary purpose of this class is to serve as a base class for + * TVirtualTransport, and prevent infinite recursion if one of its subclasses + * does not override the TTransport implementation of these methods. (Since + * TVirtualTransport::read_virt() calls read(), and TTransport::read() calls + * read_virt().) + */ +class TTransportDefaults : public TTransport { +public: + /* + * TTransport *_virt() methods provide reasonable default implementations. + * Invoke them non-virtually. + */ + uint32_t read(uint8_t* buf, uint32_t len) { return this->TTransport::read_virt(buf, len); } + uint32_t readAll(uint8_t* buf, uint32_t len) { return this->TTransport::readAll_virt(buf, len); } + void write(const uint8_t* buf, uint32_t len) { this->TTransport::write_virt(buf, len); } + const uint8_t* borrow(uint8_t* buf, uint32_t* len) { + return this->TTransport::borrow_virt(buf, len); + } + void consume(uint32_t len) { this->TTransport::consume_virt(len); } + +protected: + TTransportDefaults() = default; +}; + +/** + * Helper class to provide polymorphism for subclasses of TTransport. + * + * This class implements *_virt() methods of TTransport, to call the + * non-virtual versions of these functions in the proper subclass. + * + * To define your own transport class using TVirtualTransport: + * 1) Derive your subclass from TVirtualTransport + * e.g: class MyTransport : public TVirtualTransport { + * 2) Provide your own implementations of read(), readAll(), etc. + * These methods should be non-virtual. + * + * Transport implementations that need to use virtual inheritance when + * inheriting from TTransport cannot use TVirtualTransport. + * + * @author Chad Walters + */ +template +class TVirtualTransport : public Super_ { +public: + /* + * Implementations of the *_virt() functions, to call the subclass's + * non-virtual implementation function. + */ + uint32_t read_virt(uint8_t* buf, uint32_t len) override { + return static_cast(this)->read(buf, len); + } + + uint32_t readAll_virt(uint8_t* buf, uint32_t len) override { + return static_cast(this)->readAll(buf, len); + } + + void write_virt(const uint8_t* buf, uint32_t len) override { + static_cast(this)->write(buf, len); + } + + const uint8_t* borrow_virt(uint8_t* buf, uint32_t* len) override { + return static_cast(this)->borrow(buf, len); + } + + void consume_virt(uint32_t len) override { static_cast(this)->consume(len); } + + /* + * Provide a default readAll() implementation that invokes + * read() non-virtually. + * + * Note: subclasses that use TVirtualTransport to derive from another + * transport implementation (i.e., not TTransportDefaults) should beware that + * this may override any non-default readAll() implementation provided by + * the parent transport class. They may need to redefine readAll() to call + * the correct parent implementation, if desired. + */ + uint32_t readAll(uint8_t* buf, uint32_t len) { + auto* trans = static_cast(this); + return ::apache::thrift::transport::readAll(*trans, buf, len); + } + +protected: + TVirtualTransport() = default; + + /* + * Templatized constructors, to allow arguments to be passed to the Super_ + * constructor. Currently we only support 0, 1, or 2 arguments, but + * additional versions can be added as needed. + */ + template + TVirtualTransport(Arg_ const& arg) + : Super_(arg) {} + + template + TVirtualTransport(Arg1_ const& a1, Arg2_ const& a2) + : Super_(a1, a2) {} +}; +} +} +} // apache::thrift::transport + +#endif // #ifndef _THRIFT_TRANSPORT_TVIRTUALTRANSPORT_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TZlibTransport.cpp b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TZlibTransport.cpp new file mode 100644 index 000000000..437190b29 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TZlibTransport.cpp @@ -0,0 +1,402 @@ +/* + * 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. + */ + +#include +#include +#include +#include + +using std::string; + +namespace apache { +namespace thrift { +namespace transport { + +// Don't call this outside of the constructor. +void TZlibTransport::initZlib() { + int rv; + bool r_init = false; + try { + rstream_ = new z_stream; + wstream_ = new z_stream; + + rstream_->zalloc = Z_NULL; + wstream_->zalloc = Z_NULL; + rstream_->zfree = Z_NULL; + wstream_->zfree = Z_NULL; + rstream_->opaque = Z_NULL; + wstream_->opaque = Z_NULL; + + rstream_->next_in = crbuf_; + wstream_->next_in = uwbuf_; + rstream_->next_out = urbuf_; + wstream_->next_out = cwbuf_; + rstream_->avail_in = 0; + wstream_->avail_in = 0; + rstream_->avail_out = urbuf_size_; + wstream_->avail_out = cwbuf_size_; + + rv = inflateInit(rstream_); + checkZlibRv(rv, rstream_->msg); + + // Have to set this flag so we know whether to de-initialize. + r_init = true; + + rv = deflateInit(wstream_, comp_level_); + checkZlibRv(rv, wstream_->msg); + } + + catch (...) { + if (r_init) { + rv = inflateEnd(rstream_); + checkZlibRvNothrow(rv, rstream_->msg); + } + // There is no way we can get here if wstream_ was initialized. + + throw; + } +} + +inline void TZlibTransport::checkZlibRv(int status, const char* message) { + if (status != Z_OK) { + throw TZlibTransportException(status, message); + } +} + +inline void TZlibTransport::checkZlibRvNothrow(int status, const char* message) { + if (status != Z_OK) { + string output = "TZlibTransport: zlib failure in destructor: " + + TZlibTransportException::errorMessage(status, message); + GlobalOutput(output.c_str()); + } +} + +TZlibTransport::~TZlibTransport() { + int rv; + rv = inflateEnd(rstream_); + checkZlibRvNothrow(rv, rstream_->msg); + + rv = deflateEnd(wstream_); + // Z_DATA_ERROR may be returned if the caller has written data, but not + // called flush() to actually finish writing the data out to the underlying + // transport. The defined TTransport behavior in this case is that this data + // may be discarded, so we ignore the error and silently discard the data. + // For other erros, log a message. + if (rv != Z_DATA_ERROR) { + checkZlibRvNothrow(rv, wstream_->msg); + } + + delete[] urbuf_; + delete[] crbuf_; + delete[] uwbuf_; + delete[] cwbuf_; + delete rstream_; + delete wstream_; +} + +bool TZlibTransport::isOpen() const { + return (readAvail() > 0) || (rstream_->avail_in > 0) || transport_->isOpen(); +} + +bool TZlibTransport::peek() { + return (readAvail() > 0) || (rstream_->avail_in > 0) || transport_->peek(); +} + +// READING STRATEGY +// +// We have two buffers for reading: one containing the compressed data (crbuf_) +// and one containing the uncompressed data (urbuf_). When read is called, +// we repeat the following steps until we have satisfied the request: +// - Copy data from urbuf_ into the caller's buffer. +// - If we had enough, return. +// - If urbuf_ is empty, read some data into it from the underlying transport. +// - Inflate data from crbuf_ into urbuf_. +// +// In standalone objects, we set input_ended_ to true when inflate returns +// Z_STREAM_END. This allows to make sure that a checksum was verified. + +inline int TZlibTransport::readAvail() const { + return urbuf_size_ - rstream_->avail_out - urpos_; +} + +uint32_t TZlibTransport::read(uint8_t* buf, uint32_t len) { + uint32_t need = len; + + // TODO(dreiss): Skip urbuf on big reads. + + while (true) { + // Copy out whatever we have available, then give them the min of + // what we have and what they want, then advance indices. + int give = (std::min)((uint32_t)readAvail(), need); + memcpy(buf, urbuf_ + urpos_, give); + need -= give; + buf += give; + urpos_ += give; + + // If they were satisfied, we are done. + if (need == 0) { + return len; + } + + // If we will need to read from the underlying transport to get more data, + // but we already have some data available, return it now. Reading from + // the underlying transport may block, and read() is only allowed to block + // when no data is available. + if (need < len && rstream_->avail_in == 0) { + return len - need; + } + + // If we get to this point, we need to get some more data. + + // If zlib has reported the end of a stream, we can't really do any more. + if (input_ended_) { + return len - need; + } + + // The uncompressed read buffer is empty, so reset the stream fields. + rstream_->next_out = urbuf_; + rstream_->avail_out = urbuf_size_; + urpos_ = 0; + + // Call inflate() to uncompress some more data + if (!readFromZlib()) { + // no data available from underlying transport + return len - need; + } + + // Okay. The read buffer should have whatever we can give it now. + // Loop back to the start and try to give some more. + } +} + +bool TZlibTransport::readFromZlib() { + assert(!input_ended_); + + // If we don't have any more compressed data available, + // read some from the underlying transport. + if (rstream_->avail_in == 0) { + uint32_t got = transport_->read(crbuf_, crbuf_size_); + if (got == 0) { + return false; + } + rstream_->next_in = crbuf_; + rstream_->avail_in = got; + } + + // We have some compressed data now. Uncompress it. + int zlib_rv = inflate(rstream_, Z_SYNC_FLUSH); + + if (zlib_rv == Z_STREAM_END) { + input_ended_ = true; + } else { + checkZlibRv(zlib_rv, rstream_->msg); + } + + return true; +} + +// WRITING STRATEGY +// +// We buffer up small writes before sending them to zlib, so our logic is: +// - Is the write big? +// - Send the buffer to zlib. +// - Send this data to zlib. +// - Is the write small? +// - Is there insufficient space in the buffer for it? +// - Send the buffer to zlib. +// - Copy the data to the buffer. +// +// We have two buffers for writing also: the uncompressed buffer (mentioned +// above) and the compressed buffer. When sending data to zlib we loop over +// the following until the source (uncompressed buffer or big write) is empty: +// - Is there no more space in the compressed buffer? +// - Write the compressed buffer to the underlying transport. +// - Deflate from the source into the compressed buffer. + +void TZlibTransport::write(const uint8_t* buf, uint32_t len) { + if (output_finished_) { + throw TTransportException(TTransportException::BAD_ARGS, "write() called after finish()"); + } + + // zlib's "deflate" function has enough logic in it that I think + // we're better off (performance-wise) buffering up small writes. + if (len > MIN_DIRECT_DEFLATE_SIZE) { + flushToZlib(uwbuf_, uwpos_, Z_NO_FLUSH); + uwpos_ = 0; + flushToZlib(buf, len, Z_NO_FLUSH); + } else if (len > 0) { + if (uwbuf_size_ - uwpos_ < len) { + flushToZlib(uwbuf_, uwpos_, Z_NO_FLUSH); + uwpos_ = 0; + } + memcpy(uwbuf_ + uwpos_, buf, len); + uwpos_ += len; + } +} + +void TZlibTransport::flush() { + if (output_finished_) { + throw TTransportException(TTransportException::BAD_ARGS, "flush() called after finish()"); + } + + flushToZlib(uwbuf_, uwpos_, Z_BLOCK); + uwpos_ = 0; + + if(wstream_->avail_out < 6){ + transport_->write(cwbuf_, cwbuf_size_ - wstream_->avail_out); + wstream_->next_out = cwbuf_; + wstream_->avail_out = cwbuf_size_; + } + + flushToTransport(Z_FULL_FLUSH); +} + +void TZlibTransport::finish() { + if (output_finished_) { + throw TTransportException(TTransportException::BAD_ARGS, "finish() called more than once"); + } + + flushToTransport(Z_FINISH); +} + +void TZlibTransport::flushToTransport(int flush) { + // write pending data in uwbuf_ to zlib + flushToZlib(uwbuf_, uwpos_, flush); + uwpos_ = 0; + + // write all available data from zlib to the transport + transport_->write(cwbuf_, cwbuf_size_ - wstream_->avail_out); + wstream_->next_out = cwbuf_; + wstream_->avail_out = cwbuf_size_; + + // flush the transport + transport_->flush(); +} + +void TZlibTransport::flushToZlib(const uint8_t* buf, int len, int flush) { + wstream_->next_in = const_cast(buf); + wstream_->avail_in = len; + + while (true) { + if ((flush == Z_NO_FLUSH || flush == Z_BLOCK) && wstream_->avail_in == 0) { + break; + } + + // If our ouput buffer is full, flush to the underlying transport. + if (wstream_->avail_out == 0) { + transport_->write(cwbuf_, cwbuf_size_); + wstream_->next_out = cwbuf_; + wstream_->avail_out = cwbuf_size_; + } + + int zlib_rv = deflate(wstream_, flush); + + if (flush == Z_FINISH && zlib_rv == Z_STREAM_END) { + assert(wstream_->avail_in == 0); + output_finished_ = true; + break; + } + + checkZlibRv(zlib_rv, wstream_->msg); + + if ((flush == Z_SYNC_FLUSH || flush == Z_FULL_FLUSH) && wstream_->avail_in == 0 + && wstream_->avail_out != 0) { + break; + } + } +} + +const uint8_t* TZlibTransport::borrow(uint8_t* buf, uint32_t* len) { + (void)buf; + // Don't try to be clever with shifting buffers. + // If we have enough data, give a pointer to it, + // otherwise let the protcol use its slow path. + if (readAvail() >= (int)*len) { + *len = (uint32_t)readAvail(); + return urbuf_ + urpos_; + } + return nullptr; +} + +void TZlibTransport::consume(uint32_t len) { + if (readAvail() >= (int)len) { + urpos_ += len; + } else { + throw TTransportException(TTransportException::BAD_ARGS, "consume did not follow a borrow."); + } +} + +void TZlibTransport::verifyChecksum() { + // If zlib has already reported the end of the stream, + // it has verified the checksum. + if (input_ended_) { + return; + } + + // This should only be called when reading is complete. + // If the caller still has unread data, throw an exception. + if (readAvail() > 0) { + throw TTransportException(TTransportException::CORRUPTED_DATA, + "verifyChecksum() called before end of zlib stream"); + } + + // Reset the rstream fields, in case avail_out is 0. + // (Since readAvail() is 0, we know there is no unread data in urbuf_) + rstream_->next_out = urbuf_; + rstream_->avail_out = urbuf_size_; + urpos_ = 0; + + // Call inflate() + // This will throw an exception if the checksum is bad. + bool performed_inflate = readFromZlib(); + if (!performed_inflate) { + // We needed to read from the underlying transport, and the read() call + // returned 0. + // + // Not all TTransport implementations behave the same way here, so we'll + // end up with different behavior depending on the underlying transport. + // + // For some transports (e.g., TFDTransport), read() blocks if no more data + // is available. They only return 0 if EOF has been reached, or if the + // remote endpoint has closed the connection. For those transports, + // verifyChecksum() will block until the checksum becomes available. + // + // Other transport types (e.g., TMemoryBuffer) always return 0 immediately + // if no more data is available. For those transport types, verifyChecksum + // will raise the following exception if the checksum is not available from + // the underlying transport yet. + throw TTransportException(TTransportException::CORRUPTED_DATA, + "checksum not available yet in " + "verifyChecksum()"); + } + + // If input_ended_ is true now, the checksum has been verified + if (input_ended_) { + return; + } + + // The caller invoked us before the actual end of the data stream + assert(rstream_->avail_out < urbuf_size_); + throw TTransportException(TTransportException::CORRUPTED_DATA, + "verifyChecksum() called before end of " + "zlib stream"); +} +} +} +} // apache::thrift::transport diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TZlibTransport.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TZlibTransport.h new file mode 100644 index 000000000..29afae0d4 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/transport/TZlibTransport.h @@ -0,0 +1,242 @@ +/* + * 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. + */ + +#ifndef _THRIFT_TRANSPORT_TZLIBTRANSPORT_H_ +#define _THRIFT_TRANSPORT_TZLIBTRANSPORT_H_ 1 + +#include +#include +#include +#include + +struct z_stream_s; + +namespace apache { +namespace thrift { +namespace transport { + +class TZlibTransportException : public TTransportException { +public: + TZlibTransportException(int status, const char* msg) + : TTransportException(TTransportException::INTERNAL_ERROR, errorMessage(status, msg)), + zlib_status_(status), + zlib_msg_(msg == nullptr ? "(null)" : msg) {} + + ~TZlibTransportException() noexcept override = default; + + int getZlibStatus() { return zlib_status_; } + std::string getZlibMessage() { return zlib_msg_; } + + static std::string errorMessage(int status, const char* msg) { + std::string rv = "zlib error: "; + if (msg) { + rv += msg; + } else { + rv += "(no message)"; + } + rv += " (status = "; + rv += to_string(status); + rv += ")"; + return rv; + } + + int zlib_status_; + std::string zlib_msg_; +}; + +/** + * This transport uses zlib to compress on write and decompress on read + * + * TODO(dreiss): Don't do an extra copy of the compressed data if + * the underlying transport is TBuffered or TMemory. + * + */ +class TZlibTransport : public TVirtualTransport { +public: + /** + * @param transport The transport to read compressed data from + * and write compressed data to. + * @param urbuf_size Uncompressed buffer size for reading. + * @param crbuf_size Compressed buffer size for reading. + * @param uwbuf_size Uncompressed buffer size for writing. + * @param cwbuf_size Compressed buffer size for writing. + * @param comp_level Compression level (0=none[fast], 6=default, 9=max[slow]). + */ + TZlibTransport(std::shared_ptr transport, + int urbuf_size = DEFAULT_URBUF_SIZE, + int crbuf_size = DEFAULT_CRBUF_SIZE, + int uwbuf_size = DEFAULT_UWBUF_SIZE, + int cwbuf_size = DEFAULT_CWBUF_SIZE, + int16_t comp_level = Z_DEFAULT_COMPRESSION) + : transport_(transport), + urpos_(0), + uwpos_(0), + input_ended_(false), + output_finished_(false), + urbuf_size_(urbuf_size), + crbuf_size_(crbuf_size), + uwbuf_size_(uwbuf_size), + cwbuf_size_(cwbuf_size), + urbuf_(nullptr), + crbuf_(nullptr), + uwbuf_(nullptr), + cwbuf_(nullptr), + rstream_(nullptr), + wstream_(nullptr), + comp_level_(comp_level) { + if (uwbuf_size_ < MIN_DIRECT_DEFLATE_SIZE) { + // Have to copy this into a local because of a linking issue. + int minimum = MIN_DIRECT_DEFLATE_SIZE; + throw TTransportException(TTransportException::BAD_ARGS, + "TZLibTransport: uncompressed write buffer must be at least" + + to_string(minimum) + "."); + } + + try { + urbuf_ = new uint8_t[urbuf_size]; + crbuf_ = new uint8_t[crbuf_size]; + uwbuf_ = new uint8_t[uwbuf_size]; + cwbuf_ = new uint8_t[cwbuf_size]; + + // Don't call this outside of the constructor. + initZlib(); + + } catch (...) { + delete[] urbuf_; + delete[] crbuf_; + delete[] uwbuf_; + delete[] cwbuf_; + throw; + } + } + + // Don't call this outside of the constructor. + void initZlib(); + + /** + * TZlibTransport destructor. + * + * Warning: Destroying a TZlibTransport object may discard any written but + * unflushed data. You must explicitly call flush() or finish() to ensure + * that data is actually written and flushed to the underlying transport. + */ + ~TZlibTransport() override; + + bool isOpen() const override; + bool peek() override; + + void open() override { transport_->open(); } + + void close() override { transport_->close(); } + + uint32_t read(uint8_t* buf, uint32_t len); + + void write(const uint8_t* buf, uint32_t len); + + void flush() override; + + /** + * Finalize the zlib stream. + * + * This causes zlib to flush any pending write data and write end-of-stream + * information, including the checksum. Once finish() has been called, no + * new data can be written to the stream. + */ + void finish(); + + const uint8_t* borrow(uint8_t* buf, uint32_t* len); + + void consume(uint32_t len); + + /** + * Verify the checksum at the end of the zlib stream. + * + * This may only be called after all data has been read. + * It verifies the checksum that was written by the finish() call. + */ + void verifyChecksum(); + + /** + * TODO(someone_smart): Choose smart defaults. + */ + static const int DEFAULT_URBUF_SIZE = 128; + static const int DEFAULT_CRBUF_SIZE = 1024; + static const int DEFAULT_UWBUF_SIZE = 128; + static const int DEFAULT_CWBUF_SIZE = 1024; + + std::shared_ptr getUnderlyingTransport() const { return transport_; } + +protected: + inline void checkZlibRv(int status, const char* msg); + inline void checkZlibRvNothrow(int status, const char* msg); + inline int readAvail() const; + void flushToTransport(int flush); + void flushToZlib(const uint8_t* buf, int len, int flush); + bool readFromZlib(); + +protected: + // Writes smaller than this are buffered up. + // Larger (or equal) writes are dumped straight to zlib. + static const uint32_t MIN_DIRECT_DEFLATE_SIZE = 32; + + std::shared_ptr transport_; + + int urpos_; + int uwpos_; + + /// True iff zlib has reached the end of the input stream. + bool input_ended_; + /// True iff we have finished the output stream. + bool output_finished_; + + uint32_t urbuf_size_; + uint32_t crbuf_size_; + uint32_t uwbuf_size_; + uint32_t cwbuf_size_; + + uint8_t* urbuf_; + uint8_t* crbuf_; + uint8_t* uwbuf_; + uint8_t* cwbuf_; + + struct z_stream_s* rstream_; + struct z_stream_s* wstream_; + + const int comp_level_; +}; + +/** + * Wraps a transport into a zlibbed one. + * + */ +class TZlibTransportFactory : public TTransportFactory { +public: + TZlibTransportFactory() = default; + + ~TZlibTransportFactory() override = default; + + std::shared_ptr getTransport(std::shared_ptr trans) override { + return std::shared_ptr(new TZlibTransport(trans)); + } +}; +} +} +} // apache::thrift::transport + +#endif // #ifndef _THRIFT_TRANSPORT_TZLIBTRANSPORT_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/windows/GetTimeOfDay.cpp b/src/jaegertracing/thrift/lib/cpp/src/thrift/windows/GetTimeOfDay.cpp new file mode 100644 index 000000000..0a0292c7e --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/windows/GetTimeOfDay.cpp @@ -0,0 +1,95 @@ +/* + * 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. + */ + +#include +#include + +// win32 +#if defined(__MINGW32__) + #include +#endif + +#if !defined(__MINGW32__) +struct timezone { + int tz_minuteswest; /* minutes W of Greenwich */ + int tz_dsttime; /* type of dst correction */ +}; +#endif + +#if defined(__MINGW32__) +int thrift_gettimeofday(struct timeval* tv, struct timezone* tz) { + return gettimeofday(tv,tz); +} +#else +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include + +// This code started from a "FREE implementation" posted to Stack Overflow at: +// https://stackoverflow.com/questions/10905892/equivalent-of-gettimeday-for-windows +// added: assert +// added: error handling +// added: C++ style casts +int thrift_gettimeofday(struct timeval * tp, struct timezone * tzp) +{ + // We don't fill it in so prove nobody is looking for the data + assert(tzp == NULL); + + // Note: some broken versions only have 8 trailing zero's, the correct epoch has 9 trailing zero's + // This magic number is the number of 100 nanosecond intervals since January 1, 1601 (UTC) + // until 00:00:00 January 1, 1970 + static const uint64_t EPOCH = static_cast(116444736000000000ULL); + + SYSTEMTIME system_time; + FILETIME file_time; + uint64_t time; + + GetSystemTime( &system_time ); + if (!SystemTimeToFileTime( &system_time, &file_time )) { + DWORD lastError = GetLastError(); + std::stringstream ss; + ss << "SystemTimeToFileTime failed: 0x" << std::hex << lastError; + using apache::thrift::transport::TTransportException; + throw TTransportException(TTransportException::INTERNAL_ERROR, ss.str()); + } + time = static_cast(file_time.dwLowDateTime ) ; + time += static_cast(file_time.dwHighDateTime) << 32; + + tp->tv_sec = static_cast((time - EPOCH) / 10000000L); + tp->tv_usec = static_cast(system_time.wMilliseconds * 1000); + return 0; +} +#endif + +int thrift_sleep(unsigned int seconds) { + ::Sleep(seconds * 1000); + return 0; +} +int thrift_usleep(unsigned int microseconds) { + unsigned int milliseconds = (microseconds + 999) / 1000; + ::Sleep(milliseconds); + return 0; +} + +char* thrift_ctime_r(const time_t* _clock, char* _buf) { + strcpy(_buf, ctime(_clock)); + return _buf; +} diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/windows/GetTimeOfDay.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/windows/GetTimeOfDay.h new file mode 100644 index 000000000..762ac5e24 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/windows/GetTimeOfDay.h @@ -0,0 +1,44 @@ +/* + * 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. + */ + +#ifndef _THRIFT_WINDOWS_GETTIMEOFDAY_H_ +#define _THRIFT_WINDOWS_GETTIMEOFDAY_H_ + +#if defined(_MSC_VER) && (_MSC_VER > 1200) +#pragma once +#endif // _MSC_VER + +#ifndef _WIN32 +#error This is a MSVC header only. +#endif + +#include +#include + +struct thrift_timespec { + int64_t tv_sec; + int64_t tv_nsec; +}; + +int thrift_gettimeofday(struct timeval* tv, struct timezone* tz); +int thrift_sleep(unsigned int seconds); +int thrift_usleep(unsigned int micro_seconds); +char* thrift_ctime_r(const time_t* _clock, char* _buf); + +#endif // _THRIFT_WINDOWS_GETTIMEOFDAY_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/windows/Operators.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/windows/Operators.h new file mode 100644 index 000000000..9b8609680 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/windows/Operators.h @@ -0,0 +1,40 @@ +/* + * 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. + */ + +#ifndef _THRIFT_WINDOWS_OPERATORS_H_ +#define _THRIFT_WINDOWS_OPERATORS_H_ + +#if defined(_MSC_VER) && (_MSC_VER > 1200) +#pragma once +#endif // _MSC_VER + +namespace apache { +namespace thrift { + +class TEnumIterator; + +inline bool operator==(const TEnumIterator&, const TEnumIterator&) { + // Not entirely sure what the test should be here. It is only to enable + // iterator debugging and is not used in release mode. + return true; +} +} +} // apache::thrift + +#endif // _THRIFT_WINDOWS_OPERATORS_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/windows/OverlappedSubmissionThread.cpp b/src/jaegertracing/thrift/lib/cpp/src/thrift/windows/OverlappedSubmissionThread.cpp new file mode 100644 index 000000000..5ac6fe00b --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/windows/OverlappedSubmissionThread.cpp @@ -0,0 +1,151 @@ +/* +* 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. +*/ + +#include +#include +#include +#include +#include + +namespace apache { +namespace thrift { +namespace transport { + +TOverlappedWorkItem::TOverlappedWorkItem() + : SLIST_ENTRY(), + action(UNKNOWN), + h(INVALID_HANDLE_VALUE), + buffer(NULL), + buffer_len(0), + overlap(), + last_error(0), + success(TRUE) { +} + +void TOverlappedWorkItem::reset(uint8_t* buf, uint32_t len, HANDLE event) { + memset(&overlap, 0, sizeof(overlap)); + overlap.hEvent = event; + buffer = buf; + buffer_len = len; + last_error = 0; + success = FALSE; +} + +uint32_t TOverlappedWorkItem::overlappedResults(bool signal_failure) { + DWORD bytes = 0; + BOOL result = ::GetOverlappedResult(h, &overlap, &bytes, TRUE); + if (signal_failure && !result) // get overlapped error case + { + GlobalOutput.perror("TPipe ::GetOverlappedResult errored GLE=", ::GetLastError()); + throw TTransportException(TTransportException::UNKNOWN, "TPipe: GetOverlappedResult failed"); + } + return bytes; +} + +bool TOverlappedWorkItem::process() { + BOOST_SCOPE_EXIT((&doneSubmittingEvent)) { SetEvent(doneSubmittingEvent.h); } + BOOST_SCOPE_EXIT_END + + switch (action) { + case (CONNECT): + success = ::ConnectNamedPipe(h, &overlap); + if (success == FALSE) + last_error = ::GetLastError(); + return true; + case (READ): + success = ::ReadFile(h, buffer, buffer_len, NULL, &overlap); + if (success == FALSE) + last_error = ::GetLastError(); + return true; + case (CANCELIO): + success = ::CancelIo(h); + if (success == FALSE) + last_error = ::GetLastError(); + return true; + case (STOP): + default: + return false; + } +} + +void TOverlappedSubmissionThread::addWorkItem(TOverlappedWorkItem* item) { + InterlockedPushEntrySList(&workList_, item); + SetEvent(workAvailableEvent_.h); + WaitForSingleObject(item->doneSubmittingEvent.h, INFINITE); +} + +TOverlappedSubmissionThread* TOverlappedSubmissionThread::acquire_instance() { + TAutoCrit lock(instanceGuard_); + if (instance_ == NULL) { + assert(instanceRefCount_ == 0); + instance_ = new TOverlappedSubmissionThread; + } + ++instanceRefCount_; + return instance_; +} +void TOverlappedSubmissionThread::release_instance() { + TAutoCrit lock(instanceGuard_); + if (--instanceRefCount_ == 0) { + delete instance_; + instance_ = NULL; + } +} + +TOverlappedSubmissionThread::TOverlappedSubmissionThread() { + stopItem_.action = TOverlappedWorkItem::STOP; + + InitializeSListHead(&workList_); + thread_ = (HANDLE)_beginthreadex(NULL, 0, thread_proc, this, 0, NULL); + if (thread_ == 0) { + GlobalOutput.perror("TOverlappedSubmissionThread unable to create thread, errno=", errno); + throw TTransportException(TTransportException::NOT_OPEN, + " TOverlappedSubmissionThread unable to create thread"); + } +} + +TOverlappedSubmissionThread::~TOverlappedSubmissionThread() { + addWorkItem(&stopItem_); + ::WaitForSingleObject(thread_, INFINITE); + CloseHandle(thread_); +} + +void TOverlappedSubmissionThread::run() { + for (;;) { + WaitForSingleObject(workAvailableEvent_.h, INFINITE); + // todo check result + SLIST_ENTRY* entry = NULL; + while ((entry = InterlockedPopEntrySList(&workList_)) != NULL) { + TOverlappedWorkItem& item = *static_cast(entry); + if (!item.process()) + return; + } + } +} + +unsigned __stdcall TOverlappedSubmissionThread::thread_proc(void* addr) { + static_cast(addr)->run(); + return 0; +} + +TCriticalSection TOverlappedSubmissionThread::instanceGuard_; +TOverlappedSubmissionThread* TOverlappedSubmissionThread::instance_; +uint32_t TOverlappedSubmissionThread::instanceRefCount_ = 0; +} +} +} // apach::thrift::transport diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/windows/OverlappedSubmissionThread.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/windows/OverlappedSubmissionThread.h new file mode 100644 index 000000000..dd0c5c957 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/windows/OverlappedSubmissionThread.h @@ -0,0 +1,133 @@ +/* + * 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. + */ + +#ifndef _THRIFT_WINDOWS_OverlappedSubmissionThread_H_ +#define _THRIFT_WINDOWS_OverlappedSubmissionThread_H_ 1 + +#ifndef _WIN32 +#error "OverlappedSubmissionThread.h is only usable on Windows" +#endif + +#include +#include +#include + +/* + *** Why does this class exist? + In short, because we want to enable something similar to a "select" loop, on Windows, with + named pipes. The core of the "select" loop is a call to WaitForMultipleObjects. So that means + we need a signalable object that indicates when data is available. + + A pipe handle doesn't do that. A pipe handle is signaled when a read or write completes, and if + no one has called read or write, then the pipe handle is useless in WaitForMultipleObjects. So + instead, we use overlapped I/O. With overlapped I/O, you call read, and associate an event with + the read. When the read finishes, the event is signaled. This means that when you create a pipe, + you start a read. When the customer calls read on your transport object, you wait for the last + read to finish, and then kick off another. + + There is one big caveat to this though. The thread that initiated the read must stay alive. If + the thread that initiated the read exits, then the read completes in an error state. To ensure + that the initiating thread stays alive, we create a singleton thread whose sole responsibility is + to manage this overlapped I/O requests. This introduces some overhead, but it is overhead that + is necessary for correct behavior. + + This thread currently supports connect, read, and cancel io. So far, I haven't needed to put any + writes on this thread, but if needed, it could be done. The client write buffer would need to be + copied to ensure that it doesn't get invalidated. + + *** How does one use this class? + Create a TOverlappedWorkItem, and fill in the action and "h", then call reset(). Your work item + is now ready to be submitted to the overlapped submission thread. Create a TAutoOverlapThread, + and call thread->addWorkItem with your work item. After addWorkItem completes, you may inspect + last_error and success. At some point in the future, call workItem.overlappedResults to wait + until the operation has completed. +*/ + +namespace apache { +namespace thrift { +namespace transport { + +struct DECLSPEC_ALIGN(MEMORY_ALLOCATION_ALIGNMENT) TOverlappedWorkItem : public SLIST_ENTRY { + TOverlappedWorkItem(); + + enum action_t { + UNKNOWN = 3000, + CONNECT, + READ, + CANCELIO, + STOP, + }; + + TAutoResetEvent doneSubmittingEvent; + action_t action; + HANDLE h; + uint8_t* buffer; + uint32_t buffer_len; + OVERLAPPED overlap; + + DWORD last_error; + BOOL success; + + void reset(uint8_t* buf, uint32_t len, HANDLE event); + uint32_t overlappedResults(bool signal_failure = true); + bool process(); +}; + +class TOverlappedSubmissionThread : boost::noncopyable { +public: + void addWorkItem(TOverlappedWorkItem* item); + + // singleton stuff +public: + static TOverlappedSubmissionThread* acquire_instance(); + static void release_instance(); + +private: + static TCriticalSection instanceGuard_; + static TOverlappedSubmissionThread* instance_; + static uint32_t instanceRefCount_; + + // thread details +private: + TOverlappedSubmissionThread(); + ~TOverlappedSubmissionThread(); + void run(); + static unsigned __stdcall thread_proc(void* addr); + +private: + DECLSPEC_ALIGN(MEMORY_ALLOCATION_ALIGNMENT) SLIST_HEADER workList_; + TOverlappedWorkItem stopItem_; + TAutoResetEvent workAvailableEvent_; + HANDLE thread_; +}; + +class TAutoOverlapThread : boost::noncopyable { +private: + TOverlappedSubmissionThread* p; + +public: + TAutoOverlapThread() : p(TOverlappedSubmissionThread::acquire_instance()) {} + ~TAutoOverlapThread() { TOverlappedSubmissionThread::release_instance(); } + TOverlappedSubmissionThread* operator->() { return p; } +}; +} +} +} // apache::thrift::transport + +#endif diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/windows/SocketPair.cpp b/src/jaegertracing/thrift/lib/cpp/src/thrift/windows/SocketPair.cpp new file mode 100644 index 000000000..7228832ee --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/windows/SocketPair.cpp @@ -0,0 +1,100 @@ +/* socketpair.c + * Copyright 2007 by Nathan C. Myers ; some rights reserved. + * This code is Free Software. It may be copied freely, in original or + * modified form, subject only to the restrictions that (1) the author is + * relieved from all responsibilities for any use for any purpose, and (2) + * this copyright notice must be retained, unchanged, in its entirety. If + * for any reason the author might be held responsible for any consequences + * of copying or use, license is withheld. + */ + +/* + * 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. + */ + +#include +#include + +// stl +#include + +// Win32 +#include + +int thrift_socketpair(int d, int type, int protocol, THRIFT_SOCKET sv[2]) { + THRIFT_UNUSED_VARIABLE(protocol); + THRIFT_UNUSED_VARIABLE(type); + THRIFT_UNUSED_VARIABLE(d); + + union { + struct sockaddr_in inaddr; + struct sockaddr addr; + } a; + THRIFT_SOCKET listener; + int e; + socklen_t addrlen = sizeof(a.inaddr); + DWORD flags = 0; + int reuse = 1; + + if (sv == 0) { + WSASetLastError(WSAEINVAL); + return SOCKET_ERROR; + } + + listener = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (listener == INVALID_SOCKET) + return SOCKET_ERROR; + + memset(&a, 0, sizeof(a)); + a.inaddr.sin_family = AF_INET; + a.inaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + a.inaddr.sin_port = 0; + + sv[0] = sv[1] = INVALID_SOCKET; + do { + // ignore errors coming out of this setsockopt. This is because + // SO_EXCLUSIVEADDRUSE requires admin privileges on WinXP, but we don't + // want to force socket pairs to be an admin. + setsockopt(listener, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (char*)&reuse, (socklen_t)sizeof(reuse)); + if (bind(listener, &a.addr, sizeof(a.inaddr)) == SOCKET_ERROR) + break; + if (getsockname(listener, &a.addr, &addrlen) == SOCKET_ERROR) + break; + if (listen(listener, 1) == SOCKET_ERROR) + break; + sv[0] = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, flags); + if (sv[0] == INVALID_SOCKET) + break; + if (connect(sv[0], &a.addr, sizeof(a.inaddr)) == SOCKET_ERROR) + break; + sv[1] = accept(listener, NULL, NULL); + if (sv[1] == INVALID_SOCKET) + break; + + closesocket(listener); + return 0; + + } while (0); + + e = WSAGetLastError(); + closesocket(listener); + closesocket(sv[0]); + closesocket(sv[1]); + WSASetLastError(e); + return SOCKET_ERROR; +} diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/windows/SocketPair.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/windows/SocketPair.h new file mode 100644 index 000000000..86bf43150 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/windows/SocketPair.h @@ -0,0 +1,37 @@ +/* + * 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. + */ + +#ifndef _THRIFT_WINDOWS_SOCKETPAIR_H_ +#define _THRIFT_WINDOWS_SOCKETPAIR_H_ 1 + +#if defined(_MSC_VER) && (_MSC_VER > 1200) +#pragma once +#endif // _MSC_VER + +#ifndef _WIN32 +#error This is a MSVC header only. +#endif + +// Win32 +#include +#include + +int thrift_socketpair(int d, int type, int protocol, THRIFT_SOCKET sv[2]); + +#endif // _THRIFT_WINDOWS_SOCKETPAIR_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/windows/Sync.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/windows/Sync.h new file mode 100644 index 000000000..5d321996b --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/windows/Sync.h @@ -0,0 +1,104 @@ +/* + * 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. + */ + +#ifndef _THRIFT_WINDOWS_Sync_H_ +#define _THRIFT_WINDOWS_Sync_H_ 1 + +#ifndef _WIN32 +#error "windows/Sync.h is only usable on Windows" +#endif + +#include +#include +#include + +/* + Lightweight synchronization objects that only make sense on Windows. For cross-platform + code, use the classes found in the concurrency namespace +*/ + +namespace apache { +namespace thrift { + +struct TCriticalSection : boost::noncopyable { + CRITICAL_SECTION cs; + TCriticalSection() { InitializeCriticalSection(&cs); } + ~TCriticalSection() { DeleteCriticalSection(&cs); } +}; + +class TAutoCrit : boost::noncopyable { +private: + CRITICAL_SECTION* cs_; + +public: + explicit TAutoCrit(TCriticalSection& cs) : cs_(&cs.cs) { EnterCriticalSection(cs_); } + ~TAutoCrit() { LeaveCriticalSection(cs_); } +}; + +struct TAutoResetEvent : boost::noncopyable { + HANDLE h; + + TAutoResetEvent() { + h = CreateEvent(NULL, FALSE, FALSE, NULL); + if (h == NULL) { + GlobalOutput.perror("TAutoResetEvent unable to create event, GLE=", GetLastError()); + throw apache::thrift::concurrency::SystemResourceException("CreateEvent failed"); + } + } + ~TAutoResetEvent() { CloseHandle(h); } +}; + +struct TManualResetEvent : boost::noncopyable { + HANDLE h; + + TManualResetEvent() { + h = CreateEvent(NULL, TRUE, FALSE, NULL); + if (h == NULL) { + GlobalOutput.perror("TManualResetEvent unable to create event, GLE=", GetLastError()); + throw apache::thrift::concurrency::SystemResourceException("CreateEvent failed"); + } + } + ~TManualResetEvent() { CloseHandle(h); } +}; + +struct TAutoHandle : boost::noncopyable { + HANDLE h; + explicit TAutoHandle(HANDLE h_ = INVALID_HANDLE_VALUE) : h(h_) {} + ~TAutoHandle() { + if (h != INVALID_HANDLE_VALUE) + CloseHandle(h); + } + + HANDLE release() { + HANDLE retval = h; + h = INVALID_HANDLE_VALUE; + return retval; + } + void reset(HANDLE h_ = INVALID_HANDLE_VALUE) { + if (h_ == h) + return; + if (h != INVALID_HANDLE_VALUE) + CloseHandle(h); + h = h_; + } +}; +} +} // apache::thrift + +#endif diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/windows/TWinsockSingleton.cpp b/src/jaegertracing/thrift/lib/cpp/src/thrift/windows/TWinsockSingleton.cpp new file mode 100644 index 000000000..a502cbdc6 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/windows/TWinsockSingleton.cpp @@ -0,0 +1,59 @@ +/* + * 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. + */ + +#include + +// boost +#include + +namespace apache { +namespace thrift { +namespace transport { + +TWinsockSingleton::instance_ptr TWinsockSingleton::instance_ptr_(NULL); +std::once_flag TWinsockSingleton::flags_; + +//------------------------------------------------------------------------------ +TWinsockSingleton::TWinsockSingleton(void) { + WORD version(MAKEWORD(2, 2)); + WSAData data = {0}; + + int error(WSAStartup(version, &data)); + if (error != 0) { + throw std::runtime_error("Failed to initialise Winsock."); + } +} + +//------------------------------------------------------------------------------ +TWinsockSingleton::~TWinsockSingleton(void) { + WSACleanup(); +} + +//------------------------------------------------------------------------------ +void TWinsockSingleton::create(void) { + std::call_once(flags_, init); +} + +//------------------------------------------------------------------------------ +void TWinsockSingleton::init(void) { + instance_ptr_.reset(new TWinsockSingleton); +} +} +} +} // apache::thrift::transport diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/windows/TWinsockSingleton.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/windows/TWinsockSingleton.h new file mode 100644 index 000000000..a30806b98 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/windows/TWinsockSingleton.h @@ -0,0 +1,73 @@ +/* + * 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. + */ + +#ifndef _THRIFT_TRANSPORT_WINDOWS_TWINSOCKSINGLETON_H_ +#define _THRIFT_TRANSPORT_WINDOWS_TWINSOCKSINGLETON_H_ 1 + +#if defined(_MSC_VER) && (_MSC_VER > 1200) +#pragma once +#endif // _MSC_VER + +#ifndef _WIN32 +#error This is a MSVC header only. +#endif + +#include + +// boost +#include + +#include +#include + + +namespace apache { +namespace thrift { +namespace transport { + +/** + * Winsock2 must be intialised once only in order to create sockets. This class + * performs a one time initialisation when create is called. + */ +class TWinsockSingleton : private boost::noncopyable { + +public: + typedef std::shared_ptr instance_ptr; + +private: + TWinsockSingleton(void); + +public: + ~TWinsockSingleton(void); + +public: + static void create(void); + +private: + static void init(void); + +private: + static instance_ptr instance_ptr_; + static std::once_flag flags_; +}; +} +} +} // apache::thrift::transport + +#endif // _THRIFT_TRANSPORT_WINDOWS_TWINSOCKSINGLETON_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/windows/WinFcntl.cpp b/src/jaegertracing/thrift/lib/cpp/src/thrift/windows/WinFcntl.cpp new file mode 100644 index 000000000..c907e92f2 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/windows/WinFcntl.cpp @@ -0,0 +1,101 @@ +/* + * 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. + */ + +#include + +int thrift_fcntl(THRIFT_SOCKET fd, int cmd, int flags) { + if (cmd != THRIFT_F_GETFL && cmd != THRIFT_F_SETFL) { + return -1; + } + + if (flags != THRIFT_O_NONBLOCK && flags != 0) { + return -1; + } + + if (cmd == THRIFT_F_GETFL) { + return 0; + } + + int res; + if (flags) { + res = ioctlsocket(fd, FIONBIO, reinterpret_cast(&(flags = 1))); + } else { + res = ioctlsocket(fd, FIONBIO, reinterpret_cast(&(flags = 0))); + } + + return res; +} + +#if WINVER <= 0x0502 // XP, Server2003 +int thrift_poll(THRIFT_POLLFD* fdArray, ULONG nfds, INT timeout) { + fd_set read_fds, write_fds; + fd_set* read_fds_ptr = NULL; + fd_set* write_fds_ptr = NULL; + + FD_ZERO(&read_fds); + FD_ZERO(&write_fds); + + for (ULONG i = 0; i < nfds; i++) { + // Read (in) socket + if ((fdArray[i].events & THRIFT_POLLIN) == THRIFT_POLLIN) { + read_fds_ptr = &read_fds; + FD_SET(fdArray[i].fd, &read_fds); + } + // Write (out) socket + else if ((fdArray[i].events & THRIFT_POLLOUT) == THRIFT_POLLOUT) { + write_fds_ptr = &write_fds; + FD_SET(fdArray[i].fd, &write_fds); + } + } + + timeval time_out; + timeval* time_out_ptr = NULL; + if (timeout >= 0) { + time_out.tv_sec = timeout / 1000; + time_out.tv_usec = (timeout % 1000) * 1000; + time_out_ptr = &time_out; + } else { // to avoid compiler warnings + (void)time_out; + (void)timeout; + } + + int sktready = select(1, read_fds_ptr, write_fds_ptr, NULL, time_out_ptr); + if (sktready > 0) { + for (ULONG i = 0; i < nfds; i++) { + fdArray[i].revents = 0; + if (FD_ISSET(fdArray[i].fd, &read_fds)) + fdArray[i].revents |= THRIFT_POLLIN; + if (FD_ISSET(fdArray[i].fd, &write_fds)) + fdArray[i].revents |= THRIFT_POLLOUT; + } + } + return sktready; +} +#else // Vista, Win7... +int thrift_poll(THRIFT_POLLFD* fdArray, ULONG nfds, INT timeout) { + return WSAPoll(fdArray, nfds, timeout); +} +#endif // WINVER + +#ifdef _WIN32_WCE +std::string thrift_wstr2str(std::wstring ws) { + std::string s(ws.begin(), ws.end()); + return s; +} +#endif diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/windows/WinFcntl.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/windows/WinFcntl.h new file mode 100644 index 000000000..6c6be97ef --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/windows/WinFcntl.h @@ -0,0 +1,56 @@ +/* + * 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. + */ + +#ifndef _THRIFT_WINDOWS_FCNTL_H_ +#define _THRIFT_WINDOWS_FCNTL_H_ 1 + +#if defined(_MSC_VER) && (_MSC_VER > 1200) +#pragma once +#endif // _MSC_VER + +#ifndef _WIN32 +#error This is a MSVC header only. +#endif + +#ifdef _WIN32_WCE +#include +#endif + +// Win32 +#include +#include + +#if WINVER <= 0x0502 // XP, Server2003 +struct thrift_pollfd { + THRIFT_SOCKET fd; + SHORT events; + SHORT revents; +}; +#endif + +extern "C" { +int thrift_fcntl(THRIFT_SOCKET fd, int cmd, int flags); +int thrift_poll(THRIFT_POLLFD* fdArray, ULONG nfds, INT timeout); +} + +#ifdef _WIN32_WCE +std::string thrift_wstr2str(std::wstring ws); +#endif + +#endif // _THRIFT_WINDOWS_FCNTL_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/src/thrift/windows/config.h b/src/jaegertracing/thrift/lib/cpp/src/thrift/windows/config.h new file mode 100644 index 000000000..063a92ad8 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/src/thrift/windows/config.h @@ -0,0 +1,72 @@ +/* + * 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. + */ + +#ifndef _THRIFT_WINDOWS_CONFIG_H_ +#define _THRIFT_WINDOWS_CONFIG_H_ 1 + +#if defined(_MSC_VER) && (_MSC_VER > 1200) +#pragma once +#endif // _MSC_VER + +#ifndef _WIN32 +#error "This is a Windows header only" +#endif + +// Something that defines PRId64 is required to build +#define HAVE_INTTYPES_H 1 + +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0601 +#endif + +#if defined(_M_IX86) || defined(_M_X64) +#define ARITHMETIC_RIGHT_SHIFT 1 +#define SIGNED_RIGHT_SHIFT_IS 1 +#endif + +#ifndef __MINGW32__ +#pragma warning(disable : 4996) // Deprecated posix name. +#endif + +#define HAVE_GETTIMEOFDAY 1 +#define HAVE_SYS_STAT_H 1 + +#include + +#include +#include +#include +#include +#include +#include + +// windows +#include +#include +#ifndef __MINGW32__ + #ifdef _WIN32_WCE + #pragma comment(lib, "Ws2.lib") + #else + #pragma comment(lib, "Ws2_32.lib") + #pragma comment(lib, "advapi32.lib") // For security APIs in TPipeServer + #pragma comment(lib, "Shlwapi.lib") // For StrStrIA in TPipeServer + #endif +#endif // __MINGW32__ + +#endif // _THRIFT_WINDOWS_CONFIG_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/test/AllProtocolTests.cpp b/src/jaegertracing/thrift/lib/cpp/test/AllProtocolTests.cpp new file mode 100644 index 000000000..6b5c7c436 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/test/AllProtocolTests.cpp @@ -0,0 +1,47 @@ +/* + * 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. + */ + +#include + +#include +#include +#include + +#define BOOST_TEST_MODULE AllProtocolTests +#include + +#include "AllProtocolTests.tcc" + +using namespace apache::thrift; +using namespace apache::thrift::protocol; +using namespace apache::thrift::transport; + +char errorMessage[ERR_LEN]; + +BOOST_AUTO_TEST_CASE(test_binary_protocol) { + testProtocol("TBinaryProtocol"); +} + +BOOST_AUTO_TEST_CASE(test_little_binary_protocol) { + testProtocol("TLEBinaryProtocol"); +} + +BOOST_AUTO_TEST_CASE(test_compact_protocol) { + testProtocol("TCompactProtocol"); +} diff --git a/src/jaegertracing/thrift/lib/cpp/test/AllProtocolTests.tcc b/src/jaegertracing/thrift/lib/cpp/test/AllProtocolTests.tcc new file mode 100644 index 000000000..80a4ea097 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/test/AllProtocolTests.tcc @@ -0,0 +1,225 @@ +/* + * 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. + */ + +#ifndef _THRIFT_TEST_GENERICPROTOCOLTEST_TCC_ +#define _THRIFT_TEST_GENERICPROTOCOLTEST_TCC_ 1 + +#include + +#include +#include +#include + +#include "GenericHelpers.h" + +using std::shared_ptr; +using namespace apache::thrift; +using namespace apache::thrift::protocol; +using namespace apache::thrift::transport; + +#define ERR_LEN 512 +extern char errorMessage[ERR_LEN]; + +template +void testNaked(Val val) { + shared_ptr transport(new TMemoryBuffer()); + shared_ptr protocol(new TProto(transport)); + + GenericIO::write(protocol, val); + Val out; + GenericIO::read(protocol, out); + if (out != val) { + THRIFT_SNPRINTF(errorMessage, + ERR_LEN, + "Invalid naked test (type: %s)", + ClassNames::getName()); + throw TException(errorMessage); + } +} + +template +void testField(const Val val) { + shared_ptr transport(new TMemoryBuffer()); + shared_ptr protocol(new TProto(transport)); + + protocol->writeStructBegin("test_struct"); + protocol->writeFieldBegin("test_field", type, (int16_t)15); + + GenericIO::write(protocol, val); + + protocol->writeFieldEnd(); + protocol->writeStructEnd(); + + std::string name; + TType fieldType; + int16_t fieldId; + + protocol->readStructBegin(name); + protocol->readFieldBegin(name, fieldType, fieldId); + + if (fieldId != 15) { + THRIFT_SNPRINTF(errorMessage, ERR_LEN, "Invalid ID (type: %s)", typeid(val).name()); + throw TException(errorMessage); + } + if (fieldType != type) { + THRIFT_SNPRINTF(errorMessage, ERR_LEN, "Invalid Field Type (type: %s)", typeid(val).name()); + throw TException(errorMessage); + } + + Val out; + GenericIO::read(protocol, out); + + if (out != val) { + THRIFT_SNPRINTF(errorMessage, ERR_LEN, "Invalid value read (type: %s)", typeid(val).name()); + throw TException(errorMessage); + } + + protocol->readFieldEnd(); + protocol->readStructEnd(); +} + +template +void testMessage() { + struct TMessage { + const char* name; + TMessageType type; + int32_t seqid; + } messages[] = {{"short message name", T_CALL, 0}, + {"1", T_REPLY, 12345}, + {"loooooooooooooooooooooooooooooooooong", T_EXCEPTION, 1 << 16}, + {"one way push", T_ONEWAY, 12}, + {"Janky", T_CALL, 0}}; + const int messages_count = sizeof(messages) / sizeof(TMessage); + + for (int i = 0; i < messages_count; i++) { + shared_ptr transport(new TMemoryBuffer()); + shared_ptr protocol(new TProto(transport)); + + protocol->writeMessageBegin(messages[i].name, messages[i].type, messages[i].seqid); + protocol->writeMessageEnd(); + + std::string name; + TMessageType type; + int32_t seqid; + + protocol->readMessageBegin(name, type, seqid); + if (name != messages[i].name || type != messages[i].type || seqid != messages[i].seqid) { + throw TException("readMessageBegin failed."); + } + } +} + +template +void testProtocol(const char* protoname) { + try { + testNaked((int8_t)123); + + for (int32_t i = 0; i < 128; i++) { + testField((int8_t)i); + testField((int8_t)-i); + } + + testNaked((int16_t)0); + testNaked((int16_t)1); + testNaked((int16_t)15000); + testNaked((int16_t)0x7fff); + testNaked((int16_t)-1); + testNaked((int16_t)-15000); + testNaked((int16_t)-0x7fff); + testNaked((std::numeric_limits::min)()); + testNaked((std::numeric_limits::max)()); + + testField((int16_t)0); + testField((int16_t)1); + testField((int16_t)7); + testField((int16_t)150); + testField((int16_t)15000); + testField((int16_t)0x7fff); + testField((int16_t)-1); + testField((int16_t)-7); + testField((int16_t)-150); + testField((int16_t)-15000); + testField((int16_t)-0x7fff); + + testNaked(0); + testNaked(1); + testNaked(15000); + testNaked(0xffff); + testNaked(-1); + testNaked(-15000); + testNaked(-0xffff); + testNaked((std::numeric_limits::min)()); + testNaked((std::numeric_limits::max)()); + + testField(0); + testField(1); + testField(7); + testField(150); + testField(15000); + testField(31337); + testField(0xffff); + testField(0xffffff); + testField(-1); + testField(-7); + testField(-150); + testField(-15000); + testField(-0xffff); + testField(-0xffffff); + testNaked((std::numeric_limits::min)()); + testNaked((std::numeric_limits::max)()); + testNaked((std::numeric_limits::min)() + 10); + testNaked((std::numeric_limits::max)() - 16); + testNaked((std::numeric_limits::min)()); + testNaked((std::numeric_limits::max)()); + + testNaked(0); + for (int64_t i = 0; i < 62; i++) { + testNaked(1LL << i); + testNaked(-(1LL << i)); + } + + testField(0); + for (int i = 0; i < 62; i++) { + testField(1LL << i); + testField(-(1LL << i)); + } + + testNaked(123.456); + + testNaked(""); + testNaked("short"); + testNaked("borderlinetiny"); + testNaked("a bit longer than the smallest possible"); + testNaked("\x1\x2\x3\x4\x5\x6\x7\x8\x9\xA"); // kinda binary test + + testField(""); + testField("short"); + testField("borderlinetiny"); + testField("a bit longer than the smallest possible"); + + testMessage(); + + printf("%s => OK\n", protoname); + } catch (const TException &e) { + THRIFT_SNPRINTF(errorMessage, ERR_LEN, "%s => Test FAILED: %s", protoname, e.what()); + throw TException(errorMessage); + } +} + +#endif diff --git a/src/jaegertracing/thrift/lib/cpp/test/AnnotationTest.cpp b/src/jaegertracing/thrift/lib/cpp/test/AnnotationTest.cpp new file mode 100644 index 000000000..2e18840e9 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/test/AnnotationTest.cpp @@ -0,0 +1,68 @@ +/* + * 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. + */ +#define BOOST_TEST_MODULE AnnotationTest +#include +#include "gen-cpp/AnnotationTest_types.h" +#include +#include + +// Normally thrift generates ostream operators, however +// with the annotation "cpp.customostream" one can tell the +// compiler they are going to provide their own, and not +// emit operator << or printTo(). + +std::ostream& operator<<(std::ostream& os, const ostr_custom& osc) +{ + os << "{ bar = " << osc.bar << "; }"; + return os; +} + +BOOST_AUTO_TEST_SUITE(BOOST_TEST_MODULE) + +BOOST_AUTO_TEST_CASE(test_cpp_compiler_generated_ostream_operator) +{ + ostr_default def; + def.__set_bar(10); + + std::stringstream ssd; + ssd << def; + BOOST_CHECK_EQUAL(ssd.str(), "ostr_default(bar=10)"); +} + +BOOST_AUTO_TEST_CASE(test_cpp_customostream_uses_consuming_application_definition) +{ + ostr_custom cus; + cus.__set_bar(10); + + std::stringstream csd; + csd << cus; + BOOST_CHECK_EQUAL(csd.str(), "{ bar = 10; }"); +} + +/** + * Disabled; see THRIFT-1567 - not sure what it is supposed to do +BOOST_AUTO_TEST_CASE(test_cpp_type) { + // Check that the "cpp.type" annotation changes "struct foo" to "DenseFoo" + // This won't compile if the annotation is mishandled + DenseFoo foo; + foo.__set_bar(5); +} + */ + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/jaegertracing/thrift/lib/cpp/test/Base64Test.cpp b/src/jaegertracing/thrift/lib/cpp/test/Base64Test.cpp new file mode 100644 index 000000000..7686e4e7b --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/test/Base64Test.cpp @@ -0,0 +1,74 @@ +/* + * 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. + */ + +#include +#include + +using apache::thrift::protocol::base64_encode; +using apache::thrift::protocol::base64_decode; + +BOOST_AUTO_TEST_SUITE(Base64Test) + +void setupTestData(int i, uint8_t* data, int& len) { + len = 0; + do { + data[len] = (uint8_t)(i & 0xFF); + i >>= 8; + len++; + } while ((len < 3) && (i != 0)); + + BOOST_ASSERT(i == 0); +} + +void checkEncoding(uint8_t* data, int len) { +#ifdef NDEBUG + ((void)data); +#endif + + for (int i = 0; i < len; i++) { + BOOST_ASSERT(isalnum(data[i]) || data[i] == '/' || data[i] == '+'); + } +} + +BOOST_AUTO_TEST_CASE(test_Base64_Encode_Decode) { + int len; + uint8_t testInput[3]; + uint8_t testOutput[4]; + + // Test all possible encoding / decoding cases given the + // three byte limit for base64_encode. + + for (int i = 0xFFFFFF; i >= 0; i--) { + + // fill testInput based on i + setupTestData(i, testInput, len); + + // encode the test data, then decode it again + base64_encode(testInput, len, testOutput); + + // verify each byte has a valid Base64 value (alphanumeric or either + or /) + checkEncoding(testOutput, len); + + // decode output and check that it matches input + base64_decode(testOutput, len + 1); + BOOST_ASSERT(0 == memcmp(testInput, testOutput, len)); + } +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/jaegertracing/thrift/lib/cpp/test/Benchmark.cpp b/src/jaegertracing/thrift/lib/cpp/test/Benchmark.cpp new file mode 100644 index 000000000..56adac0b2 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/test/Benchmark.cpp @@ -0,0 +1,244 @@ +/* + * 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. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif +#include +#define _USE_MATH_DEFINES +#include +#include +#include "thrift/protocol/TBinaryProtocol.h" +#include "thrift/transport/TBufferTransports.h" +#include "gen-cpp/DebugProtoTest_types.h" + +#ifdef HAVE_SYS_TIME_H +#include +#endif + +class Timer { +public: + timeval vStart; + + Timer() { THRIFT_GETTIMEOFDAY(&vStart, nullptr); } + void start() { THRIFT_GETTIMEOFDAY(&vStart, nullptr); } + + double frame() { + timeval vEnd; + THRIFT_GETTIMEOFDAY(&vEnd, nullptr); + double dstart = vStart.tv_sec + ((double)vStart.tv_usec / 1000000.0); + double dend = vEnd.tv_sec + ((double)vEnd.tv_usec / 1000000.0); + return dend - dstart; + } +}; + +int main() { + using namespace thrift::test::debug; + using namespace apache::thrift::transport; + using namespace apache::thrift::protocol; + using std::cout; + using std::endl; + + OneOfEach ooe; + ooe.im_true = true; + ooe.im_false = false; + ooe.a_bite = 0x7f; + ooe.integer16 = 27000; + ooe.integer32 = 1 << 24; + ooe.integer64 = (uint64_t)6000 * 1000 * 1000; + ooe.double_precision = M_PI; + ooe.some_characters = "JSON THIS! \"\1"; + ooe.zomg_unicode = "\xd7\n\a\t"; + ooe.base64 = "\1\2\3\255"; + + int num = 100000; + std::shared_ptr buf(new TMemoryBuffer(num*1000)); + + uint8_t* data = nullptr; + uint32_t datasize = 0; + + { + buf->resetBuffer(); + TBinaryProtocolT prot(buf); + double elapsed = 0.0; + Timer timer; + + for (int i = 0; i < num; i++) { + ooe.write(&prot); + } + elapsed = timer.frame(); + cout << "Write big endian: " << num / (1000 * elapsed) << " kHz" << endl; + } + + buf->getBuffer(&data, &datasize); + + { + std::shared_ptr buf2(new TMemoryBuffer(data, datasize)); + TBinaryProtocolT prot(buf2); + OneOfEach ooe2; + double elapsed = 0.0; + Timer timer; + + for (int i = 0; i < num; i++) { + ooe2.read(&prot); + } + elapsed = timer.frame(); + cout << " Read big endian: " << num / (1000 * elapsed) << " kHz" << endl; + } + + { + buf->resetBuffer(); + TBinaryProtocolT prot(buf); + double elapsed = 0.0; + Timer timer; + + for (int i = 0; i < num; i++) { + ooe.write(&prot); + } + elapsed = timer.frame(); + cout << "Write little endian: " << num / (1000 * elapsed) << " kHz" << endl; + } + + { + OneOfEach ooe2; + std::shared_ptr buf2(new TMemoryBuffer(data, datasize)); + TBinaryProtocolT prot(buf2); + double elapsed = 0.0; + Timer timer; + + for (int i = 0; i < num; i++) { + ooe2.read(&prot); + } + elapsed = timer.frame(); + cout << " Read little endian: " << num / (1000 * elapsed) << " kHz" << endl; + } + + { + buf->resetBuffer(); + TBinaryProtocolT prot(buf); + double elapsed = 0.0; + Timer timer; + + for (int i = 0; i < num; i++) { + ooe.write(&prot); + } + elapsed = timer.frame(); + cout << "Write big endian: " << num / (1000 * elapsed) << " kHz" << endl; + } + + { + std::shared_ptr buf2(new TMemoryBuffer(data, datasize)); + TBinaryProtocolT prot(buf2); + OneOfEach ooe2; + double elapsed = 0.0; + Timer timer; + + for (int i = 0; i < num; i++) { + ooe2.read(&prot); + } + elapsed = timer.frame(); + cout << " Read big endian: " << num / (1000 * elapsed) << " kHz" << endl; + } + + + data = nullptr; + datasize = 0; + num = 10000000; + + ListDoublePerf listDoublePerf; + listDoublePerf.field.reserve(num); + for (int x = 0; x < num; ++x) + listDoublePerf.field.push_back(double(x)); + + buf.reset(new TMemoryBuffer(num * 100)); + + { + buf->resetBuffer(); + TBinaryProtocolT prot(buf); + double elapsed = 0.0; + Timer timer; + + listDoublePerf.write(&prot); + elapsed = timer.frame(); + cout << "Double write big endian: " << num / (1000 * elapsed) << " kHz" << endl; + } + + buf->getBuffer(&data, &datasize); + + { + std::shared_ptr buf2(new TMemoryBuffer(data, datasize)); + TBinaryProtocolT prot(buf2); + ListDoublePerf listDoublePerf2; + double elapsed = 0.0; + Timer timer; + + listDoublePerf2.read(&prot); + elapsed = timer.frame(); + cout << " Double read big endian: " << num / (1000 * elapsed) << " kHz" << endl; + } + + { + buf->resetBuffer(); + TBinaryProtocolT prot(buf); + double elapsed = 0.0; + Timer timer; + + listDoublePerf.write(&prot); + elapsed = timer.frame(); + cout << "Double write little endian: " << num / (1000 * elapsed) << " kHz" << endl; + } + + { + ListDoublePerf listDoublePerf2; + std::shared_ptr buf2(new TMemoryBuffer(data, datasize)); + TBinaryProtocolT prot(buf2); + double elapsed = 0.0; + Timer timer; + + listDoublePerf2.read(&prot); + elapsed = timer.frame(); + cout << " Double read little endian: " << num / (1000 * elapsed) << " kHz" << endl; + } + + { + buf->resetBuffer(); + TBinaryProtocolT prot(buf); + double elapsed = 0.0; + Timer timer; + + listDoublePerf.write(&prot); + elapsed = timer.frame(); + cout << "Double write big endian: " << num / (1000 * elapsed) << " kHz" << endl; + } + + { + std::shared_ptr buf2(new TMemoryBuffer(data, datasize)); + TBinaryProtocolT prot(buf2); + ListDoublePerf listDoublePerf2; + double elapsed = 0.0; + Timer timer; + + listDoublePerf2.read(&prot); + elapsed = timer.frame(); + cout << " Double read big endian: " << num / (1000 * elapsed) << " kHz" << endl; + } + + + return 0; +} diff --git a/src/jaegertracing/thrift/lib/cpp/test/CMakeLists.txt b/src/jaegertracing/thrift/lib/cpp/test/CMakeLists.txt new file mode 100644 index 000000000..ef08dbce2 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/test/CMakeLists.txt @@ -0,0 +1,390 @@ +# +# 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. +# + +# Unit tests still require boost +include(BoostMacros) +REQUIRE_BOOST_HEADERS() +set(BOOST_COMPONENTS chrono date_time filesystem random thread unit_test_framework) +REQUIRE_BOOST_LIBRARIES(BOOST_COMPONENTS) + +include(ThriftMacros) + +# Make sure gen-cpp files can be included +include_directories("${CMAKE_CURRENT_BINARY_DIR}") + +# Create the thrift C++ test library +set(testgencpp_SOURCES + gen-cpp/AnnotationTest_types.cpp + gen-cpp/AnnotationTest_types.h + gen-cpp/DebugProtoTest_types.cpp + gen-cpp/DebugProtoTest_types.h + gen-cpp/EnumTest_types.cpp + gen-cpp/EnumTest_types.h + gen-cpp/OptionalRequiredTest_types.cpp + gen-cpp/OptionalRequiredTest_types.h + gen-cpp/Recursive_types.cpp + gen-cpp/Recursive_types.h + gen-cpp/ThriftTest_types.cpp + gen-cpp/ThriftTest_types.h + gen-cpp/OneWayTest_types.cpp + gen-cpp/OneWayTest_types.h + gen-cpp/OneWayService.cpp + gen-cpp/OneWayService.h + gen-cpp/TypedefTest_types.cpp + gen-cpp/TypedefTest_types.h + ThriftTest_extras.cpp + DebugProtoTest_extras.cpp +) + +add_library(testgencpp STATIC ${testgencpp_SOURCES}) + +set(testgencpp_cob_SOURCES + gen-cpp/ChildService.cpp + gen-cpp/ChildService.h + gen-cpp/EmptyService.cpp + gen-cpp/EmptyService.h + gen-cpp/ParentService.cpp + gen-cpp/ParentService.h + gen-cpp/proc_types.cpp + gen-cpp/proc_types.h +) +add_library(testgencpp_cob STATIC ${testgencpp_cob_SOURCES}) + +add_executable(Benchmark Benchmark.cpp) +target_link_libraries(Benchmark testgencpp) +LINK_AGAINST_THRIFT_LIBRARY(Benchmark thrift) +add_test(NAME Benchmark COMMAND Benchmark) +target_link_libraries(Benchmark testgencpp) + +set(UnitTest_SOURCES + UnitTestMain.cpp + OneWayHTTPTest.cpp + TMemoryBufferTest.cpp + TBufferBaseTest.cpp + Base64Test.cpp + ToStringTest.cpp + TypedefTest.cpp + TServerSocketTest.cpp + TServerTransportTest.cpp +) + +add_executable(UnitTests ${UnitTest_SOURCES}) +target_link_libraries(UnitTests testgencpp ${Boost_LIBRARIES}) +LINK_AGAINST_THRIFT_LIBRARY(UnitTests thrift) +add_test(NAME UnitTests COMMAND UnitTests) +if ( MSVC ) + # Disable C4503: decorated name length exceeded, name was truncated + # 'insanity' results in very long decorated names + set_property( TARGET UnitTests APPEND_STRING PROPERTY COMPILE_FLAGS /wd4503 ) +endif ( MSVC ) + + +set( TInterruptTest_SOURCES + TSocketInterruptTest.cpp + TSSLSocketInterruptTest.cpp +) +if (WIN32) + list(APPEND TInterruptTest_SOURCES + TPipeInterruptTest.cpp + ) +endif() +add_executable(TInterruptTest ${TInterruptTest_SOURCES}) +target_link_libraries(TInterruptTest + testgencpp + ${Boost_LIBRARIES} +) +LINK_AGAINST_THRIFT_LIBRARY(TInterruptTest thrift) +if (NOT MSVC AND NOT ${CMAKE_SYSTEM_NAME} MATCHES "Darwin" AND NOT MINGW) +target_link_libraries(TInterruptTest -lrt) +endif () +add_test(NAME TInterruptTest COMMAND TInterruptTest -- "${CMAKE_CURRENT_SOURCE_DIR}/../../../test/keys") + +add_executable(TServerIntegrationTest TServerIntegrationTest.cpp) +target_link_libraries(TServerIntegrationTest + testgencpp_cob + ${Boost_LIBRARIES} +) +LINK_AGAINST_THRIFT_LIBRARY(TServerIntegrationTest thrift) +if (NOT MSVC AND NOT ${CMAKE_SYSTEM_NAME} MATCHES "Darwin" AND NOT MINGW) +target_link_libraries(TServerIntegrationTest -lrt) +endif () +add_test(NAME TServerIntegrationTest COMMAND TServerIntegrationTest) + +if(WITH_ZLIB) +include_directories(SYSTEM "${ZLIB_INCLUDE_DIRS}") +add_executable(TransportTest TransportTest.cpp) +target_link_libraries(TransportTest + testgencpp + ${Boost_LIBRARIES} + ${ZLIB_LIBRARIES} +) +LINK_AGAINST_THRIFT_LIBRARY(TransportTest thrift) +LINK_AGAINST_THRIFT_LIBRARY(TransportTest thriftz) +add_test(NAME TransportTest COMMAND TransportTest) + +add_executable(ZlibTest ZlibTest.cpp) +target_link_libraries(ZlibTest + testgencpp + ${Boost_LIBRARIES} + ${ZLIB_LIBRARIES} +) +LINK_AGAINST_THRIFT_LIBRARY(ZlibTest thrift) +LINK_AGAINST_THRIFT_LIBRARY(ZlibTest thriftz) +add_test(NAME ZlibTest COMMAND ZlibTest) +endif(WITH_ZLIB) + +add_executable(AnnotationTest AnnotationTest.cpp) +target_link_libraries(AnnotationTest + testgencpp + ${Boost_LIBRARIES} +) +LINK_AGAINST_THRIFT_LIBRARY(AnnotationTest thrift) +add_test(NAME AnnotationTest COMMAND AnnotationTest) + +add_executable(EnumTest EnumTest.cpp) +target_link_libraries(EnumTest + testgencpp + ${Boost_LIBRARIES} +) +LINK_AGAINST_THRIFT_LIBRARY(EnumTest thrift) +add_test(NAME EnumTest COMMAND EnumTest) + +if(HAVE_GETOPT_H) +add_executable(TFileTransportTest TFileTransportTest.cpp) +target_link_libraries(TFileTransportTest + testgencpp + ${Boost_LIBRARIES} +) +LINK_AGAINST_THRIFT_LIBRARY(TFileTransportTest thrift) +add_test(NAME TFileTransportTest COMMAND TFileTransportTest) +endif() + +add_executable(TFDTransportTest TFDTransportTest.cpp) +target_link_libraries(TFDTransportTest + ${Boost_LIBRARIES} +) +LINK_AGAINST_THRIFT_LIBRARY(TFDTransportTest thrift) +add_test(NAME TFDTransportTest COMMAND TFDTransportTest) + +add_executable(TPipedTransportTest TPipedTransportTest.cpp) +target_link_libraries(TPipedTransportTest + ${Boost_LIBRARIES} +) +LINK_AGAINST_THRIFT_LIBRARY(TPipedTransportTest thrift) +add_test(NAME TPipedTransportTest COMMAND TPipedTransportTest) + +set(AllProtocolsTest_SOURCES + AllProtocolTests.cpp + AllProtocolTests.tcc + GenericHelpers + ) + +add_executable(AllProtocolsTest ${AllProtocolsTest_SOURCES}) +target_link_libraries(AllProtocolsTest + testgencpp + ${Boost_LIBRARIES} +) +LINK_AGAINST_THRIFT_LIBRARY(AllProtocolsTest thrift) +add_test(NAME AllProtocolsTest COMMAND AllProtocolsTest) + +# The debug run-time in Windows asserts on isprint() with negative inputs +if (NOT MSVC OR (MSVC AND CMAKE_BUILD_TYPE EQUAL "DEBUG")) +add_executable(DebugProtoTest DebugProtoTest.cpp) +target_link_libraries(DebugProtoTest + testgencpp + ${Boost_LIBRARIES} +) +LINK_AGAINST_THRIFT_LIBRARY(DebugProtoTest thrift) +add_test(NAME DebugProtoTest COMMAND DebugProtoTest) +endif() + +add_executable(JSONProtoTest JSONProtoTest.cpp) +target_link_libraries(JSONProtoTest + testgencpp + ${Boost_LIBRARIES} +) +LINK_AGAINST_THRIFT_LIBRARY(JSONProtoTest thrift) +add_test(NAME JSONProtoTest COMMAND JSONProtoTest) + +add_executable(OptionalRequiredTest OptionalRequiredTest.cpp) +target_link_libraries(OptionalRequiredTest + testgencpp + ${Boost_LIBRARIES} +) +LINK_AGAINST_THRIFT_LIBRARY(OptionalRequiredTest thrift) +add_test(NAME OptionalRequiredTest COMMAND OptionalRequiredTest) + +add_executable(RecursiveTest RecursiveTest.cpp) +target_link_libraries(RecursiveTest + testgencpp + ${Boost_LIBRARIES} +) +LINK_AGAINST_THRIFT_LIBRARY(RecursiveTest thrift) +add_test(NAME RecursiveTest COMMAND RecursiveTest) + +add_executable(SpecializationTest SpecializationTest.cpp) +target_link_libraries(SpecializationTest + testgencpp + ${Boost_LIBRARIES} +) +LINK_AGAINST_THRIFT_LIBRARY(SpecializationTest thrift) +add_test(NAME SpecializationTest COMMAND SpecializationTest) + +set(concurrency_test_SOURCES + concurrency/Tests.cpp + concurrency/ThreadFactoryTests.h + concurrency/ThreadManagerTests.h + concurrency/TimerManagerTests.h +) +add_executable(concurrency_test ${concurrency_test_SOURCES}) +LINK_AGAINST_THRIFT_LIBRARY(concurrency_test thrift) +add_test(NAME concurrency_test COMMAND concurrency_test) + +set(link_test_SOURCES + link/LinkTest.cpp + gen-cpp/ParentService.h + link/TemplatedService1.cpp + link/TemplatedService2.cpp +) + +add_executable(link_test ${link_test_SOURCES}) +target_link_libraries(link_test testgencpp_cob) +LINK_AGAINST_THRIFT_LIBRARY(link_test thrift) +target_link_libraries(link_test testgencpp) +add_test(NAME link_test COMMAND link_test) + +if(WITH_LIBEVENT) +set(processor_test_SOURCES + processor/ProcessorTest.cpp + processor/EventLog.cpp + processor/ServerThread.cpp + processor/EventLog.h + processor/Handlers.h + processor/ServerThread.h +) +add_executable(processor_test ${processor_test_SOURCES}) +target_link_libraries(processor_test + testgencpp_cob + ${Boost_LIBRARIES} +) +LINK_AGAINST_THRIFT_LIBRARY(processor_test thrift) +LINK_AGAINST_THRIFT_LIBRARY(processor_test thriftnb) +add_test(NAME processor_test COMMAND processor_test) + +set(TNonblockingServerTest_SOURCES TNonblockingServerTest.cpp) +add_executable(TNonblockingServerTest ${TNonblockingServerTest_SOURCES}) +include_directories(${LIBEVENT_INCLUDE_DIRS}) +target_link_libraries(TNonblockingServerTest + testgencpp_cob + ${LIBEVENT_LIBRARIES} + ${Boost_LIBRARIES} +) +LINK_AGAINST_THRIFT_LIBRARY(TNonblockingServerTest thrift) +LINK_AGAINST_THRIFT_LIBRARY(TNonblockingServerTest thriftnb) +add_test(NAME TNonblockingServerTest COMMAND TNonblockingServerTest) + +if(OPENSSL_FOUND AND WITH_OPENSSL) + set(TNonblockingSSLServerTest_SOURCES TNonblockingSSLServerTest.cpp) + add_executable(TNonblockingSSLServerTest ${TNonblockingSSLServerTest_SOURCES}) + include_directories(${LIBEVENT_INCLUDE_DIRS}) + target_link_libraries(TNonblockingSSLServerTest + testgencpp_cob + ${LIBEVENT_LIBRARIES} + ${Boost_LIBRARIES} + ) + LINK_AGAINST_THRIFT_LIBRARY(TNonblockingSSLServerTest thrift) + LINK_AGAINST_THRIFT_LIBRARY(TNonblockingSSLServerTest thriftnb) + add_test(NAME TNonblockingSSLServerTest COMMAND TNonblockingSSLServerTest -- "${CMAKE_CURRENT_SOURCE_DIR}/../../../test/keys") +endif(OPENSSL_FOUND AND WITH_OPENSSL) +endif(WITH_LIBEVENT) + +if(OPENSSL_FOUND AND WITH_OPENSSL) +add_executable(OpenSSLManualInitTest OpenSSLManualInitTest.cpp) +target_link_libraries(OpenSSLManualInitTest + ${OPENSSL_LIBRARIES} + ${Boost_LIBRARIES} +) +LINK_AGAINST_THRIFT_LIBRARY(OpenSSLManualInitTest thrift) +add_test(NAME OpenSSLManualInitTest COMMAND OpenSSLManualInitTest) + +add_executable(SecurityTest SecurityTest.cpp) +target_link_libraries(SecurityTest + testgencpp + ${Boost_LIBRARIES} +) +LINK_AGAINST_THRIFT_LIBRARY(SecurityTest thrift) +if (NOT MSVC AND NOT ${CMAKE_SYSTEM_NAME} MATCHES "Darwin" AND NOT MINGW) +target_link_libraries(SecurityTest -lrt) +endif () +add_test(NAME SecurityTest COMMAND SecurityTest -- "${CMAKE_CURRENT_SOURCE_DIR}/../../../test/keys") + +endif() + +if(WITH_QT5) +add_subdirectory(qt) +endif() + +# +# Common thrift code generation rules +# + +add_custom_command(OUTPUT gen-cpp/AnnotationTest_constants.cpp + gen-cpp/AnnotationTest_constants.h + gen-cpp/AnnotationTest_types.cpp + gen-cpp/AnnotationTest_types.h + gen-cpp/foo_service.cpp + gen-cpp/foo_service.h + COMMAND ${THRIFT_COMPILER} --gen cpp ${PROJECT_SOURCE_DIR}/test/AnnotationTest.thrift +) + +add_custom_command(OUTPUT gen-cpp/DebugProtoTest_types.cpp gen-cpp/DebugProtoTest_types.h gen-cpp/EmptyService.cpp gen-cpp/EmptyService.h + COMMAND ${THRIFT_COMPILER} --gen cpp ${PROJECT_SOURCE_DIR}/test/DebugProtoTest.thrift +) + +add_custom_command(OUTPUT gen-cpp/EnumTest_types.cpp gen-cpp/EnumTest_types.h + COMMAND ${THRIFT_COMPILER} --gen cpp ${PROJECT_SOURCE_DIR}/test/EnumTest.thrift +) + +add_custom_command(OUTPUT gen-cpp/TypedefTest_types.cpp gen-cpp/TypedefTest_types.h + COMMAND ${THRIFT_COMPILER} --gen cpp ${PROJECT_SOURCE_DIR}/test/TypedefTest.thrift +) + +add_custom_command(OUTPUT gen-cpp/OptionalRequiredTest_types.cpp gen-cpp/OptionalRequiredTest_types.h + COMMAND ${THRIFT_COMPILER} --gen cpp ${PROJECT_SOURCE_DIR}/test/OptionalRequiredTest.thrift +) + +add_custom_command(OUTPUT gen-cpp/Recursive_types.cpp gen-cpp/Recursive_types.h + COMMAND ${THRIFT_COMPILER} --gen cpp ${PROJECT_SOURCE_DIR}/test/Recursive.thrift +) + +add_custom_command(OUTPUT gen-cpp/Service.cpp gen-cpp/StressTest_types.cpp + COMMAND ${THRIFT_COMPILER} --gen cpp ${PROJECT_SOURCE_DIR}/test/StressTest.thrift +) + +add_custom_command(OUTPUT gen-cpp/SecondService.cpp gen-cpp/ThriftTest_constants.cpp gen-cpp/ThriftTest.cpp gen-cpp/ThriftTest_types.cpp gen-cpp/ThriftTest_types.h + COMMAND ${THRIFT_COMPILER} --gen cpp ${PROJECT_SOURCE_DIR}/test/ThriftTest.thrift +) + +add_custom_command(OUTPUT gen-cpp/OneWayService.cpp gen-cpp/OneWayTest_constants.cpp gen-cpp/OneWayTest_types.h gen-cpp/OneWayService.h gen-cpp/OneWayTest_constants.h gen-cpp/OneWayTest_types.cpp + COMMAND ${THRIFT_COMPILER} --gen cpp ${CMAKE_CURRENT_SOURCE_DIR}/OneWayTest.thrift +) + +add_custom_command(OUTPUT gen-cpp/ChildService.cpp gen-cpp/ChildService.h gen-cpp/ParentService.cpp gen-cpp/ParentService.h gen-cpp/proc_types.cpp gen-cpp/proc_types.h + COMMAND ${THRIFT_COMPILER} --gen cpp:templates,cob_style ${CMAKE_CURRENT_SOURCE_DIR}/processor/proc.thrift +) diff --git a/src/jaegertracing/thrift/lib/cpp/test/DebugProtoTest.cpp b/src/jaegertracing/thrift/lib/cpp/test/DebugProtoTest.cpp new file mode 100644 index 000000000..060f3547d --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/test/DebugProtoTest.cpp @@ -0,0 +1,310 @@ +/* + * 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. + */ + +#define _USE_MATH_DEFINES +#include +#include "gen-cpp/DebugProtoTest_types.h" +#include +#include + +#define BOOST_TEST_MODULE DebugProtoTest +#include + +using namespace thrift::test::debug; + +static ::std::shared_ptr ooe; + +void testCaseSetup_1() { + ooe.reset(new OneOfEach); + ooe->im_true = true; + ooe->im_false = false; + ooe->a_bite = 0x7f; + ooe->integer16 = 27000; + ooe->integer32 = 1 << 24; + ooe->integer64 = (uint64_t)6000 * 1000 * 1000; + ooe->double_precision = M_PI; + ooe->some_characters = "Debug THIS!"; + ooe->zomg_unicode = "\xd7\n\a\t"; +} + +BOOST_AUTO_TEST_CASE(test_debug_proto_1) { + testCaseSetup_1(); + + const std::string expected_result( + "OneOfEach {\n" + " 01: im_true (bool) = true,\n" + " 02: im_false (bool) = false,\n" + " 03: a_bite (byte) = 0x7f,\n" + " 04: integer16 (i16) = 27000,\n" + " 05: integer32 (i32) = 16777216,\n" + " 06: integer64 (i64) = 6000000000,\n" + " 07: double_precision (double) = 3.1415926535897931,\n" + " 08: some_characters (string) = \"Debug THIS!\",\n" + " 09: zomg_unicode (string) = \"\\xd7\\n\\a\\t\",\n" + " 10: what_who (bool) = false,\n" + " 11: base64 (string) = \"\",\n" + " 12: byte_list (list) = list[3] {\n" + " [0] = 0x01,\n" + " [1] = 0x02,\n" + " [2] = 0x03,\n" + " },\n" + " 13: i16_list (list) = list[3] {\n" + " [0] = 1,\n" + " [1] = 2,\n" + " [2] = 3,\n" + " },\n" + " 14: i64_list (list) = list[3] {\n" + " [0] = 1,\n" + " [1] = 2,\n" + " [2] = 3,\n" + " },\n" + "}"); + const std::string result(apache::thrift::ThriftDebugString(*ooe)); + + BOOST_CHECK_MESSAGE(!expected_result.compare(result), + "Expected:\n" << expected_result << "\nGotten:\n" << result); +} + +static ::std::shared_ptr n; + +void testCaseSetup_2() { + testCaseSetup_1(); + + n.reset(new Nesting); + n->my_ooe = *ooe; + n->my_ooe.integer16 = 16; + n->my_ooe.integer32 = 32; + n->my_ooe.integer64 = 64; + n->my_ooe.double_precision = (std::sqrt(5.0) + 1) / 2; + n->my_ooe.some_characters = ":R (me going \"rrrr\")"; + n->my_ooe.zomg_unicode = "\xd3\x80\xe2\x85\xae\xce\x9d\x20\xd0\x9d\xce" + "\xbf\xe2\x85\xbf\xd0\xbe\xc9\xa1\xd0\xb3\xd0" + "\xb0\xcf\x81\xe2\x84\x8e\x20\xce\x91\x74\x74" + "\xce\xb1\xe2\x85\xbd\xce\xba\xc7\x83\xe2\x80" + "\xbc"; + n->my_bonk.type = 31337; + n->my_bonk.message = "I am a bonk... xor!"; +} + +BOOST_AUTO_TEST_CASE(test_debug_proto_2) { + testCaseSetup_2(); + + const std::string expected_result( + "Nesting {\n" + " 01: my_bonk (struct) = Bonk {\n" + " 01: type (i32) = 31337,\n" + " 02: message (string) = \"I am a bonk... xor!\",\n" + " },\n" + " 02: my_ooe (struct) = OneOfEach {\n" + " 01: im_true (bool) = true,\n" + " 02: im_false (bool) = false,\n" + " 03: a_bite (byte) = 0x7f,\n" + " 04: integer16 (i16) = 16,\n" + " 05: integer32 (i32) = 32,\n" + " 06: integer64 (i64) = 64,\n" + " 07: double_precision (double) = 1.6180339887498949,\n" + " 08: some_characters (string) = \":R (me going \\\"rrrr\\\")\",\n" + " 09: zomg_unicode (string) = \"\\xd3\\x80\\xe2\\x85\\xae\\xce\\x9d \\xd" + "0\\x9d\\xce\\xbf\\xe2\\x85\\xbf\\xd0\\xbe\\xc9\\xa1\\xd0\\xb3\\xd0\\xb0" + "\\xcf\\x81\\xe2\\x84\\x8e \\xce\\x91tt\\xce\\xb1\\xe2\\x85\\xbd\\xce\\xb" + "a\\xc7\\x83\\xe2\\x80\\xbc\",\n" + " 10: what_who (bool) = false,\n" + " 11: base64 (string) = \"\",\n" + " 12: byte_list (list) = list[3] {\n" + " [0] = 0x01,\n" + " [1] = 0x02,\n" + " [2] = 0x03,\n" + " },\n" + " 13: i16_list (list) = list[3] {\n" + " [0] = 1,\n" + " [1] = 2,\n" + " [2] = 3,\n" + " },\n" + " 14: i64_list (list) = list[3] {\n" + " [0] = 1,\n" + " [1] = 2,\n" + " [2] = 3,\n" + " },\n" + " },\n" + "}"); + const std::string result(apache::thrift::ThriftDebugString(*n)); + + BOOST_CHECK_MESSAGE(!expected_result.compare(result), + "Expected:\n" << expected_result << "\nGotten:\n" << result); +} + +static ::std::shared_ptr hm; + +void testCaseSetup_3() { + testCaseSetup_2(); + + hm.reset(new HolyMoley); + + hm->big.push_back(*ooe); + hm->big.push_back(n->my_ooe); + hm->big[0].a_bite = 0x22; + hm->big[1].a_bite = 0x33; + + std::vector stage1; + stage1.push_back("and a one"); + stage1.push_back("and a two"); + hm->contain.insert(stage1); + stage1.clear(); + stage1.push_back("then a one, two"); + stage1.push_back("three!"); + stage1.push_back("FOUR!!"); + hm->contain.insert(stage1); + stage1.clear(); + hm->contain.insert(stage1); + + std::vector stage2; + hm->bonks["nothing"] = stage2; + stage2.resize(stage2.size() + 1); + stage2.back().type = 1; + stage2.back().message = "Wait."; + stage2.resize(stage2.size() + 1); + stage2.back().type = 2; + stage2.back().message = "What?"; + hm->bonks["something"] = stage2; + stage2.clear(); + stage2.resize(stage2.size() + 1); + stage2.back().type = 3; + stage2.back().message = "quoth"; + stage2.resize(stage2.size() + 1); + stage2.back().type = 4; + stage2.back().message = "the raven"; + stage2.resize(stage2.size() + 1); + stage2.back().type = 5; + stage2.back().message = "nevermore"; + hm->bonks["poe"] = stage2; +} + +BOOST_AUTO_TEST_CASE(test_debug_proto_3) { + testCaseSetup_3(); + + const std::string expected_result( + "HolyMoley {\n" + " 01: big (list) = list[2] {\n" + " [0] = OneOfEach {\n" + " 01: im_true (bool) = true,\n" + " 02: im_false (bool) = false,\n" + " 03: a_bite (byte) = 0x22,\n" + " 04: integer16 (i16) = 27000,\n" + " 05: integer32 (i32) = 16777216,\n" + " 06: integer64 (i64) = 6000000000,\n" + " 07: double_precision (double) = 3.1415926535897931,\n" + " 08: some_characters (string) = \"Debug THIS!\",\n" + " 09: zomg_unicode (string) = \"\\xd7\\n\\a\\t\",\n" + " 10: what_who (bool) = false,\n" + " 11: base64 (string) = \"\",\n" + " 12: byte_list (list) = list[3] {\n" + " [0] = 0x01,\n" + " [1] = 0x02,\n" + " [2] = 0x03,\n" + " },\n" + " 13: i16_list (list) = list[3] {\n" + " [0] = 1,\n" + " [1] = 2,\n" + " [2] = 3,\n" + " },\n" + " 14: i64_list (list) = list[3] {\n" + " [0] = 1,\n" + " [1] = 2,\n" + " [2] = 3,\n" + " },\n" + " },\n" + " [1] = OneOfEach {\n" + " 01: im_true (bool) = true,\n" + " 02: im_false (bool) = false,\n" + " 03: a_bite (byte) = 0x33,\n" + " 04: integer16 (i16) = 16,\n" + " 05: integer32 (i32) = 32,\n" + " 06: integer64 (i64) = 64,\n" + " 07: double_precision (double) = 1.6180339887498949,\n" + " 08: some_characters (string) = \":R (me going \\\"rrrr\\\")\",\n" + " 09: zomg_unicode (string) = \"\\xd3\\x80\\xe2\\x85\\xae\\xce\\x9d \\" + "xd0\\x9d\\xce\\xbf\\xe2\\x85\\xbf\\xd0\\xbe\\xc9\\xa1\\xd0\\xb3\\xd0\\xb" + "0\\xcf\\x81\\xe2\\x84\\x8e \\xce\\x91tt\\xce\\xb1\\xe2\\x85\\xbd\\xce\\x" + "ba\\xc7\\x83\\xe2\\x80\\xbc\",\n" + " 10: what_who (bool) = false,\n" + " 11: base64 (string) = \"\",\n" + " 12: byte_list (list) = list[3] {\n" + " [0] = 0x01,\n" + " [1] = 0x02,\n" + " [2] = 0x03,\n" + " },\n" + " 13: i16_list (list) = list[3] {\n" + " [0] = 1,\n" + " [1] = 2,\n" + " [2] = 3,\n" + " },\n" + " 14: i64_list (list) = list[3] {\n" + " [0] = 1,\n" + " [1] = 2,\n" + " [2] = 3,\n" + " },\n" + " },\n" + " },\n" + " 02: contain (set) = set[3] {\n" + " list[0] {\n" + " },\n" + " list[2] {\n" + " [0] = \"and a one\",\n" + " [1] = \"and a two\",\n" + " },\n" + " list[3] {\n" + " [0] = \"then a one, two\",\n" + " [1] = \"three!\",\n" + " [2] = \"FOUR!!\",\n" + " },\n" + " },\n" + " 03: bonks (map) = map[3] {\n" + " \"nothing\" -> list[0] {\n" + " },\n" + " \"poe\" -> list[3] {\n" + " [0] = Bonk {\n" + " 01: type (i32) = 3,\n" + " 02: message (string) = \"quoth\",\n" + " },\n" + " [1] = Bonk {\n" + " 01: type (i32) = 4,\n" + " 02: message (string) = \"the raven\",\n" + " },\n" + " [2] = Bonk {\n" + " 01: type (i32) = 5,\n" + " 02: message (string) = \"nevermore\",\n" + " },\n" + " },\n" + " \"something\" -> list[2] {\n" + " [0] = Bonk {\n" + " 01: type (i32) = 1,\n" + " 02: message (string) = \"Wait.\",\n" + " },\n" + " [1] = Bonk {\n" + " 01: type (i32) = 2,\n" + " 02: message (string) = \"What?\",\n" + " },\n" + " },\n" + " },\n" + "}"); + const std::string result(apache::thrift::ThriftDebugString(*hm)); + + BOOST_CHECK_MESSAGE(!expected_result.compare(result), + "Expected:\n" << expected_result << "\nGotten:\n" << result); +} diff --git a/src/jaegertracing/thrift/lib/cpp/test/DebugProtoTest_extras.cpp b/src/jaegertracing/thrift/lib/cpp/test/DebugProtoTest_extras.cpp new file mode 100644 index 000000000..5c4fd35e7 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/test/DebugProtoTest_extras.cpp @@ -0,0 +1,35 @@ +/* + * 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. + */ + +// Extra functions required for DebugProtoTest_types to work + +#include "gen-cpp/DebugProtoTest_types.h" + +namespace thrift { +namespace test { +namespace debug { + +bool Empty::operator<(Empty const& other) const { + (void)other; + // It is empty, so all are equal. + return false; +} +} +} +} diff --git a/src/jaegertracing/thrift/lib/cpp/test/EnumTest.cpp b/src/jaegertracing/thrift/lib/cpp/test/EnumTest.cpp new file mode 100644 index 000000000..388abb7e9 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/test/EnumTest.cpp @@ -0,0 +1,108 @@ +/* + * 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. + */ +#define BOOST_TEST_MODULE EnumTest +#include +#include "gen-cpp/EnumTest_types.h" + +std::ostream& operator <<(std::ostream& os, const MyEnumWithCustomOstream::type& val) +{ + os << "{" << (int)val << ":CUSTOM!" << "}"; + return os; +} + +std::string to_string(const MyEnumWithCustomOstream::type& val) +{ + std::ostringstream os; + os << val; + return os.str(); +} + +BOOST_AUTO_TEST_SUITE(EnumTest) + +BOOST_AUTO_TEST_CASE(test_enum_value) { + // Check that all the enum values match what we expect + BOOST_CHECK_EQUAL(MyEnum1::ME1_0, 0); + BOOST_CHECK_EQUAL(MyEnum1::ME1_1, 1); + BOOST_CHECK_EQUAL(MyEnum1::ME1_2, 2); + BOOST_CHECK_EQUAL(MyEnum1::ME1_3, 3); + BOOST_CHECK_EQUAL(MyEnum1::ME1_5, 5); + BOOST_CHECK_EQUAL(MyEnum1::ME1_6, 6); + + BOOST_CHECK_EQUAL(MyEnum2::ME2_0, 0); + BOOST_CHECK_EQUAL(MyEnum2::ME2_1, 1); + BOOST_CHECK_EQUAL(MyEnum2::ME2_2, 2); + + BOOST_CHECK_EQUAL(MyEnum3::ME3_0, 0); + BOOST_CHECK_EQUAL(MyEnum3::ME3_1, 1); + BOOST_CHECK_EQUAL(MyEnum3::ME3_N2, -2); + BOOST_CHECK_EQUAL(MyEnum3::ME3_N1, -1); + BOOST_CHECK_EQUAL(MyEnum3::ME3_D0, 0); + BOOST_CHECK_EQUAL(MyEnum3::ME3_D1, 1); + BOOST_CHECK_EQUAL(MyEnum3::ME3_9, 9); + BOOST_CHECK_EQUAL(MyEnum3::ME3_10, 10); + + BOOST_CHECK_EQUAL(MyEnum4::ME4_A, 0x7ffffffd); + BOOST_CHECK_EQUAL(MyEnum4::ME4_B, 0x7ffffffe); + BOOST_CHECK_EQUAL(MyEnum4::ME4_C, 0x7fffffff); + + BOOST_CHECK_EQUAL(MyEnum5::e1, 0); + BOOST_CHECK_EQUAL(MyEnum5::e2, 42); +} + +template +std::string EnumToString(_T e) +{ + std::stringstream ss; + ss << e; + return ss.str(); +} + +BOOST_AUTO_TEST_CASE(test_enum_ostream) +{ + BOOST_CHECK_EQUAL(EnumToString(MyEnum1::ME1_0), "ME1_0"); + BOOST_CHECK_EQUAL(EnumToString(MyEnum5::e2), "e2"); + BOOST_CHECK_EQUAL(EnumToString(MyEnum3::ME3_N1), "ME3_N1"); + BOOST_CHECK_EQUAL(EnumToString(MyEnumWithCustomOstream::CustoM2), "{2:CUSTOM!}"); + + // some invalid or unknown value + auto uut = static_cast(44); + BOOST_CHECK_EQUAL(EnumToString(uut), "44"); +} + +BOOST_AUTO_TEST_CASE(test_enum_to_string) +{ + BOOST_CHECK_EQUAL(::to_string(MyEnum1::ME1_0), "ME1_0"); + BOOST_CHECK_EQUAL(::to_string(MyEnum5::e2), "e2"); + BOOST_CHECK_EQUAL(::to_string(MyEnum3::ME3_N1), "ME3_N1"); + BOOST_CHECK_EQUAL(::to_string(MyEnumWithCustomOstream::CustoM2), "{2:CUSTOM!}"); + + // some invalid or unknown value + auto uut = static_cast(44); + BOOST_CHECK_EQUAL(::to_string(uut), "44"); +} + +BOOST_AUTO_TEST_CASE(test_enum_constant) +{ + MyStruct ms; + BOOST_CHECK_EQUAL(ms.me2_2, 2); + BOOST_CHECK_EQUAL(ms.me3_n2, -2); + BOOST_CHECK_EQUAL(ms.me3_d1, 1); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/jaegertracing/thrift/lib/cpp/test/GenericHelpers.h b/src/jaegertracing/thrift/lib/cpp/test/GenericHelpers.h new file mode 100644 index 000000000..bcef9f242 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/test/GenericHelpers.h @@ -0,0 +1,107 @@ +/* + * 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. + */ + +#ifndef _THRIFT_TEST_GENERICHELPERS_H_ +#define _THRIFT_TEST_GENERICHELPERS_H_ 1 + +#include +#include +#include + +/* ClassName Helper for cleaner exceptions */ +class ClassNames { +public: + template + static const char* getName() { + return "Unknown type"; + } +}; + +template <> +const char* ClassNames::getName() { + return "byte"; +} +template <> +const char* ClassNames::getName() { + return "short"; +} +template <> +const char* ClassNames::getName() { + return "int"; +} +template <> +const char* ClassNames::getName() { + return "long"; +} +template <> +const char* ClassNames::getName() { + return "double"; +} +template <> +const char* ClassNames::getName() { + return "string"; +} + +/* Generic Protocol I/O function for tests */ +class GenericIO { +public: + /* Write functions */ + + static uint32_t write(std::shared_ptr proto, const int8_t& val) { + return proto->writeByte(val); + } + + static uint32_t write(std::shared_ptr proto, const int16_t& val) { + return proto->writeI16(val); + } + + static uint32_t write(std::shared_ptr proto, const int32_t& val) { + return proto->writeI32(val); + } + + static uint32_t write(std::shared_ptr proto, const double& val) { + return proto->writeDouble(val); + } + + static uint32_t write(std::shared_ptr proto, const int64_t& val) { + return proto->writeI64(val); + } + + static uint32_t write(std::shared_ptr proto, const std::string& val) { + return proto->writeString(val); + } + + /* Read functions */ + + static uint32_t read(std::shared_ptr proto, int8_t& val) { return proto->readByte(val); } + + static uint32_t read(std::shared_ptr proto, int16_t& val) { return proto->readI16(val); } + + static uint32_t read(std::shared_ptr proto, int32_t& val) { return proto->readI32(val); } + + static uint32_t read(std::shared_ptr proto, int64_t& val) { return proto->readI64(val); } + + static uint32_t read(std::shared_ptr proto, double& val) { return proto->readDouble(val); } + + static uint32_t read(std::shared_ptr proto, std::string& val) { + return proto->readString(val); + } +}; + +#endif diff --git a/src/jaegertracing/thrift/lib/cpp/test/JSONProtoTest.cpp b/src/jaegertracing/thrift/lib/cpp/test/JSONProtoTest.cpp new file mode 100644 index 000000000..c2ad73e71 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/test/JSONProtoTest.cpp @@ -0,0 +1,343 @@ +/* + * 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. + */ + +#define _USE_MATH_DEFINES +#include +#include +#include +#include +#include +#include +#include "gen-cpp/DebugProtoTest_types.h" + +#define BOOST_TEST_MODULE JSONProtoTest +#include + +using namespace thrift::test::debug; +using namespace apache::thrift; +using apache::thrift::transport::TMemoryBuffer; +using apache::thrift::protocol::TJSONProtocol; + +static std::shared_ptr ooe; + +void testCaseSetup_1() { + ooe.reset(new OneOfEach); + ooe->im_true = true; + ooe->im_false = false; + ooe->a_bite = 0x7f; + ooe->integer16 = 27000; + ooe->integer32 = 1 << 24; + ooe->integer64 = (uint64_t)6000 * 1000 * 1000; + ooe->double_precision = M_PI; + ooe->some_characters = "JSON THIS! \"\1"; + ooe->zomg_unicode = "\xd7\n\a\t"; + ooe->base64 = "\1\2\3\255"; +} + +BOOST_AUTO_TEST_CASE(test_json_proto_1) { + testCaseSetup_1(); + + const std::string expected_result( + "{\"1\":{\"tf\":1},\"2\":{\"tf\":0},\"3\":{\"i8\":127},\"4\":{\"i16\":27000}," + "\"5\":{\"i32\":16777216},\"6\":{\"i64\":6000000000},\"7\":{\"dbl\":3.1415926" + "535897931},\"8\":{\"str\":\"JSON THIS! \\\"\\u0001\"},\"9\":{\"str\":\"\xd7\\" + "n\\u0007\\t\"},\"10\":{\"tf\":0},\"11\":{\"str\":\"AQIDrQ\"},\"12\":{\"lst\"" + ":[\"i8\",3,1,2,3]},\"13\":{\"lst\":[\"i16\",3,1,2,3]},\"14\":{\"lst\":[\"i64" + "\",3,1,2,3]}}"); + + const std::string result(apache::thrift::ThriftJSONString(*ooe)); + + BOOST_CHECK_MESSAGE(!expected_result.compare(result), + "Expected:\n" << expected_result << "\nGotten:\n" << result); +} + +static std::shared_ptr n; + +void testCaseSetup_2() { + testCaseSetup_1(); + + n.reset(new Nesting); + n->my_ooe = *ooe; + n->my_ooe.integer16 = 16; + n->my_ooe.integer32 = 32; + n->my_ooe.integer64 = 64; + n->my_ooe.double_precision = (std::sqrt(5.0) + 1) / 2; + n->my_ooe.some_characters = ":R (me going \"rrrr\")"; + n->my_ooe.zomg_unicode = "\xd3\x80\xe2\x85\xae\xce\x9d\x20\xd0\x9d\xce" + "\xbf\xe2\x85\xbf\xd0\xbe\xc9\xa1\xd0\xb3\xd0" + "\xb0\xcf\x81\xe2\x84\x8e\x20\xce\x91\x74\x74" + "\xce\xb1\xe2\x85\xbd\xce\xba\xc7\x83\xe2\x80" + "\xbc"; + n->my_bonk.type = 31337; + n->my_bonk.message = "I am a bonk... xor!"; +} + +BOOST_AUTO_TEST_CASE(test_json_proto_2) { + testCaseSetup_2(); + + const std::string expected_result( + "{\"1\":{\"rec\":{\"1\":{\"i32\":31337},\"2\":{\"str\":\"I am a bonk... xor" + "!\"}}},\"2\":{\"rec\":{\"1\":{\"tf\":1},\"2\":{\"tf\":0},\"3\":{\"i8\":127" + "},\"4\":{\"i16\":16},\"5\":{\"i32\":32},\"6\":{\"i64\":64},\"7\":{\"dbl\":" + "1.6180339887498949},\"8\":{\"str\":\":R (me going \\\"rrrr\\\")\"},\"9\":{" + "\"str\":\"ӀⅮΝ Нοⅿоɡгаρℎ Αttαⅽκǃ‼\"},\"10\":{\"tf\":0},\"11\":{\"str\":\"" + "AQIDrQ\"},\"12\":{\"lst\":[\"i8\",3,1,2,3]},\"13\":{\"lst\":[\"i16\",3,1,2" + ",3]},\"14\":{\"lst\":[\"i64\",3,1,2,3]}}}}" + ); + + const std::string result(apache::thrift::ThriftJSONString(*n)); + + BOOST_CHECK_MESSAGE(!expected_result.compare(result), + "Expected:\n" << expected_result << "\nGotten:\n" << result); +} + +static std::shared_ptr hm; + +void testCaseSetup_3() { + testCaseSetup_2(); + + hm.reset(new HolyMoley); + + hm->big.push_back(*ooe); + hm->big.push_back(n->my_ooe); + hm->big[0].a_bite = 0x22; + hm->big[1].a_bite = 0x33; + + std::vector stage1; + stage1.push_back("and a one"); + stage1.push_back("and a two"); + hm->contain.insert(stage1); + stage1.clear(); + stage1.push_back("then a one, two"); + stage1.push_back("three!"); + stage1.push_back("FOUR!!"); + hm->contain.insert(stage1); + stage1.clear(); + hm->contain.insert(stage1); + + std::vector stage2; + hm->bonks["nothing"] = stage2; + stage2.resize(stage2.size() + 1); + stage2.back().type = 1; + stage2.back().message = "Wait."; + stage2.resize(stage2.size() + 1); + stage2.back().type = 2; + stage2.back().message = "What?"; + hm->bonks["something"] = stage2; + stage2.clear(); + stage2.resize(stage2.size() + 1); + stage2.back().type = 3; + stage2.back().message = "quoth"; + stage2.resize(stage2.size() + 1); + stage2.back().type = 4; + stage2.back().message = "the raven"; + stage2.resize(stage2.size() + 1); + stage2.back().type = 5; + stage2.back().message = "nevermore"; + hm->bonks["poe"] = stage2; +} + +BOOST_AUTO_TEST_CASE(test_json_proto_3) { + testCaseSetup_3(); + + const std::string expected_result( + "{\"1\":{\"lst\":[\"rec\",2,{\"1\":{\"tf\":1},\"2\":{\"tf\":0},\"3\":{\"i8\":" + "34},\"4\":{\"i16\":27000},\"5\":{\"i32\":16777216},\"6\":{\"i64\":6000000000" + "},\"7\":{\"dbl\":3.1415926535897931},\"8\":{\"str\":\"JSON THIS! \\\"\\u0001" + "\"},\"9\":{\"str\":\"\xd7\\n\\u0007\\t\"},\"10\":{\"tf\":0},\"11\":{\"str\":" + "\"AQIDrQ\"},\"12\":{\"lst\":[\"i8\",3,1,2,3]},\"13\":{\"lst\":[\"i16\",3,1,2" + ",3]},\"14\":{\"lst\":[\"i64\",3,1,2,3]}},{\"1\":{\"tf\":1},\"2\":{\"tf\":0}," + "\"3\":{\"i8\":51},\"4\":{\"i16\":16},\"5\":{\"i32\":32},\"6\":{\"i64\":64}," + "\"7\":{\"dbl\":1.6180339887498949},\"8\":{\"str\":\":R (me going \\\"rrrr\\\"" + ")\"},\"9\":{\"str\":\"ӀⅮΝ Нοⅿоɡгаρℎ Αttαⅽκǃ‼\"},\"10\":{\"tf\":0},\"11\":{" + "\"str\":\"AQIDrQ\"},\"12\":{\"lst\":[\"i8\",3,1,2,3]},\"13\":{\"lst\":[\"i16" + "\",3,1,2,3]},\"14\":{\"lst\":[\"i64\",3,1,2,3]}}]},\"2\":{\"set\":[\"lst\",3" + ",[\"str\",0],[\"str\",2,\"and a one\",\"and a two\"],[\"str\",3,\"then a one" + ", two\",\"three!\",\"FOUR!!\"]]},\"3\":{\"map\":[\"str\",\"lst\",3,{\"nothin" + "g\":[\"rec\",0],\"poe\":[\"rec\",3,{\"1\":{\"i32\":3},\"2\":{\"str\":\"quoth" + "\"}},{\"1\":{\"i32\":4},\"2\":{\"str\":\"the raven\"}},{\"1\":{\"i32\":5},\"" + "2\":{\"str\":\"nevermore\"}}],\"something\":[\"rec\",2,{\"1\":{\"i32\":1},\"" + "2\":{\"str\":\"Wait.\"}},{\"1\":{\"i32\":2},\"2\":{\"str\":\"What?\"}}]}]}}" + ); + + const std::string result(apache::thrift::ThriftJSONString(*hm)); + + BOOST_CHECK_MESSAGE(!expected_result.compare(result), + "Expected:\n" << expected_result << "\nGotten:\n" << result); +} + +BOOST_AUTO_TEST_CASE(test_json_proto_4) { + testCaseSetup_1(); + + std::shared_ptr buffer(new TMemoryBuffer()); + std::shared_ptr proto(new TJSONProtocol(buffer)); + + ooe->write(proto.get()); + OneOfEach ooe2; + ooe2.read(proto.get()); + + BOOST_CHECK(*ooe == ooe2); +} + +BOOST_AUTO_TEST_CASE(test_json_proto_5) { + testCaseSetup_3(); + + std::shared_ptr buffer(new TMemoryBuffer()); + std::shared_ptr proto(new TJSONProtocol(buffer)); + + hm->write(proto.get()); + HolyMoley hm2; + hm2.read(proto.get()); + + BOOST_CHECK(*hm == hm2); + + hm2.big[0].a_bite = 0x00; + + BOOST_CHECK(*hm != hm2); +} + +BOOST_AUTO_TEST_CASE(test_json_proto_6) { + Doubles dub; + dub.nan = HUGE_VAL / HUGE_VAL; + dub.inf = HUGE_VAL; + dub.neginf = -HUGE_VAL; + dub.repeating = 10.0 / 3.0; + dub.big = 1E+305; + dub.tiny = 1E-305; + dub.zero = 0.0; + dub.negzero = -0.0; + + const std::string expected_result( + "{\"1\":{\"dbl\":\"NaN\"},\"2\":{\"dbl\":\"Infinity\"},\"3\":{\"dbl\":\"-Infi" + "nity\"},\"4\":{\"dbl\":3.3333333333333335},\"5\":{\"dbl\":9.9999999999999994e+" + "304},\"6\":{\"dbl\":1e-305},\"7\":{\"dbl\":0},\"8\":{\"dbl\":-0}}" + ); + + const std::string result(apache::thrift::ThriftJSONString(dub)); + + BOOST_CHECK_MESSAGE(!expected_result.compare(result), + "Expected:\n" << expected_result << "\nGotten:\n" << result); +} + +BOOST_AUTO_TEST_CASE(test_json_proto_7) { + std::shared_ptr buffer(new TMemoryBuffer()); + std::shared_ptr proto(new TJSONProtocol(buffer)); + + Base64 base; + base.a = 123; + base.b1 = "1"; + base.b2 = "12"; + base.b3 = "123"; + base.b4 = "1234"; + base.b5 = "12345"; + base.b6 = "123456"; + + base.write(proto.get()); + Base64 base2; + base2.read(proto.get()); + + BOOST_CHECK(base == base2); +} + +BOOST_AUTO_TEST_CASE(test_json_proto_8) { + const char* json_string = + "{\"1\":{\"tf\":1},\"2\":{\"tf\":0},\"3\":{\"i8\":127},\"4\":{\"i16\":27000}," + "\"5\":{\"i32\":16.77216},\"6\":{\"i64\":6000000000},\"7\":{\"dbl\":3.1415926" + "535897931},\"8\":{\"str\":\"JSON THIS! \\\"\\u0001\"},\"9\":{\"str\":\"\xd7\\" + "n\\u0007\\t\"},\"10\":{\"tf\":0},\"11\":{\"str\":\"AQIDrQ\"},\"12\":{\"lst\"" + ":[\"i8\",3,1,2,3]},\"13\":{\"lst\":[\"i16\",3,1,2,3]},\"14\":{\"lst\":[\"i64" + "\",3,1,2,3]}}"; + + const std::size_t bufSiz = strlen(json_string) * sizeof(char); + std::shared_ptr buffer(new TMemoryBuffer( + (uint8_t*)(json_string), static_cast(bufSiz))); + std::shared_ptr proto(new TJSONProtocol(buffer)); + + OneOfEach ooe2; + + BOOST_CHECK_THROW(ooe2.read(proto.get()), + apache::thrift::protocol::TProtocolException); +} + +static std::string toHexSequence(const std::string& str) { + std::stringstream ss; + ss << std::hex << std::setfill('0'); + for (std::size_t i = 0; i < str.size(); i++) { + ss << "\\x" << int(uint8_t(str[i])); + } + return ss.str(); +} + +BOOST_AUTO_TEST_CASE(test_json_unicode_escaped) { + const char json_string[] = + "{\"1\":{\"tf\":1},\"2\":{\"tf\":0},\"3\":{\"i8\":127},\"4\":{\"i16\":27000}," + "\"5\":{\"i32\":16},\"6\":{\"i64\":6000000000},\"7\":{\"dbl\":3.1415926" + "535897931},\"8\":{\"str\":\"JSON THIS!\"},\"9\":{\"str\":\"\\u0e01 \\ud835\\udd3e\"}," + "\"10\":{\"tf\":0},\"11\":{\"str\":\"000000\"},\"12\":{\"lst\"" + ":[\"i8\",3,1,2,3]},\"13\":{\"lst\":[\"i16\",3,1,2,3]},\"14\":{\"lst\":[\"i64" + "\",3,1,2,3]}}"; + const char* expected_zomg_unicode = "\xe0\xb8\x81 \xf0\x9d\x94\xbe"; + + std::shared_ptr buffer(new TMemoryBuffer( + (uint8_t*)(json_string), sizeof(json_string))); + std::shared_ptr proto(new TJSONProtocol(buffer)); + + OneOfEach ooe2; + ooe2.read(proto.get()); + BOOST_CHECK_MESSAGE(!ooe2.zomg_unicode.compare(expected_zomg_unicode), + "Expected:\n" << toHexSequence(expected_zomg_unicode) << "\nGotten:\n" + << toHexSequence(ooe2.zomg_unicode)); + +} + +BOOST_AUTO_TEST_CASE(test_json_unicode_escaped_missing_low_surrogate) { + const char json_string[] = + "{\"1\":{\"tf\":1},\"2\":{\"tf\":0},\"3\":{\"i8\":127},\"4\":{\"i16\":27000}," + "\"5\":{\"i32\":16},\"6\":{\"i64\":6000000000},\"7\":{\"dbl\":3.1415926" + "535897931},\"8\":{\"str\":\"JSON THIS!\"},\"9\":{\"str\":\"\\ud835\"}," + "\"10\":{\"tf\":0},\"11\":{\"str\":\"000000\"},\"12\":{\"lst\"" + ":[\"i8\",3,1,2,3]},\"13\":{\"lst\":[\"i16\",3,1,2,3]},\"14\":{\"lst\":[\"i64" + "\",3,1,2,3]}}"; + + std::shared_ptr buffer(new TMemoryBuffer( + (uint8_t*)(json_string), sizeof(json_string))); + std::shared_ptr proto(new TJSONProtocol(buffer)); + + OneOfEach ooe2; + BOOST_CHECK_THROW(ooe2.read(proto.get()), + apache::thrift::protocol::TProtocolException); +} + +BOOST_AUTO_TEST_CASE(test_json_unicode_escaped_missing_hi_surrogate) { + const char json_string[] = + "{\"1\":{\"tf\":1},\"2\":{\"tf\":0},\"3\":{\"i8\":127},\"4\":{\"i16\":27000}," + "\"5\":{\"i32\":16},\"6\":{\"i64\":6000000000},\"7\":{\"dbl\":3.1415926" + "535897931},\"8\":{\"str\":\"JSON THIS!\"},\"9\":{\"str\":\"\\udd3e\"}," + "\"10\":{\"tf\":0},\"11\":{\"str\":\"000000\"},\"12\":{\"lst\"" + ":[\"i8\",3,1,2,3]},\"13\":{\"lst\":[\"i16\",3,1,2,3]},\"14\":{\"lst\":[\"i64" + "\",3,1,2,3]}}"; + + std::shared_ptr buffer(new TMemoryBuffer( + (uint8_t*)(json_string), sizeof(json_string))); + std::shared_ptr proto(new TJSONProtocol(buffer)); + + OneOfEach ooe2; + BOOST_CHECK_THROW(ooe2.read(proto.get()), + apache::thrift::protocol::TProtocolException); +} diff --git a/src/jaegertracing/thrift/lib/cpp/test/Makefile.am b/src/jaegertracing/thrift/lib/cpp/test/Makefile.am new file mode 100755 index 000000000..2a0b9e693 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/test/Makefile.am @@ -0,0 +1,425 @@ +# +# 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. +# +AUTOMAKE_OPTIONS = subdir-objects serial-tests nostdinc + +BUILT_SOURCES = gen-cpp/AnnotationTest_types.h \ + gen-cpp/DebugProtoTest_types.h \ + gen-cpp/EnumTest_types.h \ + gen-cpp/OptionalRequiredTest_types.h \ + gen-cpp/Recursive_types.h \ + gen-cpp/ThriftTest_types.h \ + gen-cpp/TypedefTest_types.h \ + gen-cpp/ChildService.h \ + gen-cpp/EmptyService.h \ + gen-cpp/ParentService.h \ + gen-cpp/OneWayTest_types.h \ + gen-cpp/OneWayService.h \ + gen-cpp/OneWayTest_constants.h \ + gen-cpp/proc_types.h + +noinst_LTLIBRARIES = libtestgencpp.la libprocessortest.la +nodist_libtestgencpp_la_SOURCES = \ + gen-cpp/AnnotationTest_types.cpp \ + gen-cpp/AnnotationTest_types.h \ + gen-cpp/DebugProtoTest_types.cpp \ + gen-cpp/DebugProtoTest_types.h \ + gen-cpp/DoubleConstantsTest_constants.cpp \ + gen-cpp/DoubleConstantsTest_constants.h \ + gen-cpp/EnumTest_types.cpp \ + gen-cpp/EnumTest_types.h \ + gen-cpp/OptionalRequiredTest_types.cpp \ + gen-cpp/OptionalRequiredTest_types.h \ + gen-cpp/Recursive_types.cpp \ + gen-cpp/Recursive_types.h \ + gen-cpp/ThriftTest_types.cpp \ + gen-cpp/ThriftTest_types.h \ + gen-cpp/ThriftTest_constants.cpp \ + gen-cpp/ThriftTest_constants.h \ + gen-cpp/TypedefTest_types.cpp \ + gen-cpp/TypedefTest_types.h \ + gen-cpp/OneWayService.cpp \ + gen-cpp/OneWayTest_constants.cpp \ + gen-cpp/OneWayTest_types.h \ + gen-cpp/OneWayService.h \ + gen-cpp/OneWayTest_constants.h \ + gen-cpp/OneWayTest_types.cpp \ + ThriftTest_extras.cpp \ + DebugProtoTest_extras.cpp + +nodist_libprocessortest_la_SOURCES = \ + gen-cpp/ChildService.cpp \ + gen-cpp/ChildService.h \ + gen-cpp/EmptyService.cpp \ + gen-cpp/EmptyService.h \ + gen-cpp/ParentService.cpp \ + gen-cpp/ParentService.h \ + gen-cpp/proc_types.cpp \ + gen-cpp/proc_types.h + +ThriftTest_extras.o: gen-cpp/ThriftTest_types.h +DebugProtoTest_extras.o: gen-cpp/DebugProtoTest_types.h + +libtestgencpp_la_LIBADD = $(top_builddir)/lib/cpp/libthrift.la + +noinst_PROGRAMS = Benchmark \ + concurrency_test + +Benchmark_SOURCES = \ + Benchmark.cpp + +Benchmark_LDADD = libtestgencpp.la + +check_PROGRAMS = \ + UnitTests \ + TFDTransportTest \ + TPipedTransportTest \ + DebugProtoTest \ + JSONProtoTest \ + OptionalRequiredTest \ + RecursiveTest \ + SpecializationTest \ + AllProtocolsTest \ + TransportTest \ + TInterruptTest \ + TServerIntegrationTest \ + SecurityTest \ + ZlibTest \ + TFileTransportTest \ + link_test \ + OpenSSLManualInitTest \ + EnumTest \ + RenderedDoubleConstantsTest \ + AnnotationTest + +if AMX_HAVE_LIBEVENT +noinst_PROGRAMS += \ + processor_test +check_PROGRAMS += \ + TNonblockingServerTest \ + TNonblockingSSLServerTest +endif + +TESTS_ENVIRONMENT= \ + BOOST_TEST_LOG_SINK=tests.xml \ + BOOST_TEST_LOG_LEVEL=test_suite \ + BOOST_TEST_LOG_FORMAT=XML + +TESTS = \ + $(check_PROGRAMS) + +UnitTests_SOURCES = \ + UnitTestMain.cpp \ + OneWayHTTPTest.cpp \ + TMemoryBufferTest.cpp \ + TBufferBaseTest.cpp \ + Base64Test.cpp \ + ToStringTest.cpp \ + TypedefTest.cpp \ + TServerSocketTest.cpp \ + TServerTransportTest.cpp \ + TTransportCheckThrow.h + +UnitTests_LDADD = \ + libtestgencpp.la \ + $(BOOST_TEST_LDADD) \ + $(BOOST_SYSTEM_LDADD) \ + $(BOOST_THREAD_LDADD) + +TInterruptTest_SOURCES = \ + TSocketInterruptTest.cpp \ + TSSLSocketInterruptTest.cpp + +TInterruptTest_LDADD = \ + libtestgencpp.la \ + $(BOOST_TEST_LDADD) \ + $(BOOST_FILESYSTEM_LDADD) \ + $(BOOST_CHRONO_LDADD) \ + $(BOOST_SYSTEM_LDADD) \ + $(BOOST_THREAD_LDADD) + +TServerIntegrationTest_SOURCES = \ + TServerIntegrationTest.cpp + +TServerIntegrationTest_LDADD = \ + libtestgencpp.la \ + libprocessortest.la \ + $(BOOST_TEST_LDADD) \ + $(BOOST_SYSTEM_LDADD) \ + $(BOOST_THREAD_LDADD) + +SecurityTest_SOURCES = \ + SecurityTest.cpp + +SecurityTest_LDADD = \ + libtestgencpp.la \ + libprocessortest.la \ + $(BOOST_TEST_LDADD) \ + $(BOOST_FILESYSTEM_LDADD) \ + $(BOOST_SYSTEM_LDADD) \ + $(BOOST_THREAD_LDADD) + +TransportTest_SOURCES = \ + TransportTest.cpp + +TransportTest_LDADD = \ + libtestgencpp.la \ + $(top_builddir)/lib/cpp/libthriftz.la \ + $(BOOST_TEST_LDADD) \ + -lz + +ZlibTest_SOURCES = \ + ZlibTest.cpp + +ZlibTest_LDADD = \ + libtestgencpp.la \ + $(top_builddir)/lib/cpp/libthriftz.la \ + $(BOOST_TEST_LDADD) \ + -lz + +EnumTest_SOURCES = \ + EnumTest.cpp + +EnumTest_LDADD = \ + libtestgencpp.la \ + $(BOOST_TEST_LDADD) + +RenderedDoubleConstantsTest_SOURCES = RenderedDoubleConstantsTest.cpp + +RenderedDoubleConstantsTest_LDADD = libtestgencpp.la $(BOOST_TEST_LDADD) + +AnnotationTest_SOURCES = \ + AnnotationTest.cpp + +AnnotationTest_LDADD = \ + libtestgencpp.la \ + $(BOOST_TEST_LDADD) + +TFileTransportTest_SOURCES = \ + TFileTransportTest.cpp + +TFileTransportTest_LDADD = \ + libtestgencpp.la \ + $(BOOST_TEST_LDADD) + +# +# TFDTransportTest +# +TFDTransportTest_SOURCES = \ + TFDTransportTest.cpp + +TFDTransportTest_LDADD = \ + $(top_builddir)/lib/cpp/libthrift.la \ + $(BOOST_TEST_LDADD) + + +# +# TPipedTransportTest +# +TPipedTransportTest_SOURCES = \ + TPipedTransportTest.cpp \ + TPipeInterruptTest.cpp + +TPipedTransportTest_LDADD = \ + libtestgencpp.la \ + $(top_builddir)/lib/cpp/libthrift.la \ + $(BOOST_TEST_LDADD) \ + $(BOOST_SYSTEM_LDADD) \ + $(BOOST_THREAD_LDADD) + +# +# AllProtocolsTest +# +AllProtocolsTest_SOURCES = \ + AllProtocolTests.cpp \ + AllProtocolTests.tcc \ + GenericHelpers.h + +AllProtocolsTest_LDADD = \ + libtestgencpp.la \ + $(BOOST_TEST_LDADD) + +# +# DebugProtoTest +# +DebugProtoTest_SOURCES = \ + DebugProtoTest.cpp + +DebugProtoTest_LDADD = \ + libtestgencpp.la \ + $(BOOST_TEST_LDADD) + + +# +# JSONProtoTest +# +JSONProtoTest_SOURCES = \ + JSONProtoTest.cpp + +JSONProtoTest_LDADD = \ + libtestgencpp.la \ + $(BOOST_TEST_LDADD) + +# +# TNonblockingServerTest +# +TNonblockingServerTest_SOURCES = TNonblockingServerTest.cpp + +TNonblockingServerTest_LDADD = libprocessortest.la \ + $(top_builddir)/lib/cpp/libthrift.la \ + $(top_builddir)/lib/cpp/libthriftnb.la \ + $(BOOST_TEST_LDADD) \ + $(BOOST_LDFLAGS) \ + $(LIBEVENT_LIBS) +# +# TNonblockingSSLServerTest +# +TNonblockingSSLServerTest_SOURCES = TNonblockingSSLServerTest.cpp + +TNonblockingSSLServerTest_LDADD = libprocessortest.la \ + $(top_builddir)/lib/cpp/libthrift.la \ + $(top_builddir)/lib/cpp/libthriftnb.la \ + $(BOOST_TEST_LDADD) \ + $(BOOST_LDFLAGS) \ + $(BOOST_FILESYSTEM_LDADD) \ + $(BOOST_CHRONO_LDADD) \ + $(BOOST_SYSTEM_LDADD) \ + $(BOOST_THREAD_LDADD) \ + $(LIBEVENT_LIBS) + +# +# OptionalRequiredTest +# +OptionalRequiredTest_SOURCES = \ + OptionalRequiredTest.cpp + +OptionalRequiredTest_LDADD = \ + libtestgencpp.la \ + $(BOOST_TEST_LDADD) + +# +# OptionalRequiredTest +# +RecursiveTest_SOURCES = \ + RecursiveTest.cpp + +RecursiveTest_LDADD = \ + libtestgencpp.la \ + $(BOOST_TEST_LDADD) + +# +# SpecializationTest +# +SpecializationTest_SOURCES = \ + SpecializationTest.cpp + +SpecializationTest_LDADD = \ + libtestgencpp.la \ + $(BOOST_TEST_LDADD) + +concurrency_test_SOURCES = \ + concurrency/Tests.cpp \ + concurrency/ThreadFactoryTests.h \ + concurrency/ThreadManagerTests.h \ + concurrency/TimerManagerTests.h + +concurrency_test_LDADD = \ + $(top_builddir)/lib/cpp/libthrift.la + +link_test_SOURCES = \ + link/LinkTest.cpp \ + link/TemplatedService1.cpp \ + link/TemplatedService2.cpp + +processor_test_SOURCES = \ + processor/ProcessorTest.cpp \ + processor/EventLog.cpp \ + processor/ServerThread.cpp \ + processor/EventLog.h \ + processor/Handlers.h \ + processor/ServerThread.h + +processor_test_LDADD = libprocessortest.la \ + $(top_builddir)/lib/cpp/libthrift.la \ + $(top_builddir)/lib/cpp/libthriftnb.la \ + $(BOOST_TEST_LDADD) \ + $(BOOST_LDFLAGS) \ + $(LIBEVENT_LIBS) + +OpenSSLManualInitTest_SOURCES = \ + OpenSSLManualInitTest.cpp + +OpenSSLManualInitTest_LDADD = \ + $(top_builddir)/lib/cpp/libthrift.la \ + $(BOOST_TEST_LDADD) \ + $(OPENSSL_LDFLAGS) \ + $(OPENSSL_LIBS) + +# +# Common thrift code generation rules +# + +gen-cpp/AnnotationTest_constants.cpp gen-cpp/AnnotationTest_constants.h gen-cpp/AnnotationTest_types.cpp gen-cpp/AnnotationTest_types.h: $(top_srcdir)/test/AnnotationTest.thrift + $(THRIFT) --gen cpp $< + +gen-cpp/DebugProtoTest_types.cpp gen-cpp/DebugProtoTest_types.h gen-cpp/EmptyService.cpp gen-cpp/EmptyService.h: $(top_srcdir)/test/DebugProtoTest.thrift + $(THRIFT) --gen cpp $< + +gen-cpp/DoubleConstantsTest_constants.cpp gen-cpp/DoubleConstantsTest_constants.h: $(top_srcdir)/test/DoubleConstantsTest.thrift + $(THRIFT) --gen cpp $< + + +gen-cpp/EnumTest_types.cpp gen-cpp/EnumTest_types.h: $(top_srcdir)/test/EnumTest.thrift + $(THRIFT) --gen cpp $< + +gen-cpp/TypedefTest_types.cpp gen-cpp/TypedefTest_types.h: $(top_srcdir)/test/TypedefTest.thrift + $(THRIFT) --gen cpp $< + +gen-cpp/OptionalRequiredTest_types.cpp gen-cpp/OptionalRequiredTest_types.h: $(top_srcdir)/test/OptionalRequiredTest.thrift + $(THRIFT) --gen cpp $< + +gen-cpp/Recursive_types.cpp gen-cpp/Recursive_types.h: $(top_srcdir)/test/Recursive.thrift + $(THRIFT) --gen cpp $< + +gen-cpp/Service.cpp gen-cpp/StressTest_types.cpp: $(top_srcdir)/test/StressTest.thrift + $(THRIFT) --gen cpp $< + +gen-cpp/SecondService.cpp gen-cpp/ThriftTest_constants.cpp gen-cpp/ThriftTest.cpp gen-cpp/ThriftTest_types.cpp gen-cpp/ThriftTest_types.h: $(top_srcdir)/test/ThriftTest.thrift + $(THRIFT) --gen cpp $< + +gen-cpp/OneWayService.cpp gen-cpp/OneWayTest_constants.cpp gen-cpp/OneWayTest_types.h gen-cpp/OneWayService.h gen-cpp/OneWayTest_constants.h gen-cpp/OneWayTest_types.cpp: OneWayTest.thrift + $(THRIFT) --gen cpp $< + +gen-cpp/ChildService.cpp gen-cpp/ChildService.h gen-cpp/ParentService.cpp gen-cpp/ParentService.h gen-cpp/proc_types.cpp gen-cpp/proc_types.h: processor/proc.thrift + $(THRIFT) --gen cpp:templates,cob_style $< + +AM_CPPFLAGS = $(BOOST_CPPFLAGS) -I$(top_srcdir)/lib/cpp/src -I$(top_srcdir)/lib/cpp/src/thrift -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS -I. +AM_LDFLAGS = $(BOOST_LDFLAGS) +AM_CXXFLAGS = -Wall -Wextra -pedantic + +clean-local: + $(RM) gen-cpp/* + +EXTRA_DIST = \ + concurrency \ + processor \ + qt \ + CMakeLists.txt \ + DebugProtoTest_extras.cpp \ + ThriftTest_extras.cpp \ + OneWayTest.thrift diff --git a/src/jaegertracing/thrift/lib/cpp/test/OneWayHTTPTest.cpp b/src/jaegertracing/thrift/lib/cpp/test/OneWayHTTPTest.cpp new file mode 100644 index 000000000..55d919bba --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/test/OneWayHTTPTest.cpp @@ -0,0 +1,242 @@ +/* + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "gen-cpp/OneWayService.h" + +BOOST_AUTO_TEST_SUITE(OneWayHTTPTest) + +using namespace apache::thrift; +using apache::thrift::protocol::TProtocol; +using apache::thrift::protocol::TBinaryProtocol; +using apache::thrift::protocol::TBinaryProtocolFactory; +using apache::thrift::protocol::TJSONProtocol; +using apache::thrift::protocol::TJSONProtocolFactory; +using apache::thrift::server::TThreadedServer; +using apache::thrift::server::TServerEventHandler; +using apache::thrift::transport::TTransport; +using apache::thrift::transport::THttpServer; +using apache::thrift::transport::THttpServerTransportFactory; +using apache::thrift::transport::THttpClient; +using apache::thrift::transport::TBufferedTransport; +using apache::thrift::transport::TBufferedTransportFactory; +using apache::thrift::transport::TMemoryBuffer; +using apache::thrift::transport::TServerSocket; +using apache::thrift::transport::TSocket; +using apache::thrift::transport::TTransportException; +using std::shared_ptr; +using std::cout; +using std::cerr; +using std::endl; +using std::string; +namespace utf = boost::unit_test; + +// Define this env var to enable some logging (in case you need to debug) +#undef ENABLE_STDERR_LOGGING + +class OneWayServiceHandler : public onewaytest::OneWayServiceIf { +public: + OneWayServiceHandler() = default; + + void roundTripRPC() override { +#ifdef ENABLE_STDERR_LOGGING + cerr << "roundTripRPC()" << endl; +#endif + } + void oneWayRPC() override { +#ifdef ENABLE_STDERR_LOGGING + cerr << "oneWayRPC()" << std::endl ; +#endif + } +}; + +class OneWayServiceCloneFactory : virtual public onewaytest::OneWayServiceIfFactory { + public: + ~OneWayServiceCloneFactory() override = default; + onewaytest::OneWayServiceIf* getHandler(const ::apache::thrift::TConnectionInfo& connInfo) override + { + (void)connInfo ; + return new OneWayServiceHandler; + } + void releaseHandler( onewaytest::OneWayServiceIf* handler) override { + delete handler; + } +}; + +class RPC0ThreadClass { +public: + RPC0ThreadClass(TThreadedServer& server) : server_(server) { } // Constructor +~RPC0ThreadClass() = default; // Destructor + +void Run() { + server_.serve() ; +} + TThreadedServer& server_ ; +} ; + +using apache::thrift::concurrency::Monitor; +using apache::thrift::concurrency::Mutex; +using apache::thrift::concurrency::Synchronized; + +// copied from IntegrationTest +class TServerReadyEventHandler : public TServerEventHandler, public Monitor { +public: + TServerReadyEventHandler() : isListening_(false), accepted_(0) {} + ~TServerReadyEventHandler() override = default; + void preServe() override { + Synchronized sync(*this); + isListening_ = true; + notify(); + } + void* createContext(shared_ptr input, + shared_ptr output) override { + Synchronized sync(*this); + ++accepted_; + notify(); + + (void)input; + (void)output; + return nullptr; + } + bool isListening() const { return isListening_; } + uint64_t acceptedCount() const { return accepted_; } + +private: + bool isListening_; + uint64_t accepted_; +}; + +class TBlockableBufferedTransport : public TBufferedTransport { + public: + TBlockableBufferedTransport(std::shared_ptr transport) + : TBufferedTransport(transport, 10240), + blocked_(false) { + } + + uint32_t write_buffer_length() { + auto have_bytes = static_cast(wBase_ - wBuf_.get()); + return have_bytes ; + } + + void block() { + blocked_ = true ; +#ifdef ENABLE_STDERR_LOGGING + cerr << "block flushing\n" ; +#endif + } + void unblock() { + blocked_ = false ; +#ifdef ENABLE_STDERR_LOGGING + cerr << "unblock flushing, buffer is\n<<" << std::string((char *)wBuf_.get(), write_buffer_length()) << ">>\n" ; +#endif + } + + void flush() override { + if (blocked_) { +#ifdef ENABLE_STDERR_LOGGING + cerr << "flush was blocked\n" ; +#endif + return ; + } + TBufferedTransport::flush() ; + } + + bool blocked_ ; +} ; + +BOOST_AUTO_TEST_CASE( JSON_BufferedHTTP ) +{ + std::shared_ptr ss = std::make_shared(0) ; + TThreadedServer server( + std::make_shared(std::make_shared()), + ss, //port + std::make_shared(), + std::make_shared()); + + std::shared_ptr pEventHandler(new TServerReadyEventHandler) ; + server.setServerEventHandler(pEventHandler); + +#ifdef ENABLE_STDERR_LOGGING + cerr << "Starting the server...\n"; +#endif + RPC0ThreadClass t(server) ; + boost::thread thread(&RPC0ThreadClass::Run, &t); + + { + Synchronized sync(*(pEventHandler.get())); + while (!pEventHandler->isListening()) { + pEventHandler->wait(); + } + } + + int port = ss->getPort() ; +#ifdef ENABLE_STDERR_LOGGING + cerr << "port " << port << endl ; +#endif + + { + std::shared_ptr socket(new TSocket("localhost", port)); + socket->setRecvTimeout(10000) ; // 1000msec should be enough + std::shared_ptr blockable_transport(new TBlockableBufferedTransport(socket)); + std::shared_ptr transport(new THttpClient(blockable_transport, "localhost", "/service")); + std::shared_ptr protocol(new TJSONProtocol(transport)); + onewaytest::OneWayServiceClient client(protocol); + + + transport->open(); + client.roundTripRPC(); + blockable_transport->block() ; + uint32_t size0 = blockable_transport->write_buffer_length() ; + client.send_oneWayRPC() ; + uint32_t size1 = blockable_transport->write_buffer_length() ; + client.send_oneWayRPC() ; + uint32_t size2 = blockable_transport->write_buffer_length() ; + BOOST_CHECK((size1 - size0) == (size2 - size1)) ; + blockable_transport->unblock() ; + client.send_roundTripRPC() ; + blockable_transport->flush() ; + try { + client.recv_roundTripRPC() ; + } catch (const TTransportException &e) { + BOOST_ERROR( "we should not get a transport exception -- this means we failed: " + std::string(e.what()) ) ; + } + transport->close(); + } + server.stop(); + thread.join() ; +#ifdef ENABLE_STDERR_LOGGING + cerr << "finished.\n"; +#endif +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/jaegertracing/thrift/lib/cpp/test/OneWayTest.thrift b/src/jaegertracing/thrift/lib/cpp/test/OneWayTest.thrift new file mode 100644 index 000000000..102cf26e5 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/test/OneWayTest.thrift @@ -0,0 +1,45 @@ +/* + * 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. + * + * Contains some contributions under the Thrift Software License. + * Please see doc/old-thrift-license.txt in the Thrift distribution for + * details. + */ + +namespace c_glib OneWayTest +namespace java onewaytest +namespace cpp onewaytest +namespace rb Onewaytest +namespace perl OneWayTest +namespace csharp Onewaytest +namespace js OneWayTest +namespace st OneWayTest +namespace py OneWayTest +namespace py.twisted OneWayTest +namespace go onewaytest +namespace php OneWayTest +namespace delphi Onewaytest +namespace lua OneWayTest +namespace xsd test (uri = 'http://thrift.apache.org/ns/OneWayTest') +namespace netcore ThriftAsync.OneWayTest + +// a minimal Thrift service, for use in OneWayHTTPTtest.cpp +service OneWayService { + void roundTripRPC(), + oneway void oneWayRPC() +} diff --git a/src/jaegertracing/thrift/lib/cpp/test/OpenSSLManualInitTest.cpp b/src/jaegertracing/thrift/lib/cpp/test/OpenSSLManualInitTest.cpp new file mode 100644 index 000000000..a7518064e --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/test/OpenSSLManualInitTest.cpp @@ -0,0 +1,93 @@ +/* + * 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. + */ +// To show that this test actually tests something, you can change +// MANUAL_OPENSSL_INIT to 0 to cause automatic OpenSSL init/cleanup, +// which will cause the test to fail +#define MANUAL_OPENSSL_INIT 1 +#ifdef _WIN32 +#include +#endif + +#include +#include +#include + +using namespace apache::thrift::transport; + +void make_isolated_sslsocketfactory() { + // Here we create an isolated TSSLSocketFactory to ensure the + // constructor and destructor of TSSLSocketFactory get run. Thus + // without manual initialization normally OpenSSL would be + // uninitialized after this function. + TSSLSocketFactory factory; +} + +void openssl_init() { +#if MANUAL_OPENSSL_INIT + TSSLSocketFactory::setManualOpenSSLInitialization(true); + initializeOpenSSL(); +#endif +} + +void openssl_cleanup() { +#if MANUAL_OPENSSL_INIT + cleanupOpenSSL(); +#endif +} + +void test_openssl_availability() { + // Check whether Thrift leaves OpenSSL functionality available after + // the last TSSLSocketFactory is destroyed when manual + // initialization is set + openssl_init(); + make_isolated_sslsocketfactory(); + + // The following function is one that will fail if OpenSSL is + // uninitialized. It might also fail on very old versions of + // OpenSSL... + const EVP_MD* md = EVP_get_digestbyname("SHA256"); + BOOST_CHECK(md != nullptr); + openssl_cleanup(); +} + +#ifdef BOOST_TEST_DYN_LINK +bool init_unit_test_suite() { + boost::unit_test::test_suite* suite = &boost::unit_test::framework::master_test_suite(); + suite->p_name.value = "OpenSSLManualInit"; + + suite->add(BOOST_TEST_CASE(test_openssl_availability)); + + return true; +} + +int main( int argc, char* argv[] ) { + return ::boost::unit_test::unit_test_main(&init_unit_test_suite,argc,argv); +} +#else +boost::unit_test::test_suite* init_unit_test_suite(int argc, char* argv[]) { + THRIFT_UNUSED_VARIABLE(argc); + THRIFT_UNUSED_VARIABLE(argv); + boost::unit_test::test_suite* suite = &boost::unit_test::framework::master_test_suite(); + suite->p_name.value = "OpenSSLManualInit"; + + suite->add(BOOST_TEST_CASE(test_openssl_availability)); + + return NULL; +} +#endif \ No newline at end of file diff --git a/src/jaegertracing/thrift/lib/cpp/test/OptionalRequiredTest.cpp b/src/jaegertracing/thrift/lib/cpp/test/OptionalRequiredTest.cpp new file mode 100644 index 000000000..4c435469e --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/test/OptionalRequiredTest.cpp @@ -0,0 +1,386 @@ +/* + * 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. + * + * Contains some contributions under the Thrift Software License. + * Please see doc/old-thrift-license.txt in the Thrift distribution for + * details. + */ + +#include +#include +#include +#include +#include "gen-cpp/OptionalRequiredTest_types.h" + +#define BOOST_TEST_MODULE OptionalRequiredTest +#include + +using namespace thrift::test; +using namespace apache::thrift; +using namespace apache::thrift::transport; +using namespace apache::thrift::protocol; + +/* +template +void trywrite(const Struct& s, bool should_work) { + bool worked; + try { + TBinaryProtocol protocol(std::shared_ptr(new TMemoryBuffer)); + s.write(&protocol); + worked = true; + } catch (TProtocolException & ex) { + worked = false; + } + BOOST_CHECK(worked == should_work); +} +*/ + +template +void write_to_read(const Struct1& w, Struct2& r) { + TBinaryProtocol protocol(std::shared_ptr(new TMemoryBuffer)); + w.write(&protocol); + r.read(&protocol); +} + +BOOST_AUTO_TEST_CASE(test_optional_required_1) { + OldSchool o; + + const std::string expected_result( + "OldSchool {\n" + " 01: im_int (i16) = 0,\n" + " 02: im_str (string) = \"\",\n" + " 03: im_big (list) = list[0] {\n" + " },\n" + "}"); + const std::string result(apache::thrift::ThriftDebugString(o)); + + BOOST_CHECK_MESSAGE(!expected_result.compare(result), + "Expected:\n" << expected_result << "\nGotten:\n" << result); +} + +BOOST_AUTO_TEST_CASE(test_optional_required_2_1) { + Simple s; + + const std::string expected_result( + "Simple {\n" + " 01: im_default (i16) = 0,\n" + " 02: im_required (i16) = 0,\n" + "}"); + const std::string result(apache::thrift::ThriftDebugString(s)); + + BOOST_CHECK_MESSAGE(!expected_result.compare(result), + "Expected:\n" << expected_result << "\nGotten:\n" << result); +} + +BOOST_AUTO_TEST_CASE(test_optional_required_2_2) { + Simple s; + s.im_optional = 10; + + const std::string expected_result( + "Simple {\n" + " 01: im_default (i16) = 0,\n" + " 02: im_required (i16) = 0,\n" + "}"); + const std::string result(apache::thrift::ThriftDebugString(s)); + + BOOST_CHECK_MESSAGE(!expected_result.compare(result), + "Expected:\n" << expected_result << "\nGotten:\n" << result); +} + +BOOST_AUTO_TEST_CASE(test_optional_required_2_3) { + Simple s; + s.im_optional = 10; + s.__isset.im_optional = true; + + const std::string expected_result( + "Simple {\n" + " 01: im_default (i16) = 0,\n" + " 02: im_required (i16) = 0,\n" + " 03: im_optional (i16) = 10,\n" + "}"); + const std::string result(apache::thrift::ThriftDebugString(s)); + + BOOST_CHECK_MESSAGE(!expected_result.compare(result), + "Expected:\n" << expected_result << "\nGotten:\n" << result); +} + +BOOST_AUTO_TEST_CASE(test_optional_required_2_4) { + Simple s; + s.__isset.im_optional = true; + + const std::string expected_result( + "Simple {\n" + " 01: im_default (i16) = 0,\n" + " 02: im_required (i16) = 0,\n" + " 03: im_optional (i16) = 0,\n" + "}"); + const std::string result(apache::thrift::ThriftDebugString(s)); + + BOOST_CHECK_MESSAGE(!expected_result.compare(result), + "Expected:\n" << expected_result << "\nGotten:\n" << result); +} + +BOOST_AUTO_TEST_CASE(test_optional_required_2_5) { + Simple s; + s.__isset.im_optional = true; + s.im_optional = 10; + + const std::string expected_result( + "Simple {\n" + " 01: im_default (i16) = 0,\n" + " 02: im_required (i16) = 0,\n" + " 03: im_optional (i16) = 10,\n" + "}"); + const std::string result(apache::thrift::ThriftDebugString(s)); + + BOOST_CHECK_MESSAGE(!expected_result.compare(result), + "Expected:\n" << expected_result << "\nGotten:\n" << result); +} + +BOOST_AUTO_TEST_CASE(test_optional_required_3) { + // assign/copy-construct with non-required fields + + Simple s1, s2; + s1.__isset.im_default = true; + s1.__set_im_optional(10); + BOOST_CHECK(s1.__isset.im_default); + BOOST_CHECK(s1.__isset.im_optional); + + s2 = s1; + + BOOST_CHECK(s2.__isset.im_default); + BOOST_CHECK(s2.__isset.im_optional); + + Simple s3(s1); + + BOOST_CHECK(s3.__isset.im_default); + BOOST_CHECK(s3.__isset.im_optional); +} + +BOOST_AUTO_TEST_CASE(test_optional_required_4) { + // Write-to-read with optional fields. + + Simple s1, s2, s3; + s1.im_optional = 10; + BOOST_CHECK(!s1.__isset.im_default); + // BOOST_CHECK(!s1.__isset.im_required); // Compile error. + BOOST_CHECK(!s1.__isset.im_optional); + + write_to_read(s1, s2); + + BOOST_CHECK(s2.__isset.im_default); + // BOOST_CHECK( s2.__isset.im_required); // Compile error. + BOOST_CHECK(!s2.__isset.im_optional); + BOOST_CHECK(s3.im_optional == 0); + + s1.__isset.im_optional = true; + write_to_read(s1, s3); + + BOOST_CHECK(s3.__isset.im_default); + // BOOST_CHECK( s3.__isset.im_required); // Compile error. + BOOST_CHECK(s3.__isset.im_optional); + BOOST_CHECK(s3.im_optional == 10); +} + +BOOST_AUTO_TEST_CASE(test_optional_required_5) { + // Writing between optional and default. + + Tricky1 t1; + Tricky2 t2; + + t2.im_optional = 10; + write_to_read(t2, t1); + write_to_read(t1, t2); + BOOST_CHECK(!t1.__isset.im_default); + BOOST_CHECK(t2.__isset.im_optional); + BOOST_CHECK(t1.im_default == t2.im_optional); + BOOST_CHECK(t1.im_default == 0); +} + +BOOST_AUTO_TEST_CASE(test_optional_required_6) { + // Writing between default and required. + + Tricky1 t1; + Tricky3 t3; + write_to_read(t1, t3); + write_to_read(t3, t1); + BOOST_CHECK(t1.__isset.im_default); +} + +BOOST_AUTO_TEST_CASE(test_optional_required_7) { + // Writing between optional and required. + + Tricky2 t2; + Tricky3 t3; + t2.__isset.im_optional = true; + write_to_read(t2, t3); + write_to_read(t3, t2); +} + +BOOST_AUTO_TEST_CASE(test_optional_required_8) { + // Mu-hu-ha-ha-ha! + + Tricky2 t2; + Tricky3 t3; + try { + write_to_read(t2, t3); + abort(); + } catch (const TProtocolException&) { + } + + write_to_read(t3, t2); + BOOST_CHECK(t2.__isset.im_optional); +} + +BOOST_AUTO_TEST_CASE(test_optional_required_9) { + Complex c; + + const std::string expected_result( + "Complex {\n" + " 01: cp_default (i16) = 0,\n" + " 02: cp_required (i16) = 0,\n" + " 04: the_map (map) = map[0] {\n" + " },\n" + " 05: req_simp (struct) = Simple {\n" + " 01: im_default (i16) = 0,\n" + " 02: im_required (i16) = 0,\n" + " },\n" + "}"); + const std::string result(apache::thrift::ThriftDebugString(c)); + + BOOST_CHECK_MESSAGE(!expected_result.compare(result), + "Expected:\n" << expected_result << "\nGotten:\n" << result); +} + +BOOST_AUTO_TEST_CASE(test_optional_required_10) { + Tricky1 t1; + Tricky2 t2; + // Compile error. + //(void)(t1 == t2); +} + +BOOST_AUTO_TEST_CASE(test_optional_required_11) { + OldSchool o1, o2, o3; + BOOST_CHECK(o1 == o2); + o1.im_int = o2.im_int = 10; + BOOST_CHECK(o1 == o2); + o1.__isset.im_int = true; + o2.__isset.im_int = false; + BOOST_CHECK(o1 == o2); + o1.im_int = 20; + o1.__isset.im_int = false; + BOOST_CHECK(o1 != o2); + o1.im_int = 10; + BOOST_CHECK(o1 == o2); + o1.im_str = o2.im_str = "foo"; + BOOST_CHECK(o1 == o2); + o1.__isset.im_str = o2.__isset.im_str = true; + BOOST_CHECK(o1 == o2); + std::map mymap; + mymap[1] = "bar"; + mymap[2] = "baz"; + o1.im_big.push_back(std::map()); + BOOST_CHECK(o1 != o2); + o2.im_big.push_back(std::map()); + BOOST_CHECK(o1 == o2); + o2.im_big.push_back(mymap); + BOOST_CHECK(o1 != o2); + o1.im_big.push_back(mymap); + BOOST_CHECK(o1 == o2); + + TBinaryProtocol protocol(std::shared_ptr(new TMemoryBuffer)); + o1.write(&protocol); + + o1.im_big.push_back(mymap); + mymap[3] = "qux"; + o2.im_big.push_back(mymap); + BOOST_CHECK(o1 != o2); + o1.im_big.back()[3] = "qux"; + BOOST_CHECK(o1 == o2); + + o3.read(&protocol); + o3.im_big.push_back(mymap); + BOOST_CHECK(o1 == o3); + + const std::string expected_result( + "OldSchool {\n" + " 01: im_int (i16) = 10,\n" + " 02: im_str (string) = \"foo\",\n" + " 03: im_big (list) = list[3] {\n" + " [0] = map[0] {\n" + " },\n" + " [1] = map[2] {\n" + " 1 -> \"bar\",\n" + " 2 -> \"baz\",\n" + " },\n" + " [2] = map[3] {\n" + " 1 -> \"bar\",\n" + " 2 -> \"baz\",\n" + " 3 -> \"qux\",\n" + " },\n" + " },\n" + "}"); + const std::string result(apache::thrift::ThriftDebugString(o3)); + + BOOST_CHECK_MESSAGE(!expected_result.compare(result), + "Expected:\n" << expected_result << "\nGotten:\n" << result); +} + +BOOST_AUTO_TEST_CASE(test_optional_required_12) { + Tricky2 t1, t2; + BOOST_CHECK(t1.__isset.im_optional == false); + BOOST_CHECK(t2.__isset.im_optional == false); + BOOST_CHECK(t1 == t2); + t1.im_optional = 5; + BOOST_CHECK(t1 == t2); + t2.im_optional = 5; + BOOST_CHECK(t1 == t2); + t1.__isset.im_optional = true; + BOOST_CHECK(t1 != t2); + t2.__isset.im_optional = true; + BOOST_CHECK(t1 == t2); + t1.im_optional = 10; + BOOST_CHECK(t1 != t2); + t2.__isset.im_optional = false; + BOOST_CHECK(t1 != t2); +} + +BOOST_AUTO_TEST_CASE(test_optional_required_13) { + OptionalDefault t1, t2; + + BOOST_CHECK(t1.__isset.opt_int == true); + BOOST_CHECK(t1.__isset.opt_str == true); + BOOST_CHECK(t1.opt_int == t2.opt_int); + BOOST_CHECK(t1.opt_str == t2.opt_str); + + write_to_read(t1, t2); + BOOST_CHECK(t2.__isset.opt_int == true); + BOOST_CHECK(t2.__isset.opt_str == true); + BOOST_CHECK(t1.opt_int == t2.opt_int); + BOOST_CHECK(t1.opt_str == t2.opt_str); + + const std::string expected_result( + "OptionalDefault {\n" + " 01: opt_int (i16) = 1234,\n" + " 02: opt_str (string) = \"default\",\n" + "}"); + const std::string result(apache::thrift::ThriftDebugString(t2)); + + BOOST_CHECK_MESSAGE(!expected_result.compare(result), + "Expected:\n" << expected_result << "\nGotten:\n" << result); +} diff --git a/src/jaegertracing/thrift/lib/cpp/test/RecursiveTest.cpp b/src/jaegertracing/thrift/lib/cpp/test/RecursiveTest.cpp new file mode 100644 index 000000000..ab2e46dd7 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/test/RecursiveTest.cpp @@ -0,0 +1,92 @@ +/* + * 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. + * + * Contains some contributions under the Thrift Software License. + * Please see doc/old-thrift-license.txt in the Thrift distribution for + * details. + */ + +#include "gen-cpp/Recursive_types.h" +#include +#include +#include + +#define BOOST_TEST_MODULE RecursiveTest +#include + +using apache::thrift::transport::TMemoryBuffer; +using apache::thrift::protocol::TBinaryProtocol; +using std::shared_ptr; + +BOOST_AUTO_TEST_CASE(test_recursive_1) { + shared_ptr buf(new TMemoryBuffer()); + shared_ptr prot(new TBinaryProtocol(buf)); + + RecTree tree; + RecTree child; + tree.children.push_back(child); + + tree.write(prot.get()); + + RecTree result; + result.read(prot.get()); + BOOST_CHECK(tree == result); +} + +BOOST_AUTO_TEST_CASE(test_recursive_2) { + shared_ptr buf(new TMemoryBuffer()); + shared_ptr prot(new TBinaryProtocol(buf)); + + RecList l; + shared_ptr l2(new RecList); + l.nextitem = l2; + + l.write(prot.get()); + + RecList resultlist; + resultlist.read(prot.get()); + BOOST_CHECK(resultlist.nextitem != nullptr); + BOOST_CHECK(resultlist.nextitem->nextitem == nullptr); +} + +BOOST_AUTO_TEST_CASE(test_recursive_3) { + shared_ptr buf(new TMemoryBuffer()); + shared_ptr prot(new TBinaryProtocol(buf)); + + CoRec c; + shared_ptr r(new CoRec2); + c.other = r; + + c.write(prot.get()); + + c.read(prot.get()); + BOOST_CHECK(c.other != nullptr); + BOOST_CHECK(c.other->other.other == nullptr); +} + +BOOST_AUTO_TEST_CASE(test_recursive_4) { + shared_ptr buf(new TMemoryBuffer()); + shared_ptr prot(new TBinaryProtocol(buf)); + + shared_ptr depthLimit(new RecList); + depthLimit->nextitem = depthLimit; + BOOST_CHECK_THROW(depthLimit->write(prot.get()), + apache::thrift::protocol::TProtocolException); + + depthLimit->nextitem.reset(); +} diff --git a/src/jaegertracing/thrift/lib/cpp/test/RenderedDoubleConstantsTest.cpp b/src/jaegertracing/thrift/lib/cpp/test/RenderedDoubleConstantsTest.cpp new file mode 100644 index 000000000..0ca042b73 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/test/RenderedDoubleConstantsTest.cpp @@ -0,0 +1,122 @@ +/* + * 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. + */ +#define EPSILON 0.0000001 +#include +#include +#include + +#include "gen-cpp/DoubleConstantsTest_constants.h" +using namespace thrift::test; + +#define BOOST_TEST_MODULE RenderedDoubleConstantsTest +#include + +BOOST_AUTO_TEST_SUITE(RenderedDoubleConstantsTest) + +BOOST_AUTO_TEST_CASE(test_rendered_double_constants) { + const double EXPECTED_DOUBLE_ASSIGNED_TO_INT_CONSTANT = 1.0; + const double EXPECTED_DOUBLE_ASSIGNED_TO_NEGATIVE_INT_CONSTANT = -100.0; + const double EXPECTED_DOUBLE_ASSIGNED_TO_LARGEST_INT_CONSTANT = 9223372036854775807.0; + const double EXPECTED_DOUBLE_ASSIGNED_TO_SMALLEST_INT_CONSTANT = -9223372036854775807.0; + const double EXPECTED_DOUBLE_ASSIGNED_TO_DOUBLE_WITH_MANY_DECIMALS = 3.14159265359; + const double EXPECTED_DOUBLE_ASSIGNED_TO_FRACTIONAL_DOUBLE = 1000000.1; + const double EXPECTED_DOUBLE_ASSIGNED_TO_NEGATIVE_FRACTIONAL_DOUBLE = -1000000.1; + const double EXPECTED_DOUBLE_ASSIGNED_TO_LARGE_DOUBLE = 1.7e+308; + const double EXPECTED_DOUBLE_ASSIGNED_TO_LARGE_FRACTIONAL_DOUBLE = 9223372036854775816.43; + const double EXPECTED_DOUBLE_ASSIGNED_TO_SMALL_DOUBLE = -1.7e+308; + const double EXPECTED_DOUBLE_ASSIGNED_TO_NEGATIVE_BUT_LARGE_FRACTIONAL_DOUBLE = -9223372036854775816.43; + BOOST_CHECK_CLOSE( + g_DoubleConstantsTest_constants.DOUBLE_ASSIGNED_TO_INT_CONSTANT_TEST, + EXPECTED_DOUBLE_ASSIGNED_TO_INT_CONSTANT, EPSILON); + BOOST_CHECK_CLOSE( + g_DoubleConstantsTest_constants.DOUBLE_ASSIGNED_TO_NEGATIVE_INT_CONSTANT_TEST, + EXPECTED_DOUBLE_ASSIGNED_TO_NEGATIVE_INT_CONSTANT, EPSILON); + BOOST_CHECK_CLOSE( + g_DoubleConstantsTest_constants.DOUBLE_ASSIGNED_TO_LARGEST_INT_CONSTANT_TEST, + EXPECTED_DOUBLE_ASSIGNED_TO_LARGEST_INT_CONSTANT, EPSILON); + BOOST_CHECK_CLOSE( + g_DoubleConstantsTest_constants.DOUBLE_ASSIGNED_TO_SMALLEST_INT_CONSTANT_TEST, + EXPECTED_DOUBLE_ASSIGNED_TO_SMALLEST_INT_CONSTANT, EPSILON); + BOOST_CHECK_CLOSE( + g_DoubleConstantsTest_constants.DOUBLE_ASSIGNED_TO_DOUBLE_WITH_MANY_DECIMALS_TEST, + EXPECTED_DOUBLE_ASSIGNED_TO_DOUBLE_WITH_MANY_DECIMALS, EPSILON); + BOOST_CHECK_CLOSE( + g_DoubleConstantsTest_constants.DOUBLE_ASSIGNED_TO_FRACTIONAL_DOUBLE_TEST, + EXPECTED_DOUBLE_ASSIGNED_TO_FRACTIONAL_DOUBLE, EPSILON); + BOOST_CHECK_CLOSE( + g_DoubleConstantsTest_constants.DOUBLE_ASSIGNED_TO_NEGATIVE_FRACTIONAL_DOUBLE_TEST, + EXPECTED_DOUBLE_ASSIGNED_TO_NEGATIVE_FRACTIONAL_DOUBLE, EPSILON); + BOOST_CHECK_CLOSE( + g_DoubleConstantsTest_constants.DOUBLE_ASSIGNED_TO_LARGE_DOUBLE_TEST, + EXPECTED_DOUBLE_ASSIGNED_TO_LARGE_DOUBLE, EPSILON); + BOOST_CHECK_CLOSE( + g_DoubleConstantsTest_constants.DOUBLE_ASSIGNED_TO_LARGE_FRACTIONAL_DOUBLE_TEST, + EXPECTED_DOUBLE_ASSIGNED_TO_LARGE_FRACTIONAL_DOUBLE, EPSILON); + BOOST_CHECK_CLOSE( + g_DoubleConstantsTest_constants.DOUBLE_ASSIGNED_TO_SMALL_DOUBLE_TEST, + EXPECTED_DOUBLE_ASSIGNED_TO_SMALL_DOUBLE, EPSILON); + BOOST_CHECK_CLOSE( + g_DoubleConstantsTest_constants.DOUBLE_ASSIGNED_TO_NEGATIVE_BUT_LARGE_FRACTIONAL_DOUBLE_TEST, + EXPECTED_DOUBLE_ASSIGNED_TO_NEGATIVE_BUT_LARGE_FRACTIONAL_DOUBLE, EPSILON); + BOOST_CHECK( + typeid(g_DoubleConstantsTest_constants.DOUBLE_ASSIGNED_TO_INT_CONSTANT_TEST).hash_code() == + typeid(EXPECTED_DOUBLE_ASSIGNED_TO_INT_CONSTANT).hash_code()); + BOOST_CHECK( + typeid(g_DoubleConstantsTest_constants.DOUBLE_ASSIGNED_TO_NEGATIVE_INT_CONSTANT_TEST).hash_code() == + typeid(EXPECTED_DOUBLE_ASSIGNED_TO_NEGATIVE_INT_CONSTANT).hash_code()); + BOOST_CHECK( + typeid(g_DoubleConstantsTest_constants.DOUBLE_ASSIGNED_TO_LARGEST_INT_CONSTANT_TEST).hash_code() == + typeid(EXPECTED_DOUBLE_ASSIGNED_TO_LARGEST_INT_CONSTANT).hash_code()); + BOOST_CHECK( + typeid(g_DoubleConstantsTest_constants.DOUBLE_ASSIGNED_TO_SMALLEST_INT_CONSTANT_TEST).hash_code() == + typeid(EXPECTED_DOUBLE_ASSIGNED_TO_SMALLEST_INT_CONSTANT).hash_code()); + BOOST_CHECK( + typeid(g_DoubleConstantsTest_constants.DOUBLE_ASSIGNED_TO_DOUBLE_WITH_MANY_DECIMALS_TEST).hash_code() == + typeid(EXPECTED_DOUBLE_ASSIGNED_TO_DOUBLE_WITH_MANY_DECIMALS).hash_code()); + BOOST_CHECK( + typeid(g_DoubleConstantsTest_constants.DOUBLE_ASSIGNED_TO_FRACTIONAL_DOUBLE_TEST).hash_code() == + typeid(EXPECTED_DOUBLE_ASSIGNED_TO_FRACTIONAL_DOUBLE).hash_code()); + BOOST_CHECK( + typeid(g_DoubleConstantsTest_constants.DOUBLE_ASSIGNED_TO_NEGATIVE_FRACTIONAL_DOUBLE_TEST).hash_code() == + typeid(EXPECTED_DOUBLE_ASSIGNED_TO_NEGATIVE_FRACTIONAL_DOUBLE).hash_code()); + BOOST_CHECK( + typeid(g_DoubleConstantsTest_constants.DOUBLE_ASSIGNED_TO_LARGE_DOUBLE_TEST).hash_code() == + typeid(EXPECTED_DOUBLE_ASSIGNED_TO_LARGE_DOUBLE).hash_code()); + BOOST_CHECK( + typeid(g_DoubleConstantsTest_constants.DOUBLE_ASSIGNED_TO_LARGE_FRACTIONAL_DOUBLE_TEST).hash_code() == + typeid(EXPECTED_DOUBLE_ASSIGNED_TO_LARGE_FRACTIONAL_DOUBLE).hash_code()); + BOOST_CHECK( + typeid(g_DoubleConstantsTest_constants.DOUBLE_ASSIGNED_TO_SMALL_DOUBLE_TEST).hash_code() == + typeid(EXPECTED_DOUBLE_ASSIGNED_TO_SMALL_DOUBLE).hash_code()); + BOOST_CHECK( + typeid(g_DoubleConstantsTest_constants.DOUBLE_ASSIGNED_TO_NEGATIVE_BUT_LARGE_FRACTIONAL_DOUBLE_TEST) + .hash_code() == + typeid(EXPECTED_DOUBLE_ASSIGNED_TO_NEGATIVE_BUT_LARGE_FRACTIONAL_DOUBLE).hash_code()); +} + +BOOST_AUTO_TEST_CASE(test_rendered_double_list) { + const std::vector EXPECTED_DOUBLE_LIST{1.0,-100.0,100.0,9223372036854775807.0,-9223372036854775807.0, + 3.14159265359,1000000.1,-1000000.1,1.7e+308,-1.7e+308,9223372036854775816.43,-9223372036854775816.43}; + BOOST_CHECK_EQUAL(g_DoubleConstantsTest_constants.DOUBLE_LIST_TEST.size(), EXPECTED_DOUBLE_LIST.size()); + for (unsigned int i = 0; i < EXPECTED_DOUBLE_LIST.size(); ++i) { + BOOST_CHECK_CLOSE(g_DoubleConstantsTest_constants.DOUBLE_LIST_TEST[i], EXPECTED_DOUBLE_LIST[i], EPSILON); + } +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/jaegertracing/thrift/lib/cpp/test/SecurityTest.cpp b/src/jaegertracing/thrift/lib/cpp/test/SecurityTest.cpp new file mode 100644 index 000000000..982a4f30c --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/test/SecurityTest.cpp @@ -0,0 +1,278 @@ +/* + * 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. + */ + +#define BOOST_TEST_MODULE SecurityTest +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef __linux__ +#include +#endif + +using apache::thrift::transport::TSSLServerSocket; +using apache::thrift::transport::TServerTransport; +using apache::thrift::transport::TSSLSocket; +using apache::thrift::transport::TSSLSocketFactory; +using apache::thrift::transport::TTransport; +using apache::thrift::transport::TTransportException; +using apache::thrift::transport::TTransportFactory; + +using std::bind; +using std::shared_ptr; + +boost::filesystem::path keyDir; +boost::filesystem::path certFile(const std::string& filename) +{ + return keyDir / filename; +} +boost::mutex gMutex; + +struct GlobalFixture +{ + GlobalFixture() + { + using namespace boost::unit_test::framework; + for (int i = 0; i < master_test_suite().argc; ++i) + { + BOOST_TEST_MESSAGE(boost::format("argv[%1%] = \"%2%\"") % i % master_test_suite().argv[i]); + } + + #ifdef __linux__ + // OpenSSL calls send() without MSG_NOSIGPIPE so writing to a socket that has + // disconnected can cause a SIGPIPE signal... + signal(SIGPIPE, SIG_IGN); + #endif + + TSSLSocketFactory::setManualOpenSSLInitialization(true); + apache::thrift::transport::initializeOpenSSL(); + + keyDir = boost::filesystem::current_path().parent_path().parent_path().parent_path() / "test" / "keys"; + if (!boost::filesystem::exists(certFile("server.crt"))) + { + keyDir = boost::filesystem::path(master_test_suite().argv[master_test_suite().argc - 1]); + if (!boost::filesystem::exists(certFile("server.crt"))) + { + throw std::invalid_argument("The last argument to this test must be the directory containing the test certificate(s)."); + } + } + } + + virtual ~GlobalFixture() + { + apache::thrift::transport::cleanupOpenSSL(); +#ifdef __linux__ + signal(SIGPIPE, SIG_DFL); +#endif + } +}; + +#if (BOOST_VERSION >= 105900) +BOOST_GLOBAL_FIXTURE(GlobalFixture); +#else +BOOST_GLOBAL_FIXTURE(GlobalFixture) +#endif + +struct SecurityFixture +{ + void server(apache::thrift::transport::SSLProtocol protocol) + { + try + { + boost::mutex::scoped_lock lock(mMutex); + + shared_ptr pServerSocketFactory; + shared_ptr pServerSocket; + + pServerSocketFactory.reset(new TSSLSocketFactory(static_cast(protocol))); + pServerSocketFactory->ciphers("ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH"); + pServerSocketFactory->loadCertificate(certFile("server.crt").string().c_str()); + pServerSocketFactory->loadPrivateKey(certFile("server.key").string().c_str()); + pServerSocketFactory->server(true); + pServerSocket.reset(new TSSLServerSocket("localhost", 0, pServerSocketFactory)); + shared_ptr connectedClient; + + try + { + pServerSocket->listen(); + mPort = pServerSocket->getPort(); + mCVar.notify_one(); + lock.unlock(); + + connectedClient = pServerSocket->accept(); + uint8_t buf[2]; + buf[0] = 'O'; + buf[1] = 'K'; + connectedClient->write(&buf[0], 2); + connectedClient->flush(); + } + + catch (apache::thrift::transport::TTransportException& ex) + { + boost::mutex::scoped_lock lock(gMutex); + BOOST_TEST_MESSAGE(boost::format("SRV %1% Exception: %2%") % boost::this_thread::get_id() % ex.what()); + } + + if (connectedClient) + { + connectedClient->close(); + connectedClient.reset(); + } + + pServerSocket->close(); + pServerSocket.reset(); + } + catch (std::exception& ex) + { + BOOST_FAIL(boost::format("%1%: %2%") % typeid(ex).name() % ex.what()); + } + } + + void client(apache::thrift::transport::SSLProtocol protocol) + { + try + { + shared_ptr pClientSocketFactory; + shared_ptr pClientSocket; + + try + { + pClientSocketFactory.reset(new TSSLSocketFactory(static_cast(protocol))); + pClientSocketFactory->authenticate(true); + pClientSocketFactory->loadCertificate(certFile("client.crt").string().c_str()); + pClientSocketFactory->loadPrivateKey(certFile("client.key").string().c_str()); + pClientSocketFactory->loadTrustedCertificates(certFile("CA.pem").string().c_str()); + pClientSocket = pClientSocketFactory->createSocket("localhost", mPort); + pClientSocket->open(); + + uint8_t buf[3]; + buf[0] = 0; + buf[1] = 0; + BOOST_CHECK_EQUAL(2, pClientSocket->read(&buf[0], 2)); + BOOST_CHECK_EQUAL(0, memcmp(&buf[0], "OK", 2)); + mConnected = true; + } + catch (apache::thrift::transport::TTransportException& ex) + { + boost::mutex::scoped_lock lock(gMutex); + BOOST_TEST_MESSAGE(boost::format("CLI %1% Exception: %2%") % boost::this_thread::get_id() % ex.what()); + } + + if (pClientSocket) + { + pClientSocket->close(); + pClientSocket.reset(); + } + } + catch (std::exception& ex) + { + BOOST_FAIL(boost::format("%1%: %2%") % typeid(ex).name() % ex.what()); + } + } + + static const char *protocol2str(size_t protocol) + { + static const char *strings[apache::thrift::transport::LATEST + 1] = + { + "SSLTLS", + "SSLv2", + "SSLv3", + "TLSv1_0", + "TLSv1_1", + "TLSv1_2" + }; + return strings[protocol]; + } + + boost::mutex mMutex; + boost::condition_variable mCVar; + int mPort; + bool mConnected; +}; + +BOOST_FIXTURE_TEST_SUITE(BOOST_TEST_MODULE, SecurityFixture) + +BOOST_AUTO_TEST_CASE(ssl_security_matrix) +{ + try + { + // matrix of connection success between client and server with different SSLProtocol selections + bool matrix[apache::thrift::transport::LATEST + 1][apache::thrift::transport::LATEST + 1] = + { + // server = SSLTLS SSLv2 SSLv3 TLSv1_0 TLSv1_1 TLSv1_2 + // client + /* SSLTLS */ { true, false, false, true, true, true }, + /* SSLv2 */ { false, false, false, false, false, false }, + /* SSLv3 */ { false, false, true, false, false, false }, + /* TLSv1_0 */ { true, false, false, true, false, false }, + /* TLSv1_1 */ { true, false, false, false, true, false }, + /* TLSv1_2 */ { true, false, false, false, false, true } + }; + + for (size_t si = 0; si <= apache::thrift::transport::LATEST; ++si) + { + for (size_t ci = 0; ci <= apache::thrift::transport::LATEST; ++ci) + { + if (si == 1 || ci == 1) + { + // Skip all SSLv2 cases - protocol not supported + continue; + } + +#ifdef OPENSSL_NO_SSL3 + if (si == 2 || ci == 2) + { + // Skip all SSLv3 cases - protocol not supported + continue; + } +#endif + + boost::mutex::scoped_lock lock(mMutex); + + BOOST_TEST_MESSAGE(boost::format("TEST: Server = %1%, Client = %2%") + % protocol2str(si) % protocol2str(ci)); + + mConnected = false; + // thread_group manages the thread lifetime - ignore the return value of create_thread + boost::thread_group threads; + (void)threads.create_thread(bind(&SecurityFixture::server, this, static_cast(si))); + mCVar.wait(lock); // wait for listen() to succeed + lock.unlock(); + (void)threads.create_thread(bind(&SecurityFixture::client, this, static_cast(ci))); + threads.join_all(); + + BOOST_CHECK_MESSAGE(mConnected == matrix[ci][si], + boost::format(" Server = %1%, Client = %2% expected mConnected == %3% but was %4%") + % protocol2str(si) % protocol2str(ci) % matrix[ci][si] % mConnected); + } + } + } + catch (std::exception& ex) + { + BOOST_FAIL(boost::format("%1%: %2%") % typeid(ex).name() % ex.what()); + } +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/jaegertracing/thrift/lib/cpp/test/SpecializationTest.cpp b/src/jaegertracing/thrift/lib/cpp/test/SpecializationTest.cpp new file mode 100644 index 000000000..008837d31 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/test/SpecializationTest.cpp @@ -0,0 +1,103 @@ +#define _USE_MATH_DEFINES +#include +#include +#include +#include + +using namespace thrift::test::debug; +using namespace apache::thrift::transport; +using namespace apache::thrift::protocol; + +#define BOOST_TEST_MODULE SpecializationTest +#include + +typedef TBinaryProtocolT MyProtocol; +// typedef TBinaryProtocolT MyProtocol; + +BOOST_AUTO_TEST_CASE(test_specialization_1) { + OneOfEach ooe; + ooe.im_true = true; + ooe.im_false = false; + ooe.a_bite = 0x7f; + ooe.integer16 = 27000; + ooe.integer32 = 1 << 24; + ooe.integer64 = (uint64_t)6000 * 1000 * 1000; + ooe.double_precision = M_PI; + ooe.some_characters = "JSON THIS! \"\1"; + ooe.zomg_unicode = "\xd7\n\a\t"; + ooe.base64 = "\1\2\3\255"; + + Nesting n; + n.my_ooe = ooe; + n.my_ooe.integer16 = 16; + n.my_ooe.integer32 = 32; + n.my_ooe.integer64 = 64; + n.my_ooe.double_precision = (std::sqrt(5.0) + 1) / 2; + n.my_ooe.some_characters = ":R (me going \"rrrr\")"; + n.my_ooe.zomg_unicode = "\xd3\x80\xe2\x85\xae\xce\x9d\x20\xd0\x9d\xce" + "\xbf\xe2\x85\xbf\xd0\xbe\xc9\xa1\xd0\xb3\xd0" + "\xb0\xcf\x81\xe2\x84\x8e\x20\xce\x91\x74\x74" + "\xce\xb1\xe2\x85\xbd\xce\xba\xc7\x83\xe2\x80" + "\xbc"; + n.my_bonk.type = 31337; + n.my_bonk.message = "I am a bonk... xor!"; + + HolyMoley hm; + + hm.big.push_back(ooe); + hm.big.push_back(n.my_ooe); + hm.big[0].a_bite = 0x22; + hm.big[1].a_bite = 0x33; + + std::vector stage1; + stage1.push_back("and a one"); + stage1.push_back("and a two"); + hm.contain.insert(stage1); + stage1.clear(); + stage1.push_back("then a one, two"); + stage1.push_back("three!"); + stage1.push_back("FOUR!!"); + hm.contain.insert(stage1); + stage1.clear(); + hm.contain.insert(stage1); + + std::vector stage2; + hm.bonks["nothing"] = stage2; + stage2.resize(stage2.size() + 1); + stage2.back().type = 1; + stage2.back().message = "Wait."; + stage2.resize(stage2.size() + 1); + stage2.back().type = 2; + stage2.back().message = "What?"; + hm.bonks["something"] = stage2; + stage2.clear(); + stage2.resize(stage2.size() + 1); + stage2.back().type = 3; + stage2.back().message = "quoth"; + stage2.resize(stage2.size() + 1); + stage2.back().type = 4; + stage2.back().message = "the raven"; + stage2.resize(stage2.size() + 1); + stage2.back().type = 5; + stage2.back().message = "nevermore"; + hm.bonks["poe"] = stage2; + + std::shared_ptr buffer(new TMemoryBuffer()); + std::shared_ptr proto(new MyProtocol(buffer)); + + ooe.write(proto.get()); + OneOfEach ooe2; + ooe2.read(proto.get()); + + BOOST_CHECK(ooe == ooe2); + + hm.write(proto.get()); + HolyMoley hm2; + hm2.read(proto.get()); + + BOOST_CHECK(hm == hm2); + + hm2.big[0].a_bite = 0x00; + + BOOST_CHECK(hm != hm2); +} diff --git a/src/jaegertracing/thrift/lib/cpp/test/TBufferBaseTest.cpp b/src/jaegertracing/thrift/lib/cpp/test/TBufferBaseTest.cpp new file mode 100644 index 000000000..7203f829b --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/test/TBufferBaseTest.cpp @@ -0,0 +1,639 @@ +/* + * 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. + */ + +#include +#include +#include +#include +#include + +using std::shared_ptr; +using apache::thrift::transport::TMemoryBuffer; +using apache::thrift::transport::TBufferedTransport; +using apache::thrift::transport::TFramedTransport; +using apache::thrift::transport::test::TShortReadTransport; +using std::string; + +// Shamelessly copied from ZlibTransport. TODO: refactor. +unsigned int dist[][5000] = { + { 1<<15 }, + + { + 5,13,9,1,8,9,11,13,18,48,24,13,21,13,5,11,35,2,4,20,17,72,27,14,15,4,7,26, + 12,1,14,9,2,16,29,41,7,24,4,27,14,4,1,4,25,3,6,34,10,8,50,2,14,13,55,29,3, + 43,53,49,14,4,10,32,27,48,1,3,1,11,5,17,16,51,17,30,15,11,9,2,2,11,52,12,2, + 13,94,1,19,1,38,2,8,43,8,33,7,30,8,17,22,2,15,14,12,34,2,12,6,37,29,74,3, + 165,16,11,17,5,14,3,10,7,37,11,24,7,1,3,12,37,8,9,34,17,12,8,21,13,37,1,4, + 30,14,78,4,15,2,40,37,17,12,36,82,14,4,1,4,7,17,11,16,88,77,2,3,15,3,34,11, + 5,79,22,34,8,4,4,40,22,24,28,9,13,3,34,27,9,16,39,16,39,13,2,4,3,41,26,10,4, + 33,4,7,12,5,6,3,10,30,8,21,16,58,19,9,0,47,7,13,11,19,15,7,53,57,2,13,28,22, + 3,16,9,25,33,12,40,7,12,64,7,14,24,44,9,2,14,11,2,58,1,26,30,11,9,5,24,7,9, + 94,2,10,21,5,5,4,5,6,179,9,18,2,7,13,31,41,17,4,36,3,21,6,26,8,15,18,44,27, + 11,9,25,7,0,14,2,12,20,23,13,2,163,9,5,15,65,2,14,6,8,98,11,15,14,34,2,3,10, + 22,9,92,7,10,32,67,13,3,4,35,8,2,1,5,0,26,381,7,27,8,2,16,93,4,19,5,8,25,9, + 31,14,4,21,5,3,9,22,56,4,18,3,11,18,6,4,3,40,12,16,110,8,35,14,1,18,40,9,12, + 14,3,11,7,57,13,18,116,53,19,22,7,16,11,5,8,21,16,1,75,21,20,1,28,2,6,1,7, + 19,38,5,6,9,9,4,1,7,55,36,62,5,4,4,24,15,1,12,35,48,20,5,17,1,5,26,15,4,54, + 13,5,5,15,5,19,32,29,31,7,6,40,7,80,11,18,8,128,48,6,12,84,13,4,7,2,13,9,16, + 17,3,254,1,4,181,8,44,7,6,24,27,9,23,14,34,16,22,25,10,3,3,4,4,12,2,12,6,7, + 13,58,13,6,11,19,53,11,66,18,19,10,4,13,2,5,49,58,1,67,7,21,64,14,11,14,8,3, + 26,33,91,31,20,7,9,42,39,4,3,55,11,10,0,7,4,75,8,12,0,27,3,8,9,0,12,12,23, + 28,23,20,4,13,30,2,22,20,19,30,6,22,2,6,4,24,7,19,55,86,5,33,2,161,6,7,1,62, + 13,3,72,12,12,9,7,12,10,5,10,29,1,5,22,13,13,5,2,12,3,7,14,18,2,3,46,21,17, + 15,19,3,27,5,16,45,31,10,8,17,18,18,3,7,24,6,55,9,3,6,12,10,12,8,91,9,4,4,4, + 27,29,16,5,7,22,43,28,11,14,8,11,28,109,55,71,40,3,8,22,26,15,44,3,25,29,5, + 3,32,17,12,3,29,27,25,15,11,8,40,39,38,17,3,9,11,2,32,11,6,20,48,75,27,3,7, + 54,12,95,12,7,24,23,2,13,8,15,16,5,12,4,17,7,19,88,2,6,13,115,45,12,21,2,86, + 74,9,7,5,16,32,16,2,21,18,6,34,5,18,260,7,12,16,44,19,92,31,7,8,2,9,0,0,15, + 8,38,4,8,20,18,2,83,3,3,4,9,5,3,10,3,5,29,15,7,11,8,48,17,23,2,17,4,11,22, + 21,64,8,8,4,19,95,0,17,28,9,11,20,71,5,11,18,12,13,45,49,4,1,33,32,23,13,5, + 52,2,2,16,3,4,7,12,2,1,12,6,24,1,22,155,21,3,45,4,12,44,26,5,40,36,9,9,8,20, + 35,31,3,2,32,50,10,8,37,2,75,35,22,15,192,8,11,23,1,4,29,6,8,8,5,12,18,32,4, + 7,12,2,0,0,9,5,48,11,35,3,1,123,6,29,8,11,8,23,51,16,6,63,12,2,5,4,14,2,15, + 7,14,3,2,7,17,32,8,8,10,1,23,62,2,49,6,49,47,23,3,20,7,11,39,10,24,6,15,5,5, + 11,8,16,36,8,13,20,3,10,44,7,52,7,10,36,6,15,10,5,11,4,14,19,17,10,12,3,6, + 23,4,13,94,70,7,36,7,38,7,28,8,4,15,3,19,4,33,39,21,109,4,80,6,40,4,432,4,4, + 7,8,3,31,8,28,37,34,10,2,21,5,22,0,7,36,14,12,6,24,1,21,5,9,2,29,20,54,113, + 13,31,39,27,6,0,27,4,5,2,43,7,8,57,8,62,7,9,12,22,90,30,6,19,7,10,20,6,5,58, + 32,30,41,4,10,25,13,3,8,7,10,2,9,6,151,44,16,12,16,20,8,3,18,11,17,4,10,45, + 15,8,56,38,52,25,40,14,4,17,15,8,2,19,7,8,26,30,2,3,180,8,26,17,38,35,5,16, + 28,5,15,56,13,14,18,9,15,83,27,3,9,4,11,8,27,27,44,10,12,8,3,48,14,7,9,4,4, + 8,4,5,9,122,8,14,12,19,17,21,4,29,63,21,17,10,12,18,47,10,10,53,4,18,16,4,8, + 118,9,5,12,9,11,9,3,12,32,3,23,2,15,3,3,30,3,17,235,15,22,9,299,14,17,1,5, + 16,8,3,7,3,13,2,7,6,4,8,66,2,13,6,15,16,47,3,36,5,7,10,24,1,9,9,8,13,16,26, + 12,7,24,21,18,49,23,39,10,41,4,13,4,27,11,12,12,19,4,147,8,10,9,40,21,2,83, + 10,5,6,11,25,9,50,57,40,12,12,21,1,3,24,23,9,3,9,13,2,3,12,57,8,11,13,15,26, + 15,10,47,36,4,25,1,5,8,5,4,0,12,49,5,19,4,6,16,14,6,10,69,10,33,29,7,8,61, + 12,4,0,3,7,6,3,16,29,27,38,4,21,0,24,3,2,1,19,16,22,2,8,138,11,7,7,3,12,22, + 3,16,5,7,3,53,9,10,32,14,5,7,3,6,22,9,59,26,8,7,58,5,16,11,55,7,4,11,146,91, + 8,13,18,14,6,8,8,31,26,22,6,11,30,11,30,15,18,31,3,48,17,7,6,4,9,2,25,3,35, + 13,13,7,8,4,31,10,8,10,4,3,45,10,23,2,7,259,17,21,13,14,3,26,3,8,27,4,18,9, + 66,7,12,5,8,17,4,23,55,41,51,2,32,26,66,4,21,14,12,65,16,22,17,5,14,2,29,24, + 7,3,36,2,43,53,86,5,28,4,58,13,49,121,6,2,73,2,1,47,4,2,27,10,35,28,27,10, + 17,10,56,7,10,14,28,20,24,40,7,4,7,3,10,11,32,6,6,3,15,11,54,573,2,3,6,2,3, + 14,64,4,16,12,16,42,10,26,4,6,11,69,18,27,2,2,17,22,9,13,22,11,6,1,15,49,3, + 14,1 + }, + + { + 11,11,11,15,47,1,3,1,23,5,8,18,3,23,15,21,1,7,19,10,26,1,17,11,31,21,41,18, + 34,4,9,58,19,3,3,36,5,18,13,3,14,4,9,10,4,19,56,15,3,5,3,11,27,9,4,10,13,4, + 11,6,9,2,18,3,10,19,11,4,53,4,2,2,3,4,58,16,3,0,5,30,2,11,93,10,2,14,10,6,2, + 115,2,25,16,22,38,101,4,18,13,2,145,51,45,15,14,15,13,20,7,24,5,13,14,30,40, + 10,4,107,12,24,14,39,12,6,13,20,7,7,11,5,18,18,45,22,6,39,3,2,1,51,9,11,4, + 13,9,38,44,8,11,9,15,19,9,23,17,17,17,13,9,9,1,10,4,18,6,2,9,5,27,32,72,8, + 37,9,4,10,30,17,20,15,17,66,10,4,73,35,37,6,4,16,117,45,13,4,75,5,24,65,10, + 4,9,4,13,46,5,26,29,10,4,4,52,3,13,18,63,6,14,9,24,277,9,88,2,48,27,123,14, + 61,7,5,10,8,7,90,3,10,3,3,48,17,13,10,18,33,2,19,36,6,21,1,16,12,5,6,2,16, + 15,29,88,28,2,15,6,11,4,6,11,3,3,4,18,9,53,5,4,3,33,8,9,8,6,7,36,9,62,14,2, + 1,10,1,16,7,32,7,23,20,11,10,23,2,1,0,9,16,40,2,81,5,22,8,5,4,37,51,37,10, + 19,57,11,2,92,31,6,39,10,13,16,8,20,6,9,3,10,18,25,23,12,30,6,2,26,7,64,18, + 6,30,12,13,27,7,10,5,3,33,24,99,4,23,4,1,27,7,27,49,8,20,16,3,4,13,9,22,67, + 28,3,10,16,3,2,10,4,8,1,8,19,3,85,6,21,1,9,16,2,30,10,33,12,4,9,3,1,60,38,6, + 24,32,3,14,3,40,8,34,115,5,9,27,5,96,3,40,6,15,5,8,22,112,5,5,25,17,58,2,7, + 36,21,52,1,3,95,12,21,4,11,8,59,24,5,21,4,9,15,8,7,21,3,26,5,11,6,7,17,65, + 14,11,10,2,17,5,12,22,4,4,2,21,8,112,3,34,63,35,2,25,1,2,15,65,23,0,3,5,15, + 26,27,9,5,48,11,15,4,9,5,33,20,15,1,18,19,11,24,40,10,21,74,6,6,32,30,40,5, + 4,7,44,10,25,46,16,12,5,40,7,18,5,18,9,12,8,4,25,5,6,36,4,43,8,9,12,35,17,4, + 8,9,11,27,5,10,17,40,8,12,4,18,9,18,12,20,25,39,42,1,24,13,22,15,7,112,35,3, + 7,17,33,2,5,5,19,8,4,12,24,14,13,2,1,13,6,5,19,11,7,57,0,19,6,117,48,14,8, + 10,51,17,12,14,2,5,8,9,15,4,48,53,13,22,4,25,12,11,19,45,5,2,6,54,22,9,15,9, + 13,2,7,11,29,82,16,46,4,26,14,26,40,22,4,26,6,18,13,4,4,20,3,3,7,12,17,8,9, + 23,6,20,7,25,23,19,5,15,6,23,15,11,19,11,3,17,59,8,18,41,4,54,23,44,75,13, + 20,6,11,2,3,1,13,10,3,7,12,3,4,7,8,30,6,6,7,3,32,9,5,28,6,114,42,13,36,27, + 59,6,93,13,74,8,69,140,3,1,17,48,105,6,11,5,15,1,10,10,14,8,53,0,8,24,60,2, + 6,35,2,12,32,47,16,17,75,2,5,4,37,28,10,5,9,57,4,59,5,12,13,7,90,5,11,5,24, + 22,13,30,1,2,10,9,6,19,3,18,47,2,5,7,9,35,15,3,6,1,21,14,14,18,14,9,12,8,73, + 6,19,3,32,9,14,17,17,5,55,23,6,16,28,3,11,48,4,6,6,6,12,16,30,10,30,27,51, + 18,29,2,3,15,1,76,0,16,33,4,27,3,62,4,10,2,4,8,15,9,41,26,22,2,4,20,4,49,0, + 8,1,57,13,12,39,3,63,10,19,34,35,2,7,8,29,72,4,10,0,77,8,6,7,9,15,21,9,4,1, + 20,23,1,9,18,9,15,36,4,7,6,15,5,7,7,40,2,9,22,2,3,20,4,12,34,13,6,18,15,1, + 38,20,12,7,16,3,19,85,12,16,18,16,2,17,1,13,8,6,12,15,97,17,12,9,3,21,15,12, + 23,44,81,26,30,2,5,17,6,6,0,22,42,19,6,19,41,14,36,7,3,56,7,9,3,2,6,9,69,3, + 15,4,30,28,29,7,9,15,17,17,6,1,6,153,9,33,5,12,14,16,28,3,8,7,14,12,4,6,36, + 9,24,13,13,4,2,9,15,19,9,53,7,13,4,150,17,9,2,6,12,7,3,5,58,19,58,28,8,14,3, + 20,3,0,32,56,7,5,4,27,1,68,4,29,13,5,58,2,9,65,41,27,16,15,12,14,2,10,9,24, + 3,2,9,2,2,3,14,32,10,22,3,13,11,4,6,39,17,0,10,5,5,10,35,16,19,14,1,8,63,19, + 14,8,56,10,2,12,6,12,6,7,16,2,9,9,12,20,73,25,13,21,17,24,5,32,8,12,25,8,14, + 16,5,23,3,7,6,3,11,24,6,30,4,21,13,28,4,6,29,15,5,17,6,26,8,15,8,3,7,7,50, + 11,30,6,2,28,56,16,24,25,23,24,89,31,31,12,7,22,4,10,17,3,3,8,11,13,5,3,27, + 1,12,1,14,8,10,29,2,5,2,2,20,10,0,31,10,21,1,48,3,5,43,4,5,18,13,5,18,25,34, + 18,3,5,22,16,3,4,20,3,9,3,25,6,6,44,21,3,12,7,5,42,3,2,14,4,36,5,3,45,51,15, + 9,11,28,9,7,6,6,12,26,5,14,10,11,42,55,13,21,4,28,6,7,23,27,11,1,41,36,0,32, + 15,26,2,3,23,32,11,2,15,7,29,26,144,33,20,12,7,21,10,7,11,65,46,10,13,20,32, + 4,4,5,19,2,19,15,49,41,1,75,10,11,25,1,2,45,11,8,27,18,10,60,28,29,12,30,19, + 16,4,24,11,19,27,17,49,18,7,40,13,19,22,8,55,12,11,3,6,5,11,8,10,22,5,9,9, + 25,7,17,7,64,1,24,2,12,17,44,4,12,27,21,11,10,7,47,5,9,13,12,38,27,21,7,29, + 7,1,17,3,3,5,48,62,10,3,11,17,15,15,6,3,8,10,8,18,19,13,3,9,7,6,44,9,10,4, + 43,8,6,6,14,20,38,24,2,4,5,5,7,5,9,39,8,44,40,9,19,7,3,15,25,2,37,18,15,9,5, + 8,32,10,5,18,4,7,46,20,17,23,4,11,16,18,31,11,3,11,1,14,1,25,4,27,13,13,39, + 14,6,6,35,6,16,13,11,122,21,15,20,24,10,5,152,15,39,5,20,16,9,14,7,53,6,3,8, + 19,63,32,6,2,3,20,1,19,5,13,42,15,4,6,68,31,46,11,38,10,24,5,5,8,9,12,3,35, + 46,26,16,2,8,4,74,16,44,4,5,1,16,4,14,23,16,69,15,42,31,14,7,7,6,97,14,40,1, + 8,7,34,9,39,19,13,15,10,21,18,10,5,15,38,7,5,12,7,20,15,4,11,6,14,5,17,7,39, + 35,36,18,20,26,22,4,2,36,21,64,0,5,9,10,6,4,1,7,3,1,3,3,4,10,20,90,2,22,48, + 16,23,2,33,40,1,21,21,17,20,8,8,12,4,83,14,48,4,21,3,9,27,5,11,40,15,9,3,16, + 17,9,11,4,24,31,17,3,4,2,11,1,8,4,8,6,41,17,4,13,3,7,17,8,27,5,13,6,10,7,13, + 12,18,13,60,18,3,8,1,12,125,2,7,16,2,11,2,4,7,26,5,9,14,14,16,8,14,7,14,6,9, + 13,9,6,4,26,35,49,36,55,3,9,6,40,26,23,31,19,41,2,10,31,6,54,5,69,16,7,8,16, + 1,5,7,4,22,7,7,5,4,48,11,13,3,98,4,11,19,4,2,14,7,34,7,10,3,2,12,7,6,2,5,118 + }, +}; + +uint8_t data[1<<15]; +string data_str; +void init_data() { + static bool initted = false; + if (initted) return; + initted = true; + + // Repeatability. Kind of. + std::srand(42); + for (unsigned char & i : data) { + i = (uint8_t)rand(); + } + + data_str.assign((char*)data, sizeof(data)); +} + + +BOOST_AUTO_TEST_SUITE( TBufferBaseTest ) + +BOOST_AUTO_TEST_CASE( test_MemoryBuffer_Write_GetBuffer ) { + init_data(); + + for (auto & d1 : dist) { + TMemoryBuffer buffer(16); + int offset = 0; + int index = 0; + + while (offset < 1<<15) { + buffer.write(&data[offset], d1[index]); + offset += d1[index]; + index++; + } + + string output = buffer.getBufferAsString(); + BOOST_CHECK_EQUAL(data_str, output); + } +} + +BOOST_AUTO_TEST_CASE( test_MemoryBuffer_Write_Read ) { + init_data(); + + for (auto & d1 : dist) { + for (auto & d2 : dist) { + TMemoryBuffer buffer(16); + uint8_t data_out[1<<15]; + int offset; + int index; + + offset = 0; + index = 0; + while (offset < 1<<15) { + buffer.write(&data[offset], d1[index]); + offset += d1[index]; + index++; + } + + offset = 0; + index = 0; + while (offset < 1<<15) { + unsigned int got = buffer.read(&data_out[offset], d2[index]); + BOOST_CHECK_EQUAL(got, d2[index]); + offset += d2[index]; + index++; + } + + BOOST_CHECK(!memcmp(data, data_out, sizeof(data))); + } + } +} + +BOOST_AUTO_TEST_CASE( test_MemoryBuffer_Write_ReadString ) { + init_data(); + + for (auto & d1 : dist) { + for (auto & d2 : dist) { + TMemoryBuffer buffer(16); + string output; + int offset; + int index; + + offset = 0; + index = 0; + while (offset < 1<<15) { + buffer.write(&data[offset], d1[index]); + offset += d1[index]; + index++; + } + + offset = 0; + index = 0; + while (offset < 1<<15) { + unsigned int got = buffer.readAppendToString(output, d2[index]); + BOOST_CHECK_EQUAL(got, d2[index]); + offset += d2[index]; + index++; + } + + BOOST_CHECK_EQUAL(output, data_str); + } + } +} + +BOOST_AUTO_TEST_CASE( test_MemoryBuffer_Write_Read_Multi1 ) { + init_data(); + + // Do shorter writes and reads so we don't align to power-of-two boundaries. + + for (auto & d1 : dist) { + for (auto & d2 : dist) { + TMemoryBuffer buffer(16); + uint8_t data_out[1<<15]; + int offset; + int index; + + for (int iter = 0; iter < 6; iter++) { + offset = 0; + index = 0; + while (offset < (1<<15)-42) { + buffer.write(&data[offset], d1[index]); + offset += d1[index]; + index++; + } + + offset = 0; + index = 0; + while (offset < (1<<15)-42) { + buffer.read(&data_out[offset], d2[index]); + offset += d2[index]; + index++; + } + + BOOST_CHECK(!memcmp(data, data_out, (1<<15)-42)); + + // Pull out the extra data. + buffer.read(data_out, 42); + } + } + } +} + +BOOST_AUTO_TEST_CASE( test_MemoryBuffer_Write_Read_Multi2 ) { + init_data(); + + // Do shorter writes and reads so we don't align to power-of-two boundaries. + // Pull the buffer out of the loop so its state gets worked harder. + TMemoryBuffer buffer(16); + + for (auto & d1 : dist) { + for (auto & d2 : dist) { + uint8_t data_out[1<<15]; + int offset; + int index; + + for (int iter = 0; iter < 6; iter++) { + offset = 0; + index = 0; + while (offset < (1<<15)-42) { + buffer.write(&data[offset], d1[index]); + offset += d1[index]; + index++; + } + + offset = 0; + index = 0; + while (offset < (1<<15)-42) { + buffer.read(&data_out[offset], d2[index]); + offset += d2[index]; + index++; + } + + BOOST_CHECK(!memcmp(data, data_out, (1<<15)-42)); + + // Pull out the extra data. + buffer.read(data_out, 42); + } + } + } +} + +BOOST_AUTO_TEST_CASE( test_MemoryBuffer_Write_Read_Incomplete ) { + init_data(); + + // Do shorter writes and reads so we don't align to power-of-two boundaries. + // Pull the buffer out of the loop so its state gets worked harder. + + for (auto & d1 : dist) { + for (auto & d2 : dist) { + TMemoryBuffer buffer(16); + uint8_t data_out[1<<13]; + + int write_offset = 0; + int write_index = 0; + unsigned int to_write = (1<<14)-42; + while (to_write > 0) { + int write_amt = (std::min)(d1[write_index], to_write); + buffer.write(&data[write_offset], write_amt); + write_offset += write_amt; + write_index++; + to_write -= write_amt; + } + + int read_offset = 0; + int read_index = 0; + unsigned int to_read = (1<<13)-42; + while (to_read > 0) { + int read_amt = (std::min)(d2[read_index], to_read); + int got = buffer.read(&data_out[read_offset], read_amt); + BOOST_CHECK_EQUAL(got, read_amt); + read_offset += read_amt; + read_index++; + to_read -= read_amt; + } + + BOOST_CHECK(!memcmp(data, data_out, (1<<13)-42)); + + int second_offset = write_offset; + int second_index = write_index-1; + unsigned int to_second = (1<<14)+42; + while (to_second > 0) { + int second_amt = (std::min)(d1[second_index], to_second); + //printf("%d\n", second_amt); + buffer.write(&data[second_offset], second_amt); + second_offset += second_amt; + second_index++; + to_second -= second_amt; + } + + string output = buffer.getBufferAsString(); + BOOST_CHECK_EQUAL(data_str.substr((1<<13)-42), output); + } + } +} + +BOOST_AUTO_TEST_CASE( test_BufferedTransport_Write ) { + init_data(); + + int sizes[] = { + 12, 15, 16, 17, 20, + 501, 512, 523, + 2000, 2048, 2096, + 1<<14, 1<<17, + }; + + for (int size : sizes) { + for (auto & d1 : dist) { + shared_ptr buffer(new TMemoryBuffer(16)); + TBufferedTransport trans(buffer, size); + + int offset = 0; + int index = 0; + while (offset < 1<<15) { + trans.write(&data[offset], d1[index]); + offset += d1[index]; + index++; + } + trans.flush(); + + string output = buffer->getBufferAsString(); + BOOST_CHECK_EQUAL(data_str, output); + } + } +} + +BOOST_AUTO_TEST_CASE( test_BufferedTransport_Read_Full ) { + init_data(); + + int sizes[] = { + 12, 15, 16, 17, 20, + 501, 512, 523, + 2000, 2048, 2096, + 1<<14, 1<<17, + }; + + for (int size : sizes) { + for (auto & d1 : dist) { + shared_ptr buffer(new TMemoryBuffer(data, sizeof(data))); + TBufferedTransport trans(buffer, size); + uint8_t data_out[1<<15]; + + int offset = 0; + int index = 0; + while (offset < 1<<15) { + // Note: this doesn't work with "read" because TBufferedTransport + // doesn't try loop over reads, so we get short reads. We don't + // check the return value, so that messes us up. + trans.readAll(&data_out[offset], d1[index]); + offset += d1[index]; + index++; + } + + BOOST_CHECK(!memcmp(data, data_out, sizeof(data))); + } + } +} + +BOOST_AUTO_TEST_CASE( test_BufferedTransport_Read_Short ) { + init_data(); + + int sizes[] = { + 12, 15, 16, 17, 20, + 501, 512, 523, + 2000, 2048, 2096, + 1<<14, 1<<17, + }; + + for (int size : sizes) { + for (auto & d1 : dist) { + shared_ptr buffer(new TMemoryBuffer(data, sizeof(data))); + shared_ptr tshort(new TShortReadTransport(buffer, 0.125)); + TBufferedTransport trans(buffer, size); + uint8_t data_out[1<<15]; + + int offset = 0; + int index = 0; + while (offset < 1<<15) { + // Note: this doesn't work with "read" because TBufferedTransport + // doesn't try loop over reads, so we get short reads. We don't + // check the return value, so that messes us up. + trans.readAll(&data_out[offset], d1[index]); + offset += d1[index]; + index++; + } + + BOOST_CHECK(!memcmp(data, data_out, sizeof(data))); + } + } +} + +BOOST_AUTO_TEST_CASE( test_FramedTransport_Write ) { + init_data(); + + int sizes[] = { + 12, 15, 16, 17, 20, + 501, 512, 523, + 2000, 2048, 2096, + 1<<14, 1<<17, + }; + + for (int size : sizes) { + for (auto & d1 : dist) { + shared_ptr buffer(new TMemoryBuffer(16)); + TFramedTransport trans(buffer, size); + + int offset = 0; + int index = 0; + while (offset < 1<<15) { + trans.write(&data[offset], d1[index]); + offset += d1[index]; + index++; + } + trans.flush(); + + int32_t frame_size = -1; + buffer->read(reinterpret_cast(&frame_size), sizeof(frame_size)); + frame_size = (int32_t)ntohl((uint32_t)frame_size); + BOOST_CHECK_EQUAL(frame_size, 1<<15); + BOOST_CHECK_EQUAL(data_str.size(), (unsigned int)frame_size); + string output = buffer->getBufferAsString(); + BOOST_CHECK_EQUAL(data_str, output); + } + } +} + +BOOST_AUTO_TEST_CASE( test_FramedTransport_Read ) { + init_data(); + + for (auto & d1 : dist) { + uint8_t data_out[1<<15]; + shared_ptr buffer(new TMemoryBuffer()); + TFramedTransport trans(buffer); + int32_t length = sizeof(data); + length = (int32_t)htonl((uint32_t)length); + buffer->write(reinterpret_cast(&length), sizeof(length)); + buffer->write(data, sizeof(data)); + + int offset = 0; + int index = 0; + while (offset < 1<<15) { + // This should work with read because we have one huge frame. + trans.read(&data_out[offset], d1[index]); + offset += d1[index]; + index++; + } + + BOOST_CHECK(!memcmp(data, data_out, sizeof(data))); + } +} + +BOOST_AUTO_TEST_CASE( test_FramedTransport_Write_Read ) { + init_data(); + + int sizes[] = { + 12, 15, 16, 17, 20, + 501, 512, 523, + 2000, 2048, 2096, + 1<<14, 1<<17, + }; + + int probs[] = { 1, 2, 4, 8, 16, 32, }; + + for (int size : sizes) { + for (int prob : probs) { + for (auto & d1 : dist) { + shared_ptr buffer(new TMemoryBuffer(16)); + TFramedTransport trans(buffer, size); + std::vector data_out(1<<17, 0); + std::vector flush_sizes; + + int write_offset = 0; + int write_index = 0; + int flush_size = 0; + while (write_offset < 1<<15) { + trans.write(&data[write_offset], d1[write_index]); + write_offset += d1[write_index]; + flush_size += d1[write_index]; + write_index++; + if (flush_size > 0 && rand()%prob == 0) { + flush_sizes.push_back(flush_size); + flush_size = 0; + trans.flush(); + } + } + if (flush_size != 0) { + flush_sizes.push_back(flush_size); + flush_size = 0; + trans.flush(); + } + + int read_offset = 0; + int read_index = 0; + + for (int fsize : flush_sizes) { + // We are exploiting an implementation detail of TFramedTransport. + // The read buffer starts empty and it will never do more than one + // readFrame per read, so we should always get exactly one frame. + int got = trans.read(&data_out[read_offset], 1<<15); + BOOST_CHECK_EQUAL(got, fsize); + read_offset += got; + read_index++; + } + + BOOST_CHECK_EQUAL((unsigned int)read_offset, sizeof(data)); + BOOST_CHECK(!memcmp(data, &data_out[0], sizeof(data))); + } + } + } +} + +BOOST_AUTO_TEST_CASE( test_FramedTransport_Empty_Flush ) { + init_data(); + + string output1("\x00\x00\x00\x01""a", 5); + string output2("\x00\x00\x00\x01""a\x00\x00\x00\x02""bc", 11); + + shared_ptr buffer(new TMemoryBuffer()); + TFramedTransport trans(buffer); + + BOOST_CHECK_EQUAL(buffer->getBufferAsString(), ""); + trans.flush(); + BOOST_CHECK_EQUAL(buffer->getBufferAsString(), ""); + trans.flush(); + trans.flush(); + BOOST_CHECK_EQUAL(buffer->getBufferAsString(), ""); + trans.write((const uint8_t*)"a", 1); + BOOST_CHECK_EQUAL(buffer->getBufferAsString(), ""); + trans.flush(); + BOOST_CHECK_EQUAL(buffer->getBufferAsString(), output1); + trans.flush(); + trans.flush(); + BOOST_CHECK_EQUAL(buffer->getBufferAsString(), output1); + trans.write((const uint8_t*)"bc", 2); + BOOST_CHECK_EQUAL(buffer->getBufferAsString(), output1); + trans.flush(); + BOOST_CHECK_EQUAL(buffer->getBufferAsString(), output2); + trans.flush(); + trans.flush(); + BOOST_CHECK_EQUAL(buffer->getBufferAsString(), output2); +} + +BOOST_AUTO_TEST_SUITE_END() + diff --git a/src/jaegertracing/thrift/lib/cpp/test/TFDTransportTest.cpp b/src/jaegertracing/thrift/lib/cpp/test/TFDTransportTest.cpp new file mode 100644 index 000000000..0ba035a5b --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/test/TFDTransportTest.cpp @@ -0,0 +1,50 @@ +/* + * 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. + */ + +#include +#include +#include +#include + +#define BOOST_TEST_MODULE TFDTransportTest +#include + +// Disabled on MSVC because the RTL asserts on an invalid file descriptor +// in both debug and release mode; at least in MSVCR100 (Visual Studio 2010) +#if !defined(WIN32) + +using apache::thrift::transport::TTransportException; +using apache::thrift::transport::TFDTransport; + +BOOST_AUTO_TEST_CASE(test_tfdtransport_1) { + BOOST_CHECK_NO_THROW(TFDTransport t(256, TFDTransport::CLOSE_ON_DESTROY)); +} + +BOOST_AUTO_TEST_CASE(test_tfdtransport_2) { + TFDTransport t(256, TFDTransport::CLOSE_ON_DESTROY); + BOOST_CHECK_THROW(t.close(), TTransportException); +} + +#else + +BOOST_AUTO_TEST_CASE(test_tfdtransport_dummy) { + BOOST_CHECK(true); +} + +#endif diff --git a/src/jaegertracing/thrift/lib/cpp/test/TFileTransportTest.cpp b/src/jaegertracing/thrift/lib/cpp/test/TFileTransportTest.cpp new file mode 100644 index 000000000..21c1f3b33 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/test/TFileTransportTest.cpp @@ -0,0 +1,404 @@ +/* + * 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. + */ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE // needed for getopt_long +#endif + +#include + +#ifdef HAVE_SYS_TIME_H +#include +#endif +#include +#include + +#include + +#ifdef __MINGW32__ + #include + #include + #include + #include + #include +#endif + +using namespace apache::thrift::transport; + +/************************************************************************** + * Global state + **************************************************************************/ + +static const char* tmp_dir = "/tmp"; + +class FsyncLog; +FsyncLog* fsync_log; + +/************************************************************************** + * Helper code + **************************************************************************/ + +/** + * Class to record calls to fsync + */ +class FsyncLog { +public: + struct FsyncCall { + struct timeval time; + int fd; + }; + typedef std::list CallList; + + FsyncLog() = default; + + void fsync(int fd) { + (void)fd; + FsyncCall call; + THRIFT_GETTIMEOFDAY(&call.time, nullptr); + calls_.push_back(call); + } + + const CallList* getCalls() const { return &calls_; } + +private: + CallList calls_; +}; + +/** + * Helper class to clean up temporary files + */ +class TempFile { +public: + TempFile(const char* directory, const char* prefix) { + #ifdef __MINGW32__ + ((void)directory); + size_t path_len = strlen(prefix) + 8; + path_ = new char[path_len]; + snprintf(path_, path_len, "%sXXXXXX", prefix); + if (_mktemp_s(path_,path_len) == 0) { + fd_ = open(path_,O_CREAT | O_RDWR | O_BINARY,S_IREAD | S_IWRITE); + if (fd_ < 0) { + throw apache::thrift::TException("_mktemp_s() failed"); + } + } else { + throw apache::thrift::TException("_mktemp_s() failed"); + } + #else + size_t path_len = strlen(directory) + strlen(prefix) + 8; + path_ = new char[path_len]; + snprintf(path_, path_len, "%s/%sXXXXXX", directory, prefix); + + fd_ = mkstemp(path_); + if (fd_ < 0) { + throw apache::thrift::TException("mkstemp() failed"); + } + #endif + } + + ~TempFile() { + unlink(); + close(); + } + + const char* getPath() const { return path_; } + + int getFD() const { return fd_; } + + void unlink() { + if (path_) { + ::unlink(path_); + delete[] path_; + path_ = nullptr; + } + } + + void close() { + if (fd_ < 0) { + return; + } + + ::close(fd_); + fd_ = -1; + } + +private: + char* path_; + int fd_; +}; + +// Use our own version of fsync() for testing. +// This returns immediately, so timing in test_destructor() isn't affected by +// waiting on the actual filesystem. +extern "C" int fsync(int fd) { + if (fsync_log) { + fsync_log->fsync(fd); + } + return 0; +} + +int time_diff(const struct timeval* t1, const struct timeval* t2) { + return (t2->tv_usec - t1->tv_usec) + (t2->tv_sec - t1->tv_sec) * 1000000; +} + +/************************************************************************** + * Test cases + **************************************************************************/ + +/** + * Make sure the TFileTransport destructor exits "quickly". + * + * Previous versions had a bug causing the writer thread not to exit + * right away. + * + * It's kind of lame that we just check to see how long the destructor takes in + * wall-clock time. This could result in false failures on slower systems, or + * on heavily loaded machines. + */ +BOOST_AUTO_TEST_CASE(test_destructor) { + TempFile f(tmp_dir, "thrift.TFileTransportTest."); + + unsigned int const NUM_ITERATIONS = 1000; + + unsigned int num_over = 0; + for (unsigned int n = 0; n < NUM_ITERATIONS; ++n) { + BOOST_CHECK_EQUAL(0, ftruncate(f.getFD(), 0)); + + TFileTransport* transport = new TFileTransport(f.getPath()); + + // write something so that the writer thread gets started + transport->write(reinterpret_cast("foo"), 3); + + // Every other iteration, also call flush(), just in case that potentially + // has any effect on how the writer thread wakes up. + if (n & 0x1) { + transport->flush(); + } + + /* + * Time the call to the destructor + */ + struct timeval start; + struct timeval end; + + THRIFT_GETTIMEOFDAY(&start, nullptr); + delete transport; + THRIFT_GETTIMEOFDAY(&end, nullptr); + + int delta = time_diff(&start, &end); + + // If any attempt takes more than 500ms, treat that as a failure. + // Treat this as a fatal failure, so we'll return now instead of + // looping over a very slow operation. + BOOST_WARN( delta < 500000 ); + + // Normally, it takes less than 100ms on my dev box. + // However, if the box is heavily loaded, some of the test runs + // take longer, since we're just waiting for our turn on the CPU. + if (delta > 100000) { + ++num_over; + } + } + + // Make sure fewer than 10% of the runs took longer than 1000us + BOOST_WARN(num_over < (NUM_ITERATIONS / 10)); +} + +/** + * Make sure setFlushMaxUs() is honored. + */ +void test_flush_max_us_impl(uint32_t flush_us, uint32_t write_us, uint32_t test_us) { + // TFileTransport only calls fsync() if data has been written, + // so make sure the write interval is smaller than the flush interval. + BOOST_WARN(write_us < flush_us); + + TempFile f(tmp_dir, "thrift.TFileTransportTest."); + + // Record calls to fsync() + FsyncLog log; + fsync_log = &log; + + TFileTransport* transport = new TFileTransport(f.getPath()); + // Don't flush because of # of bytes written + transport->setFlushMaxBytes(0xffffffff); + uint8_t buf[] = "a"; + uint32_t buflen = sizeof(buf); + + // Set the flush interval + transport->setFlushMaxUs(flush_us); + + // Make one call to write, to start the writer thread now. + // (If we just let the thread get created during our test loop, + // the thread creation sometimes takes long enough to make the first + // fsync interval fail the check.) + transport->write(buf, buflen); + + // Add one entry to the fsync log, just to mark the start time + log.fsync(-1); + + // Loop doing write(), sleep(), ... + uint32_t total_time = 0; + while (true) { + transport->write(buf, buflen); + if (total_time > test_us) { + break; + } + usleep(write_us); + total_time += write_us; + } + + delete transport; + + // Stop logging new fsync() calls + fsync_log = nullptr; + + // Examine the fsync() log + // + // TFileTransport uses pthread_cond_timedwait(), which only has millisecond + // resolution. In my testing, it normally wakes up about 1 millisecond late. + // However, sometimes it takes a bit longer. Allow 5ms leeway. + int max_allowed_delta = flush_us + 5000; + + const FsyncLog::CallList* calls = log.getCalls(); + // We added 1 fsync call above. + // Make sure TFileTransport called fsync at least once + BOOST_WARN_GE(calls->size(), static_cast(1)); + + const struct timeval* prev_time = nullptr; + for (const auto & call : *calls) { + if (prev_time) { + int delta = time_diff(prev_time, &call.time); + BOOST_WARN( delta < max_allowed_delta ); + } + prev_time = &call.time; + } +} + +BOOST_AUTO_TEST_CASE(test_flush_max_us1) { + // fsync every 10ms, write every 5ms, for 500ms + test_flush_max_us_impl(10000, 5000, 500000); +} + +BOOST_AUTO_TEST_CASE(test_flush_max_us2) { + // fsync every 50ms, write every 20ms, for 500ms + test_flush_max_us_impl(50000, 20000, 500000); +} + +BOOST_AUTO_TEST_CASE(test_flush_max_us3) { + // fsync every 400ms, write every 300ms, for 1s + test_flush_max_us_impl(400000, 300000, 1000000); +} + +/** + * Make sure flush() is fast when there is nothing to do. + * + * TFileTransport used to have a bug where flush() would wait for the fsync + * timeout to expire. + */ +BOOST_AUTO_TEST_CASE(test_noop_flush) { + TempFile f(tmp_dir, "thrift.TFileTransportTest."); + TFileTransport transport(f.getPath()); + + // Write something to start the writer thread. + uint8_t buf[] = "a"; + transport.write(buf, 1); + + struct timeval start; + THRIFT_GETTIMEOFDAY(&start, nullptr); + + for (unsigned int n = 0; n < 10; ++n) { + transport.flush(); + + struct timeval now; + THRIFT_GETTIMEOFDAY(&now, nullptr); + + // Fail if at any point we've been running for longer than half a second. + // (With the buggy code, TFileTransport used to take 3 seconds per flush()) + // + // Use a fatal fail so we break out early, rather than continuing to make + // many more slow flush() calls. + int delta = time_diff(&start, &now); + BOOST_WARN( delta < 2000000 ); + } +} + +/************************************************************************** + * General Initialization + **************************************************************************/ + +void print_usage(FILE* f, const char* argv0) { + fprintf(f, "Usage: %s [boost_options] [options]\n", argv0); + fprintf(f, "Options:\n"); + fprintf(f, " --tmp-dir=DIR, -t DIR\n"); + fprintf(f, " --help\n"); +} + +void parse_args(int argc, char* argv[]) { + struct option long_opts[] + = {{"help", false, nullptr, 'h'}, {"tmp-dir", true, nullptr, 't'}, {nullptr, 0, nullptr, 0}}; + + while (true) { + optopt = 1; + int optchar = getopt_long(argc, argv, "ht:", long_opts, nullptr); + if (optchar == -1) { + break; + } + + switch (optchar) { + case 't': + tmp_dir = optarg; + break; + case 'h': + print_usage(stdout, argv[0]); + exit(0); + case '?': + exit(1); + default: + // Only happens if someone adds another option to the optarg string, + // but doesn't update the switch statement to handle it. + fprintf(stderr, "unknown option \"-%c\"\n", optchar); + exit(1); + } + } +} + +#ifdef BOOST_TEST_DYN_LINK +static int myArgc = 0; +static char **myArgv = nullptr; + +bool init_unit_test_suite() { + boost::unit_test::framework::master_test_suite().p_name.value = "TFileTransportTest"; + + // Parse arguments + parse_args(myArgc,myArgv); + return true; +} + +int main( int argc, char* argv[] ) { + myArgc = argc; + myArgv = argv; + return ::boost::unit_test::unit_test_main(&init_unit_test_suite,argc,argv); +} +#else +boost::unit_test::test_suite* init_unit_test_suite(int argc, char* argv[]) { + boost::unit_test::framework::master_test_suite().p_name.value = "TFileTransportTest"; + + // Parse arguments + parse_args(argc, argv); + return NULL; +} +#endif diff --git a/src/jaegertracing/thrift/lib/cpp/test/TMemoryBufferTest.cpp b/src/jaegertracing/thrift/lib/cpp/test/TMemoryBufferTest.cpp new file mode 100644 index 000000000..42f97112b --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/test/TMemoryBufferTest.cpp @@ -0,0 +1,160 @@ +/* + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "gen-cpp/ThriftTest_types.h" + +BOOST_AUTO_TEST_SUITE(TMemoryBufferTest) + +using apache::thrift::protocol::TBinaryProtocol; +using apache::thrift::transport::TMemoryBuffer; +using apache::thrift::transport::TTransportException; +using std::shared_ptr; +using std::cout; +using std::endl; +using std::string; + +BOOST_AUTO_TEST_CASE(test_read_write_grow) { + // Added to test the fix for THRIFT-1248 + TMemoryBuffer uut; + const int maxSize = 65536; + uint8_t verify[maxSize]; + std::vector buf; + buf.resize(maxSize); + + for (uint32_t i = 0; i < maxSize; ++i) { + buf[i] = static_cast(i); + } + + for (uint32_t i = 1; i < maxSize; i *= 2) { + uut.write(&buf[0], i); + } + + for (uint32_t i = 1; i < maxSize; i *= 2) { + uut.read(verify, i); + BOOST_CHECK_EQUAL(0, ::memcmp(verify, &buf[0], i)); + } +} + +BOOST_AUTO_TEST_CASE(test_roundtrip) { + shared_ptr strBuffer(new TMemoryBuffer()); + shared_ptr binaryProtcol(new TBinaryProtocol(strBuffer)); + + thrift::test::Xtruct a; + a.i32_thing = 10; + a.i64_thing = 30; + a.string_thing = "holla back a"; + + a.write(binaryProtcol.get()); + std::string serialized = strBuffer->getBufferAsString(); + + shared_ptr strBuffer2(new TMemoryBuffer()); + shared_ptr binaryProtcol2(new TBinaryProtocol(strBuffer2)); + + strBuffer2->resetBuffer((uint8_t*)serialized.data(), static_cast(serialized.length())); + thrift::test::Xtruct a2; + a2.read(binaryProtcol2.get()); + + BOOST_CHECK(a == a2); +} + +BOOST_AUTO_TEST_CASE(test_readAppendToString) { + string str1 = "abcd1234"; + TMemoryBuffer buf((uint8_t*)str1.data(), + static_cast(str1.length()), + TMemoryBuffer::COPY); + + string str3 = "wxyz", str4 = "6789"; + buf.readAppendToString(str3, 4); + buf.readAppendToString(str4, INT_MAX); + + BOOST_CHECK(str3 == "wxyzabcd"); + BOOST_CHECK(str4 == "67891234"); +} + +BOOST_AUTO_TEST_CASE(test_exceptions) { + char data[] = "foo\0bar"; + + TMemoryBuffer buf1((uint8_t*)data, 7, TMemoryBuffer::OBSERVE); + string str = buf1.getBufferAsString(); + BOOST_CHECK(str.length() == 7); + + buf1.resetBuffer(); + + BOOST_CHECK_THROW(buf1.write((const uint8_t*)"foo", 3), TTransportException); + + TMemoryBuffer buf2((uint8_t*)data, 7, TMemoryBuffer::COPY); + BOOST_CHECK_NO_THROW(buf2.write((const uint8_t*)"bar", 3)); +} + +BOOST_AUTO_TEST_CASE(test_default_maximum_buffer_size) +{ + BOOST_CHECK_EQUAL((std::numeric_limits::max)(), TMemoryBuffer().getMaxBufferSize()); +} + +BOOST_AUTO_TEST_CASE(test_default_buffer_size) +{ + BOOST_CHECK_EQUAL(1024, TMemoryBuffer().getBufferSize()); +} + +BOOST_AUTO_TEST_CASE(test_error_set_max_buffer_size_too_small) +{ + TMemoryBuffer buf; + BOOST_CHECK_THROW(buf.setMaxBufferSize(buf.getBufferSize() - 1), TTransportException); +} + +BOOST_AUTO_TEST_CASE(test_maximum_buffer_size) +{ + TMemoryBuffer buf; + buf.setMaxBufferSize(8192); + std::vector small_buff(1); + + for (size_t i = 0; i < 8192; ++i) + { + buf.write(&small_buff[0], 1); + } + + BOOST_CHECK_THROW(buf.write(&small_buff[0], 1), TTransportException); +} + +BOOST_AUTO_TEST_CASE(test_memory_buffer_to_get_sizeof_objects) +{ + // This is a demonstration of how to use TMemoryBuffer to determine + // the serialized size of a thrift object in the Binary protocol. + // See THRIFT-3480 + + shared_ptr memBuffer(new TMemoryBuffer()); + shared_ptr binaryProtcol(new TBinaryProtocol(memBuffer)); + + thrift::test::Xtruct object; + object.i32_thing = 10; + object.i64_thing = 30; + object.string_thing = "who's your daddy?"; + + uint32_t size = object.write(binaryProtcol.get()); + BOOST_CHECK_EQUAL(47, size); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/jaegertracing/thrift/lib/cpp/test/TNonblockingSSLServerTest.cpp b/src/jaegertracing/thrift/lib/cpp/test/TNonblockingSSLServerTest.cpp new file mode 100644 index 000000000..dc40c1257 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/test/TNonblockingSSLServerTest.cpp @@ -0,0 +1,286 @@ +/* + * 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. + */ + +#define BOOST_TEST_MODULE TNonblockingSSLServerTest +#include +#include +#include + +#include "thrift/server/TNonblockingServer.h" +#include "thrift/transport/TSSLSocket.h" +#include "thrift/transport/TNonblockingSSLServerSocket.h" + +#include "gen-cpp/ParentService.h" + +#include + +using namespace apache::thrift; +using apache::thrift::concurrency::Guard; +using apache::thrift::concurrency::Monitor; +using apache::thrift::concurrency::Mutex; +using apache::thrift::server::TServerEventHandler; +using apache::thrift::transport::TSSLSocketFactory; +using apache::thrift::transport::TSSLSocket; + +struct Handler : public test::ParentServiceIf { + void addString(const std::string& s) override { strings_.push_back(s); } + void getStrings(std::vector& _return) override { _return = strings_; } + std::vector strings_; + + // dummy overrides not used in this test + int32_t incrementGeneration() override { return 0; } + int32_t getGeneration() override { return 0; } + void getDataWait(std::string&, const int32_t) override {} + void onewayWait() override {} + void exceptionWait(const std::string&) override {} + void unexpectedExceptionWait(const std::string&) override {} +}; + +boost::filesystem::path keyDir; +boost::filesystem::path certFile(const std::string& filename) +{ + return keyDir / filename; +} + +struct GlobalFixtureSSL +{ + GlobalFixtureSSL() + { + using namespace boost::unit_test::framework; + for (int i = 0; i < master_test_suite().argc; ++i) + { + BOOST_TEST_MESSAGE(boost::format("argv[%1%] = \"%2%\"") % i % master_test_suite().argv[i]); + } + +#ifdef __linux__ + // OpenSSL calls send() without MSG_NOSIGPIPE so writing to a socket that has + // disconnected can cause a SIGPIPE signal... + signal(SIGPIPE, SIG_IGN); +#endif + + TSSLSocketFactory::setManualOpenSSLInitialization(true); + apache::thrift::transport::initializeOpenSSL(); + + keyDir = boost::filesystem::current_path().parent_path().parent_path().parent_path() / "test" / "keys"; + if (!boost::filesystem::exists(certFile("server.crt"))) + { + keyDir = boost::filesystem::path(master_test_suite().argv[master_test_suite().argc - 1]); + if (!boost::filesystem::exists(certFile("server.crt"))) + { + throw std::invalid_argument("The last argument to this test must be the directory containing the test certificate(s)."); + } + } + } + + virtual ~GlobalFixtureSSL() + { + apache::thrift::transport::cleanupOpenSSL(); +#ifdef __linux__ + signal(SIGPIPE, SIG_DFL); +#endif + } +}; + +#if (BOOST_VERSION >= 105900) +BOOST_GLOBAL_FIXTURE(GlobalFixtureSSL); +#else +BOOST_GLOBAL_FIXTURE(GlobalFixtureSSL) +#endif + +std::shared_ptr createServerSocketFactory() { + std::shared_ptr pServerSocketFactory; + + pServerSocketFactory.reset(new TSSLSocketFactory()); + pServerSocketFactory->ciphers("ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH"); + pServerSocketFactory->loadCertificate(certFile("server.crt").string().c_str()); + pServerSocketFactory->loadPrivateKey(certFile("server.key").string().c_str()); + pServerSocketFactory->server(true); + return pServerSocketFactory; +} + +std::shared_ptr createClientSocketFactory() { + std::shared_ptr pClientSocketFactory; + + pClientSocketFactory.reset(new TSSLSocketFactory()); + pClientSocketFactory->authenticate(true); + pClientSocketFactory->loadCertificate(certFile("client.crt").string().c_str()); + pClientSocketFactory->loadPrivateKey(certFile("client.key").string().c_str()); + pClientSocketFactory->loadTrustedCertificates(certFile("CA.pem").string().c_str()); + return pClientSocketFactory; +} + +class Fixture { +private: + struct ListenEventHandler : public TServerEventHandler { + public: + ListenEventHandler(Mutex* mutex) : listenMonitor_(mutex), ready_(false) {} + + void preServe() override /* override */ { + Guard g(listenMonitor_.mutex()); + ready_ = true; + listenMonitor_.notify(); + } + + Monitor listenMonitor_; + bool ready_; + }; + + struct Runner : public apache::thrift::concurrency::Runnable { + int port; + std::shared_ptr userEventBase; + std::shared_ptr processor; + std::shared_ptr server; + std::shared_ptr listenHandler; + std::shared_ptr pServerSocketFactory; + std::shared_ptr socket; + Mutex mutex_; + + Runner():port(0) { + listenHandler.reset(new ListenEventHandler(&mutex_)); + } + + void run() override { + // When binding to explicit port, allow retrying to workaround bind failures on ports in use + int retryCount = port ? 10 : 0; + pServerSocketFactory = createServerSocketFactory(); + startServer(retryCount); + } + + void readyBarrier() { + // block until server is listening and ready to accept connections + Guard g(mutex_); + while (!listenHandler->ready_) { + listenHandler->listenMonitor_.wait(); + } + } + private: + void startServer(int retry_count) { + try { + socket.reset(new transport::TNonblockingSSLServerSocket(port, pServerSocketFactory)); + server.reset(new server::TNonblockingServer(processor, socket)); + server->setServerEventHandler(listenHandler); + server->setNumIOThreads(1); + if (userEventBase) { + server->registerEvents(userEventBase.get()); + } + server->serve(); + } catch (const transport::TTransportException&) { + if (retry_count > 0) { + ++port; + startServer(retry_count - 1); + } else { + throw; + } + } + } + }; + + struct EventDeleter { + void operator()(event_base* p) { event_base_free(p); } + }; + +protected: + Fixture() : processor(new test::ParentServiceProcessor(std::make_shared())) {} + + ~Fixture() { + if (server) { + server->stop(); + } + if (thread) { + thread->join(); + } + } + + void setEventBase(event_base* user_event_base) { + userEventBase_.reset(user_event_base, EventDeleter()); + } + + int startServer(int port) { + std::shared_ptr runner(new Runner); + runner->port = port; + runner->processor = processor; + runner->userEventBase = userEventBase_; + + std::unique_ptr threadFactory( + new apache::thrift::concurrency::ThreadFactory(false)); + thread = threadFactory->newThread(runner); + thread->start(); + runner->readyBarrier(); + + server = runner->server; + return runner->port; + } + + bool canCommunicate(int serverPort) { + std::shared_ptr pClientSocketFactory = createClientSocketFactory(); + std::shared_ptr socket = pClientSocketFactory->createSocket("localhost", serverPort); + socket->open(); + test::ParentServiceClient client(std::make_shared( + std::make_shared(socket))); + client.addString("foo"); + std::vector strings; + client.getStrings(strings); + return strings.size() == 1 && !(strings[0].compare("foo")); + } + +private: + std::shared_ptr userEventBase_; + std::shared_ptr processor; +protected: + std::shared_ptr server; +private: + std::shared_ptr thread; + +}; + +BOOST_AUTO_TEST_SUITE(TNonblockingSSLServerTest) + +BOOST_FIXTURE_TEST_CASE(get_specified_port, Fixture) { + int specified_port = startServer(12345); + BOOST_REQUIRE_GE(specified_port, 12345); + BOOST_REQUIRE_EQUAL(server->getListenPort(), specified_port); + BOOST_CHECK(canCommunicate(specified_port)); + + server->stop(); +} + +BOOST_FIXTURE_TEST_CASE(get_assigned_port, Fixture) { + int specified_port = startServer(0); + BOOST_REQUIRE_EQUAL(specified_port, 0); + int assigned_port = server->getListenPort(); + BOOST_REQUIRE_NE(assigned_port, 0); + BOOST_CHECK(canCommunicate(assigned_port)); + + server->stop(); +} + +BOOST_FIXTURE_TEST_CASE(provide_event_base, Fixture) { + event_base* eb = event_base_new(); + setEventBase(eb); + startServer(0); + + // assert that the server works + BOOST_CHECK(canCommunicate(server->getListenPort())); +#if LIBEVENT_VERSION_NUMBER > 0x02010400 + // also assert that the event_base is actually used when it's easy + BOOST_CHECK_GT(event_base_get_num_events(eb, EVENT_BASE_COUNT_ADDED), 0); +#endif +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/jaegertracing/thrift/lib/cpp/test/TNonblockingServerTest.cpp b/src/jaegertracing/thrift/lib/cpp/test/TNonblockingServerTest.cpp new file mode 100644 index 000000000..f9aab4cc1 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/test/TNonblockingServerTest.cpp @@ -0,0 +1,215 @@ +/* + * 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. + */ + +#define BOOST_TEST_MODULE TNonblockingServerTest +#include +#include + +#include "thrift/concurrency/Monitor.h" +#include "thrift/concurrency/Thread.h" +#include "thrift/server/TNonblockingServer.h" +#include "thrift/transport/TNonblockingServerSocket.h" + +#include "gen-cpp/ParentService.h" + +#include + +using apache::thrift::concurrency::Guard; +using apache::thrift::concurrency::Monitor; +using apache::thrift::concurrency::Mutex; +using apache::thrift::concurrency::ThreadFactory; +using apache::thrift::concurrency::Runnable; +using apache::thrift::concurrency::Thread; +using apache::thrift::concurrency::ThreadFactory; +using apache::thrift::server::TServerEventHandler; +using std::make_shared; +using std::shared_ptr; + +using namespace apache::thrift; + +struct Handler : public test::ParentServiceIf { + void addString(const std::string& s) override { strings_.push_back(s); } + void getStrings(std::vector& _return) override { _return = strings_; } + std::vector strings_; + + // dummy overrides not used in this test + int32_t incrementGeneration() override { return 0; } + int32_t getGeneration() override { return 0; } + void getDataWait(std::string&, const int32_t) override {} + void onewayWait() override {} + void exceptionWait(const std::string&) override {} + void unexpectedExceptionWait(const std::string&) override {} +}; + +class Fixture { +private: + struct ListenEventHandler : public TServerEventHandler { + public: + ListenEventHandler(Mutex* mutex) : listenMonitor_(mutex), ready_(false) {} + + void preServe() override /* override */ { + Guard g(listenMonitor_.mutex()); + ready_ = true; + listenMonitor_.notify(); + } + + Monitor listenMonitor_; + bool ready_; + }; + + struct Runner : public Runnable { + int port; + shared_ptr userEventBase; + shared_ptr processor; + shared_ptr server; + shared_ptr listenHandler; + shared_ptr socket; + Mutex mutex_; + + Runner() { + port = 0; + listenHandler.reset(new ListenEventHandler(&mutex_)); + } + + void run() override { + // When binding to explicit port, allow retrying to workaround bind failures on ports in use + int retryCount = port ? 10 : 0; + startServer(retryCount); + } + + void readyBarrier() { + // block until server is listening and ready to accept connections + Guard g(mutex_); + while (!listenHandler->ready_) { + listenHandler->listenMonitor_.wait(); + } + } + private: + void startServer(int retry_count) { + try { + socket.reset(new transport::TNonblockingServerSocket(port)); + server.reset(new server::TNonblockingServer(processor, socket)); + server->setServerEventHandler(listenHandler); + if (userEventBase) { + server->registerEvents(userEventBase.get()); + } + server->serve(); + } catch (const transport::TTransportException&) { + if (retry_count > 0) { + ++port; + startServer(retry_count - 1); + } else { + throw; + } + } + } + }; + + struct EventDeleter { + void operator()(event_base* p) { event_base_free(p); } + }; + +protected: + Fixture() : processor(new test::ParentServiceProcessor(make_shared())) {} + + ~Fixture() { + if (server) { + server->stop(); + } + if (thread) { + thread->join(); + } + } + + void setEventBase(event_base* user_event_base) { + userEventBase_.reset(user_event_base, EventDeleter()); + } + + int startServer(int port) { + shared_ptr runner(new Runner); + runner->port = port; + runner->processor = processor; + runner->userEventBase = userEventBase_; + + shared_ptr threadFactory( + new ThreadFactory(false)); + thread = threadFactory->newThread(runner); + thread->start(); + runner->readyBarrier(); + + server = runner->server; + return runner->port; + } + + bool canCommunicate(int serverPort) { + shared_ptr socket(new transport::TSocket("localhost", serverPort)); + socket->open(); + test::ParentServiceClient client(make_shared( + make_shared(socket))); + client.addString("foo"); + std::vector strings; + client.getStrings(strings); + return strings.size() == 1 && !(strings[0].compare("foo")); + } + +private: + shared_ptr userEventBase_; + shared_ptr processor; +protected: + shared_ptr server; +private: + shared_ptr thread; + +}; + +BOOST_AUTO_TEST_SUITE(TNonblockingServerTest) + +BOOST_FIXTURE_TEST_CASE(get_specified_port, Fixture) { + int specified_port = startServer(12345); + BOOST_REQUIRE_GE(specified_port, 12345); + BOOST_REQUIRE_EQUAL(server->getListenPort(), specified_port); + BOOST_CHECK(canCommunicate(specified_port)); + + server->stop(); +} + +BOOST_FIXTURE_TEST_CASE(get_assigned_port, Fixture) { + int specified_port = startServer(0); + BOOST_REQUIRE_EQUAL(specified_port, 0); + int assigned_port = server->getListenPort(); + BOOST_REQUIRE_NE(assigned_port, 0); + BOOST_CHECK(canCommunicate(assigned_port)); + + server->stop(); +} + +BOOST_FIXTURE_TEST_CASE(provide_event_base, Fixture) { + event_base* eb = event_base_new(); + setEventBase(eb); + startServer(0); + + // assert that the server works + BOOST_CHECK(canCommunicate(server->getListenPort())); +#if LIBEVENT_VERSION_NUMBER > 0x02010400 + // also assert that the event_base is actually used when it's easy + BOOST_CHECK_GT(event_base_get_num_events(eb, EVENT_BASE_COUNT_ADDED), 0); +#endif +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/jaegertracing/thrift/lib/cpp/test/TPipeInterruptTest.cpp b/src/jaegertracing/thrift/lib/cpp/test/TPipeInterruptTest.cpp new file mode 100644 index 000000000..2423f5646 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/test/TPipeInterruptTest.cpp @@ -0,0 +1,90 @@ +/* + * 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. + */ + +#ifdef _WIN32 + +#include +#include + +#include +#include +#include +#include +#include +#include + +using apache::thrift::transport::TPipeServer; +using apache::thrift::transport::TPipe; +using apache::thrift::transport::TTransport; +using apache::thrift::transport::TTransportException; +using namespace apache::thrift; + +BOOST_AUTO_TEST_SUITE(TPipeInterruptTest) + +// TODO: duplicate the test cases in TSocketInterruptTest for pipes, +// once pipes implement interruptChildren + +BOOST_AUTO_TEST_CASE(test_interrupt_before_accept) { + TPipeServer pipe1("TPipeInterruptTest"); + pipe1.listen(); + pipe1.interrupt(); + BOOST_CHECK_THROW(pipe1.accept(), TTransportException); +} + +static void acceptWorker(TPipeServer *pipe) { + try + { + for (;;) + { + std::shared_ptr temp = pipe->accept(); + } + } + catch (...) {/*just want to make sure nothing crashes*/ } +} + +static void interruptWorker(TPipeServer *pipe) { + boost::this_thread::sleep(boost::posix_time::milliseconds(10)); + pipe->interrupt(); +} + +BOOST_AUTO_TEST_CASE(stress_pipe_accept_interruption) { + int interruptIters = 10; + + for (int i = 0; i < interruptIters; ++i) + { + TPipeServer pipeServer("TPipeInterruptTest"); + pipeServer.listen(); + boost::thread acceptThread(std::bind(acceptWorker, &pipeServer)); + boost::thread interruptThread(std::bind(interruptWorker, &pipeServer)); + try + { + for (;;) + { + TPipe client("TPipeInterruptTest"); + client.setConnTimeout(1); + client.open(); + } + } catch (...) { /*just testing for crashes*/ } + interruptThread.join(); + acceptThread.join(); + } +} + +BOOST_AUTO_TEST_SUITE_END() +#endif diff --git a/src/jaegertracing/thrift/lib/cpp/test/TPipedTransportTest.cpp b/src/jaegertracing/thrift/lib/cpp/test/TPipedTransportTest.cpp new file mode 100644 index 000000000..f3091a487 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/test/TPipedTransportTest.cpp @@ -0,0 +1,53 @@ +/* + * 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. + */ + +#include +#include +#include +#include + +#define BOOST_TEST_MODULE TPipedTransportTest +#include + +using apache::thrift::transport::TTransportException; +using apache::thrift::transport::TPipedTransport; +using apache::thrift::transport::TMemoryBuffer; +using namespace apache::thrift; + +BOOST_AUTO_TEST_CASE(test_read_write) { + std::shared_ptr underlying(new TMemoryBuffer); + std::shared_ptr pipe(new TMemoryBuffer); + std::shared_ptr trans(new TPipedTransport(underlying, pipe)); + + uint8_t buffer[4]; + + underlying->write((uint8_t*)"abcd", 4); + trans->readAll(buffer, 2); + BOOST_CHECK(std::string((char*)buffer, 2) == "ab"); + trans->readEnd(); + BOOST_CHECK(pipe->getBufferAsString() == "ab"); + pipe->resetBuffer(); + underlying->write((uint8_t*)"ef", 2); + trans->readAll(buffer, 2); + BOOST_CHECK(std::string((char*)buffer, 2) == "cd"); + trans->readAll(buffer, 2); + BOOST_CHECK(std::string((char*)buffer, 2) == "ef"); + trans->readEnd(); + BOOST_CHECK(pipe->getBufferAsString() == "cdef"); +} diff --git a/src/jaegertracing/thrift/lib/cpp/test/TSSLSocketInterruptTest.cpp b/src/jaegertracing/thrift/lib/cpp/test/TSSLSocketInterruptTest.cpp new file mode 100644 index 000000000..33f686cef --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/test/TSSLSocketInterruptTest.cpp @@ -0,0 +1,282 @@ +/* + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef __linux__ +#include +#endif + +using apache::thrift::transport::TSSLServerSocket; +using apache::thrift::transport::TSSLSocket; +using apache::thrift::transport::TTransport; +using apache::thrift::transport::TTransportException; +using apache::thrift::transport::TSSLSocketFactory; + +using std::static_pointer_cast; +using std::shared_ptr; + +BOOST_AUTO_TEST_SUITE(TSSLSocketInterruptTest) + +boost::filesystem::path keyDir; +boost::filesystem::path certFile(const std::string& filename) +{ + return keyDir / filename; +} +boost::mutex gMutex; + +struct GlobalFixtureSSL +{ + GlobalFixtureSSL() + { + using namespace boost::unit_test::framework; + for (int i = 0; i < master_test_suite().argc; ++i) + { + BOOST_TEST_MESSAGE(boost::format("argv[%1%] = \"%2%\"") % i % master_test_suite().argv[i]); + } + +#ifdef __linux__ + // OpenSSL calls send() without MSG_NOSIGPIPE so writing to a socket that has + // disconnected can cause a SIGPIPE signal... + signal(SIGPIPE, SIG_IGN); +#endif + + TSSLSocketFactory::setManualOpenSSLInitialization(true); + apache::thrift::transport::initializeOpenSSL(); + + keyDir = boost::filesystem::current_path().parent_path().parent_path().parent_path() / "test" / "keys"; + if (!boost::filesystem::exists(certFile("server.crt"))) + { + keyDir = boost::filesystem::path(master_test_suite().argv[master_test_suite().argc - 1]); + if (!boost::filesystem::exists(certFile("server.crt"))) + { + throw std::invalid_argument("The last argument to this test must be the directory containing the test certificate(s)."); + } + } + } + + virtual ~GlobalFixtureSSL() + { + apache::thrift::transport::cleanupOpenSSL(); +#ifdef __linux__ + signal(SIGPIPE, SIG_DFL); +#endif + } +}; + +#if (BOOST_VERSION >= 105900) +BOOST_GLOBAL_FIXTURE(GlobalFixtureSSL); +#else +BOOST_GLOBAL_FIXTURE(GlobalFixtureSSL) +#endif + +void readerWorker(shared_ptr tt, uint32_t expectedResult) { + uint8_t buf[4]; + try { + tt->read(buf, 1); + BOOST_CHECK_EQUAL(expectedResult, tt->read(buf, 4)); + } catch (const TTransportException& tx) { + BOOST_CHECK_EQUAL(TTransportException::TIMED_OUT, tx.getType()); + } +} + +void readerWorkerMustThrow(shared_ptr tt) { + try { + uint8_t buf[400]; + tt->read(buf, 1); + tt->read(buf, 400); + BOOST_ERROR("should not have gotten here"); + } catch (const TTransportException& tx) { + BOOST_CHECK_EQUAL(TTransportException::INTERRUPTED, tx.getType()); + } +} + +shared_ptr createServerSocketFactory() { + shared_ptr pServerSocketFactory; + + pServerSocketFactory.reset(new TSSLSocketFactory()); + pServerSocketFactory->ciphers("ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH"); + pServerSocketFactory->loadCertificate(certFile("server.crt").string().c_str()); + pServerSocketFactory->loadPrivateKey(certFile("server.key").string().c_str()); + pServerSocketFactory->server(true); + return pServerSocketFactory; +} + +shared_ptr createClientSocketFactory() { + shared_ptr pClientSocketFactory; + + pClientSocketFactory.reset(new TSSLSocketFactory()); + pClientSocketFactory->authenticate(true); + pClientSocketFactory->loadCertificate(certFile("client.crt").string().c_str()); + pClientSocketFactory->loadPrivateKey(certFile("client.key").string().c_str()); + pClientSocketFactory->loadTrustedCertificates(certFile("CA.pem").string().c_str()); + return pClientSocketFactory; +} + +BOOST_AUTO_TEST_CASE(test_ssl_interruptable_child_read_while_handshaking) { + shared_ptr pServerSocketFactory = createServerSocketFactory(); + TSSLServerSocket sock1("localhost", 0, pServerSocketFactory); + sock1.listen(); + int port = sock1.getPort(); + shared_ptr pClientSocketFactory = createClientSocketFactory(); + shared_ptr clientSock = pClientSocketFactory->createSocket("localhost", port); + clientSock->open(); + shared_ptr accepted = sock1.accept(); + boost::thread readThread(std::bind(readerWorkerMustThrow, accepted)); + boost::this_thread::sleep(boost::posix_time::milliseconds(50)); + // readThread is practically guaranteed to be blocking now + sock1.interruptChildren(); + BOOST_CHECK_MESSAGE(readThread.try_join_for(boost::chrono::milliseconds(20)), + "server socket interruptChildren did not interrupt child read"); + clientSock->close(); + accepted->close(); + sock1.close(); +} + +BOOST_AUTO_TEST_CASE(test_ssl_interruptable_child_read) { + shared_ptr pServerSocketFactory = createServerSocketFactory(); + TSSLServerSocket sock1("localhost", 0, pServerSocketFactory); + sock1.listen(); + int port = sock1.getPort(); + shared_ptr pClientSocketFactory = createClientSocketFactory(); + shared_ptr clientSock = pClientSocketFactory->createSocket("localhost", port); + clientSock->open(); + shared_ptr accepted = sock1.accept(); + boost::thread readThread(std::bind(readerWorkerMustThrow, accepted)); + clientSock->write((const uint8_t*)"0", 1); + boost::this_thread::sleep(boost::posix_time::milliseconds(50)); + // readThread is practically guaranteed to be blocking now + sock1.interruptChildren(); + BOOST_CHECK_MESSAGE(readThread.try_join_for(boost::chrono::milliseconds(20)), + "server socket interruptChildren did not interrupt child read"); + accepted->close(); + clientSock->close(); + sock1.close(); +} + +BOOST_AUTO_TEST_CASE(test_ssl_non_interruptable_child_read) { + shared_ptr pServerSocketFactory = createServerSocketFactory(); + TSSLServerSocket sock1("localhost", 0, pServerSocketFactory); + sock1.setInterruptableChildren(false); // returns to pre-THRIFT-2441 behavior + sock1.listen(); + int port = sock1.getPort(); + shared_ptr pClientSocketFactory = createClientSocketFactory(); + shared_ptr clientSock = pClientSocketFactory->createSocket("localhost", port); + clientSock->open(); + shared_ptr accepted = sock1.accept(); + static_pointer_cast(accepted)->setRecvTimeout(1000); + boost::thread readThread(std::bind(readerWorker, accepted, 0)); + clientSock->write((const uint8_t*)"0", 1); + boost::this_thread::sleep(boost::posix_time::milliseconds(50)); + // readThread is practically guaranteed to be blocking here + sock1.interruptChildren(); + BOOST_CHECK_MESSAGE(!readThread.try_join_for(boost::chrono::milliseconds(200)), + "server socket interruptChildren interrupted child read"); + + // wait for receive timeout to kick in + readThread.join(); + accepted->close(); + clientSock->close(); + sock1.close(); +} + +BOOST_AUTO_TEST_CASE(test_ssl_cannot_change_after_listen) { + shared_ptr pServerSocketFactory = createServerSocketFactory(); + TSSLServerSocket sock1("localhost", 0, pServerSocketFactory); + sock1.listen(); + BOOST_CHECK_THROW(sock1.setInterruptableChildren(false), std::logic_error); + sock1.close(); +} + +void peekerWorker(shared_ptr tt, bool expectedResult) { + uint8_t buf[400]; + try { + tt->read(buf, 1); + BOOST_CHECK_EQUAL(expectedResult, tt->peek()); + } catch (const TTransportException& tx) { + BOOST_CHECK_EQUAL(TTransportException::TIMED_OUT, tx.getType()); + } +} + +void peekerWorkerInterrupt(shared_ptr tt) { + uint8_t buf[400]; + try { + tt->read(buf, 1); + tt->peek(); + } catch (const TTransportException& tx) { + BOOST_CHECK_EQUAL(TTransportException::INTERRUPTED, tx.getType()); + } +} + +BOOST_AUTO_TEST_CASE(test_ssl_interruptable_child_peek) { + shared_ptr pServerSocketFactory = createServerSocketFactory(); + TSSLServerSocket sock1("localhost", 0, pServerSocketFactory); + sock1.listen(); + int port = sock1.getPort(); + shared_ptr pClientSocketFactory = createClientSocketFactory(); + shared_ptr clientSock = pClientSocketFactory->createSocket("localhost", port); + clientSock->open(); + shared_ptr accepted = sock1.accept(); + boost::thread peekThread(std::bind(peekerWorkerInterrupt, accepted)); + clientSock->write((const uint8_t*)"0", 1); + boost::this_thread::sleep(boost::posix_time::milliseconds(50)); + // peekThread is practically guaranteed to be blocking now + sock1.interruptChildren(); + BOOST_CHECK_MESSAGE(peekThread.try_join_for(boost::chrono::milliseconds(200)), + "server socket interruptChildren did not interrupt child peek"); + accepted->close(); + clientSock->close(); + sock1.close(); +} + +BOOST_AUTO_TEST_CASE(test_ssl_non_interruptable_child_peek) { + shared_ptr pServerSocketFactory = createServerSocketFactory(); + TSSLServerSocket sock1("localhost", 0, pServerSocketFactory); + sock1.setInterruptableChildren(false); // returns to pre-THRIFT-2441 behavior + sock1.listen(); + int port = sock1.getPort(); + shared_ptr pClientSocketFactory = createClientSocketFactory(); + shared_ptr clientSock = pClientSocketFactory->createSocket("localhost", port); + clientSock->open(); + shared_ptr accepted = sock1.accept(); + static_pointer_cast(accepted)->setRecvTimeout(1000); + boost::thread peekThread(std::bind(peekerWorker, accepted, false)); + clientSock->write((const uint8_t*)"0", 1); + boost::this_thread::sleep(boost::posix_time::milliseconds(50)); + // peekThread is practically guaranteed to be blocking now + sock1.interruptChildren(); + BOOST_CHECK_MESSAGE(!peekThread.try_join_for(boost::chrono::milliseconds(200)), + "server socket interruptChildren interrupted child peek"); + + // wait for the receive timeout to kick in + peekThread.join(); + accepted->close(); + clientSock->close(); + sock1.close(); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/jaegertracing/thrift/lib/cpp/test/TServerIntegrationTest.cpp b/src/jaegertracing/thrift/lib/cpp/test/TServerIntegrationTest.cpp new file mode 100644 index 000000000..b88c35bf4 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/test/TServerIntegrationTest.cpp @@ -0,0 +1,536 @@ +/* + * 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. + */ + +#define BOOST_TEST_MODULE TServerIntegrationTest +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "gen-cpp/ParentService.h" +#include +#include + +using apache::thrift::concurrency::Guard; +using apache::thrift::concurrency::Monitor; +using apache::thrift::concurrency::Mutex; +using apache::thrift::concurrency::Synchronized; +using apache::thrift::protocol::TBinaryProtocol; +using apache::thrift::protocol::TBinaryProtocolFactory; +using apache::thrift::protocol::TProtocol; +using apache::thrift::protocol::TProtocolFactory; +using apache::thrift::transport::TServerSocket; +using apache::thrift::transport::TServerTransport; +using apache::thrift::transport::TSocket; +using apache::thrift::transport::TTransport; +using apache::thrift::transport::TTransportException; +using apache::thrift::transport::TTransportFactory; +using apache::thrift::server::TServer; +using apache::thrift::server::TServerEventHandler; +using apache::thrift::server::TSimpleServer; +using apache::thrift::server::TThreadPoolServer; +using apache::thrift::server::TThreadedServer; +using std::dynamic_pointer_cast; +using std::make_shared; +using std::shared_ptr; +using apache::thrift::test::ParentServiceClient; +using apache::thrift::test::ParentServiceIf; +using apache::thrift::test::ParentServiceIfFactory; +using apache::thrift::test::ParentServiceIfSingletonFactory; +using apache::thrift::test::ParentServiceProcessor; +using apache::thrift::test::ParentServiceProcessorFactory; +using apache::thrift::TProcessor; +using apache::thrift::TProcessorFactory; +using boost::posix_time::milliseconds; + +/** + * preServe runs after listen() is successful, when we can connect + */ +class TServerReadyEventHandler : public TServerEventHandler, public Monitor { +public: + TServerReadyEventHandler() : isListening_(false), accepted_(0) {} + ~TServerReadyEventHandler() override = default; + void preServe() override { + Synchronized sync(*this); + isListening_ = true; + notify(); + } + void* createContext(shared_ptr input, + shared_ptr output) override { + Synchronized sync(*this); + ++accepted_; + notify(); + + (void)input; + (void)output; + return nullptr; + } + bool isListening() const { return isListening_; } + uint64_t acceptedCount() const { return accepted_; } + +private: + bool isListening_; + uint64_t accepted_; +}; + +/** + * Reusing another generated test, just something to serve up + */ +class ParentHandler : public ParentServiceIf { +public: + ParentHandler() : generation_(0) {} + + int32_t incrementGeneration() override { + Guard g(mutex_); + return ++generation_; + } + + int32_t getGeneration() override { + Guard g(mutex_); + return generation_; + } + + void addString(const std::string& s) override { + Guard g(mutex_); + strings_.push_back(s); + } + + void getStrings(std::vector& _return) override { + Guard g(mutex_); + _return = strings_; + } + + void getDataWait(std::string& _return, const int32_t length) override { + THRIFT_UNUSED_VARIABLE(_return); + THRIFT_UNUSED_VARIABLE(length); + } + + void onewayWait() override {} + + void exceptionWait(const std::string& message) override { THRIFT_UNUSED_VARIABLE(message); } + + void unexpectedExceptionWait(const std::string& message) override { THRIFT_UNUSED_VARIABLE(message); } + +protected: + Mutex mutex_; + int32_t generation_; + std::vector strings_; +}; + +void autoSocketCloser(TSocket* pSock) { + pSock->close(); + delete pSock; +} + +template +class TServerIntegrationTestFixture { +public: + TServerIntegrationTestFixture(const shared_ptr& _processorFactory) + : pServer(new TServerType(_processorFactory, + shared_ptr( + new TServerSocket("localhost", 0)), + shared_ptr(new TTransportFactory), + shared_ptr(new TBinaryProtocolFactory))), + pEventHandler(shared_ptr(new TServerReadyEventHandler)), + bStressDone(false), + bStressConnectionCount(0), + bStressRequestCount(0) { + pServer->setServerEventHandler(pEventHandler); + } + + TServerIntegrationTestFixture(const shared_ptr& _processor) + : pServer( + new TServerType(_processor, + shared_ptr(new TServerSocket("localhost", 0)), + shared_ptr(new TTransportFactory), + shared_ptr(new TBinaryProtocolFactory))), + pEventHandler(shared_ptr(new TServerReadyEventHandler)), + bStressDone(false), + bStressConnectionCount(0), + bStressRequestCount(0) { + pServer->setServerEventHandler(pEventHandler); + } + + void startServer() { + pServerThread.reset(new boost::thread(std::bind(&TServerType::serve, pServer.get()))); + + // block until listen() completes so clients will be able to connect + Synchronized sync(*(pEventHandler.get())); + while (!pEventHandler->isListening()) { + pEventHandler->wait(); + } + + BOOST_TEST_MESSAGE(" server is listening"); + } + + void blockUntilAccepted(uint64_t numAccepted) { + Synchronized sync(*(pEventHandler.get())); + while (pEventHandler->acceptedCount() < numAccepted) { + pEventHandler->wait(); + } + + BOOST_TEST_MESSAGE(boost::format(" server has accepted %1%") % numAccepted); + } + + void stopServer() { + if (pServerThread) { + pServer->stop(); + BOOST_TEST_MESSAGE(" server stop completed"); + + pServerThread->join(); + BOOST_TEST_MESSAGE(" server thread joined"); + pServerThread.reset(); + } + } + + ~TServerIntegrationTestFixture() { stopServer(); } + + /** + * Performs a baseline test where some clients are opened and issue a single operation + * and then disconnect at different intervals. + * \param[in] numToMake the number of concurrent clients + * \param[in] expectedHWM the high water mark we expect of concurrency + * \param[in] purpose a description of the test for logging purposes + */ + void baseline(int64_t numToMake, int64_t expectedHWM, const std::string& purpose) { + BOOST_TEST_MESSAGE(boost::format("Testing %1%: %2% with %3% clients, expect %4% HWM") + % typeid(TServerType).name() % purpose % numToMake % expectedHWM); + + startServer(); + + std::vector > holdSockets; + std::vector > holdThreads; + + for (int64_t i = 0; i < numToMake; ++i) { + shared_ptr pClientSock(new TSocket("localhost", getServerPort()), + autoSocketCloser); + holdSockets.push_back(pClientSock); + shared_ptr pClientProtocol(new TBinaryProtocol(pClientSock)); + ParentServiceClient client(pClientProtocol); + pClientSock->open(); + client.incrementGeneration(); + holdThreads.push_back(shared_ptr( + new boost::thread(std::bind(&TServerIntegrationTestFixture::delayClose, + this, + pClientSock, + milliseconds(10 * numToMake))))); + } + + BOOST_CHECK_EQUAL(expectedHWM, pServer->getConcurrentClientCountHWM()); + + BOOST_FOREACH (shared_ptr pThread, holdThreads) { pThread->join(); } + holdThreads.clear(); + holdSockets.clear(); + + stopServer(); + } + + /** + * Helper method used to close a connection after a delay. + * \param[in] toClose the connection to close + * \param[in] after the delay to impose + */ + void delayClose(shared_ptr toClose, boost::posix_time::time_duration after) { + boost::this_thread::sleep(after); + toClose->close(); + } + + /** + * \returns the server port number + */ + int getServerPort() { + auto* pSock = dynamic_cast(pServer->getServerTransport().get()); + if (!pSock) { throw std::logic_error("how come?"); } + return pSock->getPort(); + } + + /** + * Performs a stress test by spawning threads that connect, do a number of operations + * and disconnect, then a random delay, then do it over again. This is done for a fixed + * period of time to test for concurrency correctness. + * \param[in] numToMake the number of concurrent clients + */ + void stress(int64_t numToMake, const boost::posix_time::time_duration& duration) { + BOOST_TEST_MESSAGE(boost::format("Stress testing %1% with %2% clients for %3% seconds") + % typeid(TServerType).name() % numToMake % duration.total_seconds()); + + startServer(); + + std::vector > holdThreads; + for (int64_t i = 0; i < numToMake; ++i) { + holdThreads.push_back(shared_ptr( + new boost::thread(std::bind(&TServerIntegrationTestFixture::stressor, this)))); + } + + boost::this_thread::sleep(duration); + bStressDone = true; + + BOOST_TEST_MESSAGE(boost::format(" serviced %1% connections (HWM %2%) totaling %3% requests") + % bStressConnectionCount % pServer->getConcurrentClientCountHWM() % bStressRequestCount); + + BOOST_FOREACH (shared_ptr pThread, holdThreads) { pThread->join(); } + holdThreads.clear(); + + BOOST_CHECK(bStressRequestCount > 0); + + stopServer(); + } + + /** + * Helper method to stress the system + */ + void stressor() { + while (!bStressDone) { + shared_ptr pSocket(new TSocket("localhost", getServerPort()), autoSocketCloser); + shared_ptr pProtocol(new TBinaryProtocol(pSocket)); + ParentServiceClient client(pProtocol); + pSocket->open(); + bStressConnectionCount.fetch_add(1, std::memory_order_relaxed); + for (int i = 0; i < rand() % 1000; ++i) { + client.incrementGeneration(); + bStressRequestCount.fetch_add(1, std::memory_order_relaxed); + } + } + } + + shared_ptr pServer; + shared_ptr pEventHandler; + shared_ptr pServerThread; + std::atomic bStressDone; + std::atomic bStressConnectionCount; + std::atomic bStressRequestCount; +}; + +template +class TServerIntegrationProcessorFactoryTestFixture + : public TServerIntegrationTestFixture { +public: + TServerIntegrationProcessorFactoryTestFixture() + : TServerIntegrationTestFixture(make_shared( + make_shared( + make_shared()))) {} +}; + +template +class TServerIntegrationProcessorTestFixture : public TServerIntegrationTestFixture { +public: + TServerIntegrationProcessorTestFixture() + : TServerIntegrationTestFixture( + make_shared(make_shared())) {} +}; + +BOOST_AUTO_TEST_SUITE(constructors) + +BOOST_FIXTURE_TEST_CASE(test_simple_factory, + TServerIntegrationProcessorFactoryTestFixture) { + baseline(3, 1, "factory"); +} + +BOOST_FIXTURE_TEST_CASE(test_simple, TServerIntegrationProcessorTestFixture) { + baseline(3, 1, "processor"); +} + +BOOST_FIXTURE_TEST_CASE(test_threaded_factory, + TServerIntegrationProcessorFactoryTestFixture) { + baseline(10, 10, "factory"); +} + +BOOST_FIXTURE_TEST_CASE(test_threaded, TServerIntegrationProcessorTestFixture) { + baseline(10, 10, "processor"); +} + +BOOST_FIXTURE_TEST_CASE(test_threaded_bound, + TServerIntegrationProcessorTestFixture) { + pServer->setConcurrentClientLimit(4); + baseline(10, 4, "limit by server framework"); +} + +BOOST_FIXTURE_TEST_CASE(test_threaded_stress, + TServerIntegrationProcessorFactoryTestFixture) { + stress(10, boost::posix_time::seconds(3)); +} + +BOOST_FIXTURE_TEST_CASE(test_threadpool_factory, + TServerIntegrationProcessorFactoryTestFixture) { + pServer->getThreadManager()->threadFactory( + shared_ptr( + new apache::thrift::concurrency::ThreadFactory)); + pServer->getThreadManager()->start(); + + // thread factory has 4 threads as a default + // thread factory however is a bad way to limit concurrent clients + // as accept() will be called to grab a 5th client socket, in this case + // and then the thread factory will block adding the thread to manage + // that client. + baseline(10, 5, "limit by thread manager"); +} + +BOOST_FIXTURE_TEST_CASE(test_threadpool, + TServerIntegrationProcessorTestFixture) { + pServer->getThreadManager()->threadFactory( + shared_ptr( + new apache::thrift::concurrency::ThreadFactory)); + pServer->getThreadManager()->start(); + + // thread factory has 4 threads as a default + // thread factory however is a bad way to limit concurrent clients + // as accept() will be called to grab a 5th client socket, in this case + // and then the thread factory will block adding the thread to manage + // that client. + baseline(10, 5, "limit by thread manager"); +} + +BOOST_FIXTURE_TEST_CASE(test_threadpool_bound, + TServerIntegrationProcessorTestFixture) { + pServer->getThreadManager()->threadFactory( + shared_ptr( + new apache::thrift::concurrency::ThreadFactory)); + pServer->getThreadManager()->start(); + pServer->setConcurrentClientLimit(4); + + baseline(10, 4, "server framework connection limit"); +} + +BOOST_FIXTURE_TEST_CASE(test_threadpool_stress, + TServerIntegrationProcessorTestFixture) { + pServer->getThreadManager()->threadFactory( + shared_ptr( + new apache::thrift::concurrency::ThreadFactory)); + pServer->getThreadManager()->start(); + + stress(10, boost::posix_time::seconds(3)); +} + +BOOST_AUTO_TEST_SUITE_END() + +BOOST_FIXTURE_TEST_SUITE(TServerIntegrationTest, + TServerIntegrationProcessorTestFixture) + +BOOST_AUTO_TEST_CASE(test_stop_with_interruptable_clients_connected) { + // This tests THRIFT-2441 new behavior: stopping the server disconnects clients + BOOST_TEST_MESSAGE("Testing stop with interruptable clients"); + + startServer(); + + shared_ptr pClientSock1(new TSocket("localhost", getServerPort()), + autoSocketCloser); + pClientSock1->open(); + + shared_ptr pClientSock2(new TSocket("localhost", getServerPort()), + autoSocketCloser); + pClientSock2->open(); + + // Ensure they have been accepted + blockUntilAccepted(2); + + // The test fixture destructor will force the sockets to disconnect + // Prior to THRIFT-2441, pServer->stop() would hang until clients disconnected + stopServer(); + + // extra proof the server end disconnected the clients + uint8_t buf[1]; + BOOST_CHECK_EQUAL(0, pClientSock1->read(&buf[0], 1)); // 0 = disconnected + BOOST_CHECK_EQUAL(0, pClientSock2->read(&buf[0], 1)); // 0 = disconnected +} + +BOOST_AUTO_TEST_CASE(test_stop_with_uninterruptable_clients_connected) { + // This tests pre-THRIFT-2441 behavior: stopping the server blocks until clients + // disconnect. + BOOST_TEST_MESSAGE("Testing stop with uninterruptable clients"); + + dynamic_pointer_cast(pServer->getServerTransport()) + ->setInterruptableChildren(false); // returns to pre-THRIFT-2441 behavior + + startServer(); + + shared_ptr pClientSock1(new TSocket("localhost", getServerPort()), + autoSocketCloser); + pClientSock1->open(); + + shared_ptr pClientSock2(new TSocket("localhost", getServerPort()), + autoSocketCloser); + pClientSock2->open(); + + // Ensure they have been accepted + blockUntilAccepted(2); + + boost::thread t1(std::bind(&TServerIntegrationTestFixture::delayClose, + this, + pClientSock1, + milliseconds(250))); + boost::thread t2(std::bind(&TServerIntegrationTestFixture::delayClose, + this, + pClientSock2, + milliseconds(250))); + + // Once the clients disconnect the server will stop + stopServer(); + BOOST_CHECK(pServer->getConcurrentClientCountHWM() > 0); + t1.join(); + t2.join(); +} + +BOOST_AUTO_TEST_CASE(test_concurrent_client_limit) { + startServer(); + BOOST_TEST_MESSAGE("Testing the concurrent client limit"); + + BOOST_CHECK_EQUAL(INT64_MAX, pServer->getConcurrentClientLimit()); + pServer->setConcurrentClientLimit(2); + BOOST_CHECK_EQUAL(0, pServer->getConcurrentClientCount()); + BOOST_CHECK_EQUAL(2, pServer->getConcurrentClientLimit()); + + shared_ptr pClientSock1(new TSocket("localhost", getServerPort()), + autoSocketCloser); + pClientSock1->open(); + blockUntilAccepted(1); + BOOST_CHECK_EQUAL(1, pServer->getConcurrentClientCount()); + + shared_ptr pClientSock2(new TSocket("localhost", getServerPort()), + autoSocketCloser); + pClientSock2->open(); + blockUntilAccepted(2); + BOOST_CHECK_EQUAL(2, pServer->getConcurrentClientCount()); + + // a third client cannot connect until one of the other two closes + boost::thread t2(std::bind(&TServerIntegrationTestFixture::delayClose, + this, + pClientSock2, + milliseconds(250))); + shared_ptr pClientSock3(new TSocket("localhost", getServerPort()), + autoSocketCloser); + pClientSock2->open(); + blockUntilAccepted(2); + BOOST_CHECK_EQUAL(2, pServer->getConcurrentClientCount()); + BOOST_CHECK_EQUAL(2, pServer->getConcurrentClientCountHWM()); + + stopServer(); + BOOST_CHECK(pServer->getConcurrentClientCountHWM() > 0); + t2.join(); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/jaegertracing/thrift/lib/cpp/test/TServerSocketTest.cpp b/src/jaegertracing/thrift/lib/cpp/test/TServerSocketTest.cpp new file mode 100644 index 000000000..bec6d4756 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/test/TServerSocketTest.cpp @@ -0,0 +1,69 @@ +/* + * 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. + */ + +#include +#include +#include +#include +#include "TTransportCheckThrow.h" +#include + +using apache::thrift::transport::TServerSocket; +using apache::thrift::transport::TSocket; +using apache::thrift::transport::TTransport; +using apache::thrift::transport::TTransportException; +using std::shared_ptr; + +BOOST_AUTO_TEST_SUITE(TServerSocketTest) + +BOOST_AUTO_TEST_CASE(test_bind_to_address) { + TServerSocket sock1("localhost", 0); + sock1.listen(); + int port = sock1.getPort(); + TSocket clientSock("localhost", port); + clientSock.open(); + shared_ptr accepted = sock1.accept(); + accepted->close(); + sock1.close(); + + std::cout << "An error message from getaddrinfo on the console is expected:" << std::endl; + TServerSocket sock2("257.258.259.260", 0); + BOOST_CHECK_THROW(sock2.listen(), TTransportException); + sock2.close(); +} + +BOOST_AUTO_TEST_CASE(test_listen_valid_port) { + TServerSocket sock1(-1); + TTRANSPORT_CHECK_THROW(sock1.listen(), TTransportException::BAD_ARGS); + + TServerSocket sock2(65536); + TTRANSPORT_CHECK_THROW(sock2.listen(), TTransportException::BAD_ARGS); +} + +BOOST_AUTO_TEST_CASE(test_close_before_listen) { + TServerSocket sock1("localhost", 0); + sock1.close(); +} + +BOOST_AUTO_TEST_CASE(test_get_port) { + TServerSocket sock1("localHost", 888); + BOOST_CHECK_EQUAL(888, sock1.getPort()); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/jaegertracing/thrift/lib/cpp/test/TServerTransportTest.cpp b/src/jaegertracing/thrift/lib/cpp/test/TServerTransportTest.cpp new file mode 100644 index 000000000..18a393ee0 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/test/TServerTransportTest.cpp @@ -0,0 +1,58 @@ +/* + * 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. + */ + +#include +#include +#include +#include + +using apache::thrift::transport::TServerTransport; +using apache::thrift::transport::TTransport; +using apache::thrift::transport::TTransportException; +using std::shared_ptr; + +BOOST_AUTO_TEST_SUITE(TServerTransportTest) + +class TestTTransport : public TTransport {}; + +class TestTServerTransport : public TServerTransport { +public: + TestTServerTransport() : valid_(true) {} + void close() override {} + bool valid_; + +protected: + shared_ptr acceptImpl() override { + return valid_ ? std::make_shared() + : shared_ptr(); + } +}; + +BOOST_AUTO_TEST_CASE(test_positive_accept) { + TestTServerTransport uut; + BOOST_CHECK(uut.accept()); +} + +BOOST_AUTO_TEST_CASE(test_negative_accept) { + TestTServerTransport uut; + uut.valid_ = false; + BOOST_CHECK_THROW(uut.accept(), TTransportException); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/jaegertracing/thrift/lib/cpp/test/TSocketInterruptTest.cpp b/src/jaegertracing/thrift/lib/cpp/test/TSocketInterruptTest.cpp new file mode 100644 index 000000000..366242f7c --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/test/TSocketInterruptTest.cpp @@ -0,0 +1,146 @@ +/* + * 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. + */ + +#define BOOST_TEST_MODULE TSocketInterruptTest +#include + +#include +#include +#include +#include +#include +#include + +using apache::thrift::transport::TServerSocket; +using apache::thrift::transport::TSocket; +using apache::thrift::transport::TTransport; +using apache::thrift::transport::TTransportException; +using namespace apache::thrift; + +BOOST_AUTO_TEST_SUITE(TSocketInterruptTest) + +void readerWorker(std::shared_ptr tt, uint32_t expectedResult) { + uint8_t buf[4]; + BOOST_CHECK_EQUAL(expectedResult, tt->read(buf, 4)); +} + +void readerWorkerMustThrow(std::shared_ptr tt) { + try { + uint8_t buf[4]; + tt->read(buf, 4); + BOOST_ERROR("should not have gotten here"); + } catch (const TTransportException& tx) { + BOOST_CHECK_EQUAL(TTransportException::INTERRUPTED, tx.getType()); + } +} + +BOOST_AUTO_TEST_CASE(test_interruptable_child_read) { + TServerSocket sock1("localhost", 0); + sock1.listen(); + int port = sock1.getPort(); + TSocket clientSock("localhost", port); + clientSock.open(); + std::shared_ptr accepted = sock1.accept(); + boost::thread readThread(std::bind(readerWorkerMustThrow, accepted)); + boost::this_thread::sleep(boost::posix_time::milliseconds(50)); + // readThread is practically guaranteed to be blocking now + sock1.interruptChildren(); + BOOST_CHECK_MESSAGE(readThread.try_join_for(boost::chrono::milliseconds(200)), + "server socket interruptChildren did not interrupt child read"); + clientSock.close(); + accepted->close(); + sock1.close(); +} + +BOOST_AUTO_TEST_CASE(test_non_interruptable_child_read) { + TServerSocket sock1("localhost", 0); + sock1.setInterruptableChildren(false); // returns to pre-THRIFT-2441 behavior + sock1.listen(); + int port = sock1.getPort(); + TSocket clientSock("localhost", port); + clientSock.open(); + std::shared_ptr accepted = sock1.accept(); + boost::thread readThread(std::bind(readerWorker, accepted, 0)); + boost::this_thread::sleep(boost::posix_time::milliseconds(50)); + // readThread is practically guaranteed to be blocking here + sock1.interruptChildren(); + BOOST_CHECK_MESSAGE(!readThread.try_join_for(boost::chrono::milliseconds(200)), + "server socket interruptChildren interrupted child read"); + + // only way to proceed is to have the client disconnect + clientSock.close(); + readThread.join(); + accepted->close(); + sock1.close(); +} + +BOOST_AUTO_TEST_CASE(test_cannot_change_after_listen) { + TServerSocket sock1("localhost", 0); + sock1.listen(); + BOOST_CHECK_THROW(sock1.setInterruptableChildren(false), std::logic_error); + sock1.close(); +} + +void peekerWorker(std::shared_ptr tt, bool expectedResult) { + BOOST_CHECK_EQUAL(expectedResult, tt->peek()); +} + +BOOST_AUTO_TEST_CASE(test_interruptable_child_peek) { + TServerSocket sock1("localhost", 0); + sock1.listen(); + int port = sock1.getPort(); + TSocket clientSock("localhost", port); + clientSock.open(); + std::shared_ptr accepted = sock1.accept(); + // peek() will return false if child is interrupted + boost::thread peekThread(std::bind(peekerWorker, accepted, false)); + boost::this_thread::sleep(boost::posix_time::milliseconds(50)); + // peekThread is practically guaranteed to be blocking now + sock1.interruptChildren(); + BOOST_CHECK_MESSAGE(peekThread.try_join_for(boost::chrono::milliseconds(200)), + "server socket interruptChildren did not interrupt child peek"); + clientSock.close(); + accepted->close(); + sock1.close(); +} + +BOOST_AUTO_TEST_CASE(test_non_interruptable_child_peek) { + TServerSocket sock1("localhost", 0); + sock1.setInterruptableChildren(false); // returns to pre-THRIFT-2441 behavior + sock1.listen(); + int port = sock1.getPort(); + TSocket clientSock("localhost", port); + clientSock.open(); + std::shared_ptr accepted = sock1.accept(); + // peek() will return false when remote side is closed + boost::thread peekThread(std::bind(peekerWorker, accepted, false)); + boost::this_thread::sleep(boost::posix_time::milliseconds(50)); + // peekThread is practically guaranteed to be blocking now + sock1.interruptChildren(); + BOOST_CHECK_MESSAGE(!peekThread.try_join_for(boost::chrono::milliseconds(200)), + "server socket interruptChildren interrupted child peek"); + + // only way to proceed is to have the client disconnect + clientSock.close(); + peekThread.join(); + accepted->close(); + sock1.close(); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/jaegertracing/thrift/lib/cpp/test/TTransportCheckThrow.h b/src/jaegertracing/thrift/lib/cpp/test/TTransportCheckThrow.h new file mode 100644 index 000000000..92277b480 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/test/TTransportCheckThrow.h @@ -0,0 +1,44 @@ +/* + * 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. + */ + +#pragma once + +#define TTRANSPORT_CHECK_THROW(_CALL, _TYPE) \ + { \ + bool caught = false; \ + try { \ + (_CALL); \ + } catch (TTransportException & ex) { \ + BOOST_CHECK_EQUAL(ex.getType(), _TYPE); \ + caught = true; \ + } \ + BOOST_CHECK_MESSAGE(caught, "expected TTransportException but nothing was thrown"); \ + } + +#define TTRANSPORT_REQUIRE_THROW(_CALL, _TYPE) \ + { \ + bool caught = false; \ + try { \ + (_CALL); \ + } catch (TTransportException & ex) { \ + BOOST_REQUIRE_EQUAL(ex.getType(), _TYPE); \ + caught = true; \ + } \ + BOOST_REQUIRE_MESSAGE(caught, "expected TTransportException but nothing was thrown"); \ + } diff --git a/src/jaegertracing/thrift/lib/cpp/test/ThriftTest_extras.cpp b/src/jaegertracing/thrift/lib/cpp/test/ThriftTest_extras.cpp new file mode 100644 index 000000000..af5606efb --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/test/ThriftTest_extras.cpp @@ -0,0 +1,33 @@ +/* + * 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. + */ + +// Extra functions required for ThriftTest_types to work + +#include +#include "gen-cpp/ThriftTest_types.h" + +namespace thrift { +namespace test { + +bool Insanity::operator<(thrift::test::Insanity const& other) const { + using apache::thrift::ThriftDebugString; + return ThriftDebugString(*this) < ThriftDebugString(other); +} +} +} diff --git a/src/jaegertracing/thrift/lib/cpp/test/ToStringTest.cpp b/src/jaegertracing/thrift/lib/cpp/test/ToStringTest.cpp new file mode 100644 index 000000000..d204cb346 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/test/ToStringTest.cpp @@ -0,0 +1,137 @@ +/* + * 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. + */ + +#include +#include + +#include + +#include + +#include "gen-cpp/ThriftTest_types.h" +#include "gen-cpp/OptionalRequiredTest_types.h" +#include "gen-cpp/DebugProtoTest_types.h" + +using apache::thrift::to_string; + +BOOST_AUTO_TEST_SUITE(ToStringTest) + +BOOST_AUTO_TEST_CASE(base_types_to_string) { + BOOST_CHECK_EQUAL(to_string(10), "10"); + BOOST_CHECK_EQUAL(to_string(true), "1"); + BOOST_CHECK_EQUAL(to_string('a'), "a"); + BOOST_CHECK_EQUAL(to_string(1.2), "1.2"); + BOOST_CHECK_EQUAL(to_string("abc"), "abc"); +} + +BOOST_AUTO_TEST_CASE(empty_vector_to_string) { + std::vector l; + BOOST_CHECK_EQUAL(to_string(l), "[]"); +} + +BOOST_AUTO_TEST_CASE(single_item_vector_to_string) { + std::vector l; + l.push_back(100); + BOOST_CHECK_EQUAL(to_string(l), "[100]"); +} + +BOOST_AUTO_TEST_CASE(multiple_item_vector_to_string) { + std::vector l; + l.push_back(100); + l.push_back(150); + BOOST_CHECK_EQUAL(to_string(l), "[100, 150]"); +} + +BOOST_AUTO_TEST_CASE(empty_map_to_string) { + std::map m; + BOOST_CHECK_EQUAL(to_string(m), "{}"); +} + +BOOST_AUTO_TEST_CASE(single_item_map_to_string) { + std::map m; + m[12] = "abc"; + BOOST_CHECK_EQUAL(to_string(m), "{12: abc}"); +} + +BOOST_AUTO_TEST_CASE(multi_item_map_to_string) { + std::map m; + m[12] = "abc"; + m[31] = "xyz"; + BOOST_CHECK_EQUAL(to_string(m), "{12: abc, 31: xyz}"); +} + +BOOST_AUTO_TEST_CASE(empty_set_to_string) { + std::set s; + BOOST_CHECK_EQUAL(to_string(s), "{}"); +} + +BOOST_AUTO_TEST_CASE(single_item_set_to_string) { + std::set s; + s.insert('c'); + BOOST_CHECK_EQUAL(to_string(s), "{c}"); +} + +BOOST_AUTO_TEST_CASE(multi_item_set_to_string) { + std::set s; + s.insert('a'); + s.insert('z'); + BOOST_CHECK_EQUAL(to_string(s), "{a, z}"); +} + +BOOST_AUTO_TEST_CASE(generated_empty_object_to_string) { + thrift::test::EmptyStruct e; + BOOST_CHECK_EQUAL(to_string(e), "EmptyStruct()"); +} + +BOOST_AUTO_TEST_CASE(generated_single_basic_field_object_to_string) { + thrift::test::StructA a; + a.__set_s("abcd"); + BOOST_CHECK_EQUAL(to_string(a), "StructA(s=abcd)"); +} + +BOOST_AUTO_TEST_CASE(generated_two_basic_fields_object_to_string) { + thrift::test::Bonk a; + a.__set_message("abcd"); + a.__set_type(1234); + BOOST_CHECK_EQUAL(to_string(a), "Bonk(message=abcd, type=1234)"); +} + +BOOST_AUTO_TEST_CASE(generated_optional_fields_object_to_string) { + thrift::test::Tricky2 a; + BOOST_CHECK_EQUAL(to_string(a), "Tricky2(im_optional=)"); + a.__set_im_optional(123); + BOOST_CHECK_EQUAL(to_string(a), "Tricky2(im_optional=123)"); +} + +BOOST_AUTO_TEST_CASE(generated_nested_object_to_string) { + thrift::test::OneField a; + BOOST_CHECK_EQUAL(to_string(a), "OneField(field=EmptyStruct())"); +} + +BOOST_AUTO_TEST_CASE(generated_nested_list_object_to_string) { + thrift::test::ListBonks l; + l.bonk.assign(2, thrift::test::Bonk()); + l.bonk[0].__set_message("a"); + l.bonk[1].__set_message("b"); + + BOOST_CHECK_EQUAL(to_string(l), + "ListBonks(bonk=[Bonk(message=a, type=0), Bonk(message=b, type=0)])"); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/jaegertracing/thrift/lib/cpp/test/TransportTest.cpp b/src/jaegertracing/thrift/lib/cpp/test/TransportTest.cpp new file mode 100644 index 000000000..a890aa8ce --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/test/TransportTest.cpp @@ -0,0 +1,1089 @@ +/* + * 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. + */ + +#include + +#include +#include +#ifdef HAVE_SYS_SOCKET_H +#include +#endif +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#if _WIN32 +#include +#include +#endif + +using namespace apache::thrift::transport; +using namespace apache::thrift; + +static boost::mt19937 rng; + +void initrand(unsigned int seed) { + rng.seed(seed); +} + +class SizeGenerator { +public: + virtual ~SizeGenerator() = default; + virtual uint32_t nextSize() = 0; + virtual std::string describe() const = 0; +}; + +class ConstantSizeGenerator : public SizeGenerator { +public: + ConstantSizeGenerator(uint32_t value) : value_(value) {} + uint32_t nextSize() override { return value_; } + std::string describe() const override { + std::ostringstream desc; + desc << value_; + return desc.str(); + } + +private: + uint32_t value_; +}; + +class RandomSizeGenerator : public SizeGenerator { +public: + RandomSizeGenerator(uint32_t min, uint32_t max) + : generator_(rng, boost::uniform_int(min, max)) {} + + uint32_t nextSize() override { return generator_(); } + + std::string describe() const override { + std::ostringstream desc; + desc << "rand(" << getMin() << ", " << getMax() << ")"; + return desc.str(); + } + + uint32_t getMin() const { return (generator_.distribution().min)(); } + uint32_t getMax() const { return (generator_.distribution().max)(); } + +private: + boost::variate_generator > generator_; +}; + +/** + * This class exists solely to make the TEST_RW() macro easier to use. + * - it can be constructed implicitly from an integer + * - it can contain either a ConstantSizeGenerator or a RandomSizeGenerator + * (TEST_RW can't take a SizeGenerator pointer or reference, since it needs + * to make a copy of the generator to bind it to the test function.) + */ +class GenericSizeGenerator : public SizeGenerator { +public: + GenericSizeGenerator(uint32_t value) : generator_(new ConstantSizeGenerator(value)) {} + GenericSizeGenerator(uint32_t min, uint32_t max) + : generator_(new RandomSizeGenerator(min, max)) {} + + uint32_t nextSize() override { return generator_->nextSize(); } + std::string describe() const override { return generator_->describe(); } + +private: + std::shared_ptr generator_; +}; + +/************************************************************************** + * Classes to set up coupled transports + **************************************************************************/ + +/** + * Helper class to represent a coupled pair of transports. + * + * Data written to the out transport can be read from the in transport. + * + * This is used as the base class for the various coupled transport + * implementations. It shouldn't be instantiated directly. + */ +template +class CoupledTransports { +public: + virtual ~CoupledTransports() = default; + typedef Transport_ TransportType; + + CoupledTransports() : in(), out() {} + + std::shared_ptr in; + std::shared_ptr out; + +private: + CoupledTransports(const CoupledTransports&) = delete; + CoupledTransports& operator=(const CoupledTransports&) = delete; +}; + +/** + * Coupled TMemoryBuffers + */ +class CoupledMemoryBuffers : public CoupledTransports { +public: + CoupledMemoryBuffers() : buf(new TMemoryBuffer) { + in = buf; + out = buf; + } + + std::shared_ptr buf; +}; + +/** + * Helper template class for creating coupled transports that wrap + * another transport. + */ +template +class CoupledWrapperTransportsT : public CoupledTransports { +public: + CoupledWrapperTransportsT() { + if (inner_.in) { + this->in.reset(new WrapperTransport_(inner_.in)); + } + if (inner_.out) { + this->out.reset(new WrapperTransport_(inner_.out)); + } + } + + InnerCoupledTransports_ inner_; +}; + +/** + * Coupled TBufferedTransports. + */ +template +class CoupledBufferedTransportsT + : public CoupledWrapperTransportsT {}; + +typedef CoupledBufferedTransportsT CoupledBufferedTransports; + +/** + * Coupled TFramedTransports. + */ +template +class CoupledFramedTransportsT + : public CoupledWrapperTransportsT {}; + +typedef CoupledFramedTransportsT CoupledFramedTransports; + +/** + * Coupled TZlibTransports. + */ +template +class CoupledZlibTransportsT : public CoupledWrapperTransportsT {}; + +typedef CoupledZlibTransportsT CoupledZlibTransports; + +#ifndef _WIN32 +// FD transport doesn't make much sense on Windows. +/** + * Coupled TFDTransports. + */ +class CoupledFDTransports : public CoupledTransports { +public: + CoupledFDTransports() { + int pipes[2]; + + if (pipe(pipes) != 0) { + return; + } + + in.reset(new TFDTransport(pipes[0], TFDTransport::CLOSE_ON_DESTROY)); + out.reset(new TFDTransport(pipes[1], TFDTransport::CLOSE_ON_DESTROY)); + } +}; +#else +/** + * Coupled pipe transports + */ +class CoupledPipeTransports : public CoupledTransports { +public: + HANDLE hRead; + HANDLE hWrite; + + CoupledPipeTransports() { + BOOST_REQUIRE(CreatePipe(&hRead, &hWrite, NULL, 1048576 * 2)); + in.reset(new TPipe(hRead, hWrite)); + in->open(); + out = in; + } +}; +#endif + +/** + * Coupled TSockets + */ +class CoupledSocketTransports : public CoupledTransports { +public: + CoupledSocketTransports() { + THRIFT_SOCKET sockets[2] = {0}; + if (THRIFT_SOCKETPAIR(PF_UNIX, SOCK_STREAM, 0, sockets) != 0) { + return; + } + + in.reset(new TSocket(sockets[0])); + out.reset(new TSocket(sockets[1])); + out->setSendTimeout(100); + } +}; + +// These could be made to work on Windows, but I don't care enough to make it happen +#ifndef _WIN32 +/** + * Coupled TFileTransports + */ +class CoupledFileTransports : public CoupledTransports { +public: + CoupledFileTransports() { +#ifndef _WIN32 + const char* tmp_dir = "/tmp"; +#define FILENAME_SUFFIX "/thrift.transport_test" +#else + const char* tmp_dir = getenv("TMP"); +#define FILENAME_SUFFIX "\\thrift.transport_test" +#endif + + // Create a temporary file to use + filename.resize(strlen(tmp_dir) + strlen(FILENAME_SUFFIX)); + THRIFT_SNPRINTF(&filename[0], filename.size(), "%s" FILENAME_SUFFIX, tmp_dir); +#undef FILENAME_SUFFIX + + { std::ofstream dummy_creation(filename.c_str(), std::ofstream::trunc); } + + in.reset(new TFileTransport(filename, true)); + out.reset(new TFileTransport(filename)); + } + + ~CoupledFileTransports() override { remove(filename.c_str()); } + + std::string filename; +}; +#endif + +/** + * Wrapper around another CoupledTransports implementation that exposes the + * transports as TTransport pointers. + * + * This is used since accessing a transport via a "TTransport*" exercises a + * different code path than using the base pointer class. As part of the + * template code changes, most transport methods are no longer virtual. + */ +template +class CoupledTTransports : public CoupledTransports { +public: + CoupledTTransports() : transports() { + in = transports.in; + out = transports.out; + } + + CoupledTransports_ transports; +}; + +/** + * Wrapper around another CoupledTransports implementation that exposes the + * transports as TBufferBase pointers. + * + * This can only be instantiated with a transport type that is a subclass of + * TBufferBase. + */ +template +class CoupledBufferBases : public CoupledTransports { +public: + CoupledBufferBases() : transports() { + in = transports.in; + out = transports.out; + } + + CoupledTransports_ transports; +}; + +/************************************************************************** + * Alarm handling code for use in tests that check the transport blocking + * semantics. + * + * If the transport ends up blocking, we don't want to hang forever. We use + * SIGALRM to fire schedule signal to wake up and try to write data so the + * transport will unblock. + * + * It isn't really the safest thing in the world to be mucking around with + * complicated global data structures in a signal handler. It should probably + * be okay though, since we know the main thread should always be blocked in a + * read() request when the signal handler is running. + **************************************************************************/ + +struct TriggerInfo { + TriggerInfo(int seconds, const std::shared_ptr& transport, uint32_t writeLength) + : timeoutSeconds(seconds), transport(transport), writeLength(writeLength), next(nullptr) {} + + int timeoutSeconds; + std::shared_ptr transport; + uint32_t writeLength; + TriggerInfo* next; +}; + +apache::thrift::concurrency::Monitor g_alarm_monitor; +TriggerInfo* g_triggerInfo; +unsigned int g_numTriggersFired; +bool g_teardown = false; + +void alarm_handler() { + TriggerInfo* info = nullptr; + { + apache::thrift::concurrency::Synchronized s(g_alarm_monitor); + // The alarm timed out, which almost certainly means we're stuck + // on a transport that is incorrectly blocked. + ++g_numTriggersFired; + + // Note: we print messages to stdout instead of stderr, since + // tools/test/runner only records stdout messages in the failure messages for + // boost tests. (boost prints its test info to stdout.) + printf("Timeout alarm expired; attempting to unblock transport\n"); + if (g_triggerInfo == nullptr) { + printf(" trigger stack is empty!\n"); + } + + // Pop off the first TriggerInfo. + // If there is another one, schedule an alarm for it. + info = g_triggerInfo; + g_triggerInfo = info->next; + } + + // Write some data to the transport to hopefully unblock it. + auto* buf = new uint8_t[info->writeLength]; + memset(buf, 'b', info->writeLength); + boost::scoped_array array(buf); + info->transport->write(buf, info->writeLength); + info->transport->flush(); + + delete info; +} + +void alarm_handler_wrapper() { + int64_t timeout = 0; // timeout of 0 means wait forever + while (true) { + bool fireHandler = false; + { + apache::thrift::concurrency::Synchronized s(g_alarm_monitor); + if (g_teardown) + return; + // calculate timeout + if (g_triggerInfo == nullptr) { + timeout = 0; + } else { + timeout = g_triggerInfo->timeoutSeconds * 1000; + } + + int waitResult = g_alarm_monitor.waitForTimeRelative(timeout); + if (waitResult == THRIFT_ETIMEDOUT) + fireHandler = true; + } + if (fireHandler) + alarm_handler(); // calling outside the lock + } +} + +/** + * Add a trigger to be scheduled "seconds" seconds after the + * last currently scheduled trigger. + * + * (Note that this is not "seconds" from now. That might be more logical, but + * would require slightly more complicated sorting, rather than just appending + * to the end.) + */ +void add_trigger(unsigned int seconds, + const std::shared_ptr& transport, + uint32_t write_len) { + auto* info = new TriggerInfo(seconds, transport, write_len); + { + apache::thrift::concurrency::Synchronized s(g_alarm_monitor); + if (g_triggerInfo == nullptr) { + // This is the first trigger. + // Set g_triggerInfo, and schedule the alarm + g_triggerInfo = info; + g_alarm_monitor.notify(); + } else { + // Add this trigger to the end of the list + TriggerInfo* prev = g_triggerInfo; + while (prev->next) { + prev = prev->next; + } + prev->next = info; + } + } +} + +void clear_triggers() { + TriggerInfo* info = nullptr; + + { + apache::thrift::concurrency::Synchronized s(g_alarm_monitor); + info = g_triggerInfo; + g_triggerInfo = nullptr; + g_numTriggersFired = 0; + g_alarm_monitor.notify(); + } + + while (info != nullptr) { + TriggerInfo* next = info->next; + delete info; + info = next; + } +} + +void set_trigger(unsigned int seconds, + const std::shared_ptr& transport, + uint32_t write_len) { + clear_triggers(); + add_trigger(seconds, transport, write_len); +} + +/************************************************************************** + * Test functions + **************************************************************************/ + +/** + * Test interleaved write and read calls. + * + * Generates a buffer totalSize bytes long, then writes it to the transport, + * and verifies the written data can be read back correctly. + * + * Mode of operation: + * - call wChunkGenerator to figure out how large of a chunk to write + * - call wSizeGenerator to get the size for individual write() calls, + * and do this repeatedly until the entire chunk is written. + * - call rChunkGenerator to figure out how large of a chunk to read + * - call rSizeGenerator to get the size for individual read() calls, + * and do this repeatedly until the entire chunk is read. + * - repeat until the full buffer is written and read back, + * then compare the data read back against the original buffer + * + * + * - If any of the size generators return 0, this means to use the maximum + * possible size. + * + * - If maxOutstanding is non-zero, write chunk sizes will be chosen such that + * there are never more than maxOutstanding bytes waiting to be read back. + */ +template +void test_rw(uint32_t totalSize, + SizeGenerator& wSizeGenerator, + SizeGenerator& rSizeGenerator, + SizeGenerator& wChunkGenerator, + SizeGenerator& rChunkGenerator, + uint32_t maxOutstanding) { + CoupledTransports transports; + BOOST_REQUIRE(transports.in != nullptr); + BOOST_REQUIRE(transports.out != nullptr); + + boost::shared_array wbuf = boost::shared_array(new uint8_t[totalSize]); + boost::shared_array rbuf = boost::shared_array(new uint8_t[totalSize]); + + // store some data in wbuf + for (uint32_t n = 0; n < totalSize; ++n) { + wbuf[n] = (n & 0xff); + } + // clear rbuf + memset(rbuf.get(), 0, totalSize); + + uint32_t total_written = 0; + uint32_t total_read = 0; + while (total_read < totalSize) { + // Determine how large a chunk of data to write + uint32_t wchunk_size = wChunkGenerator.nextSize(); + if (wchunk_size == 0 || wchunk_size > totalSize - total_written) { + wchunk_size = totalSize - total_written; + } + + // Make sure (total_written - total_read) + wchunk_size + // is less than maxOutstanding + if (maxOutstanding > 0 && wchunk_size > maxOutstanding - (total_written - total_read)) { + wchunk_size = maxOutstanding - (total_written - total_read); + } + + // Write the chunk + uint32_t chunk_written = 0; + while (chunk_written < wchunk_size) { + uint32_t write_size = wSizeGenerator.nextSize(); + if (write_size == 0 || write_size > wchunk_size - chunk_written) { + write_size = wchunk_size - chunk_written; + } + + try { + transports.out->write(wbuf.get() + total_written, write_size); + } catch (TTransportException& te) { + if (te.getType() == TTransportException::TIMED_OUT) + break; + throw te; + } + chunk_written += write_size; + total_written += write_size; + } + + // Flush the data, so it will be available in the read transport + // Don't flush if wchunk_size is 0. (This should only happen if + // total_written == totalSize already, and we're only reading now.) + if (wchunk_size > 0) { + transports.out->flush(); + } + + // Determine how large a chunk of data to read back + uint32_t rchunk_size = rChunkGenerator.nextSize(); + if (rchunk_size == 0 || rchunk_size > total_written - total_read) { + rchunk_size = total_written - total_read; + } + + // Read the chunk + uint32_t chunk_read = 0; + while (chunk_read < rchunk_size) { + uint32_t read_size = rSizeGenerator.nextSize(); + if (read_size == 0 || read_size > rchunk_size - chunk_read) { + read_size = rchunk_size - chunk_read; + } + + int bytes_read = -1; + try { + bytes_read = transports.in->read(rbuf.get() + total_read, read_size); + } catch (TTransportException& e) { + BOOST_FAIL("read(pos=" << total_read << ", size=" << read_size << ") threw exception \"" + << e.what() << "\"; written so far: " << total_written << " / " + << totalSize << " bytes"); + } + + BOOST_REQUIRE_MESSAGE(bytes_read > 0, + "read(pos=" << total_read << ", size=" << read_size << ") returned " + << bytes_read << "; written so far: " << total_written + << " / " << totalSize << " bytes"); + chunk_read += bytes_read; + total_read += bytes_read; + } + } + + // make sure the data read back is identical to the data written + BOOST_CHECK_EQUAL(memcmp(rbuf.get(), wbuf.get(), totalSize), 0); +} + +template +void test_read_part_available() { + CoupledTransports transports; + BOOST_REQUIRE(transports.in != nullptr); + BOOST_REQUIRE(transports.out != nullptr); + + uint8_t write_buf[16]; + uint8_t read_buf[16]; + memset(write_buf, 'a', sizeof(write_buf)); + + // Attemping to read 10 bytes when only 9 are available should return 9 + // immediately. + transports.out->write(write_buf, 9); + transports.out->flush(); + set_trigger(3, transports.out, 1); + uint32_t bytes_read = transports.in->read(read_buf, 10); + BOOST_CHECK_EQUAL(g_numTriggersFired, (unsigned int)0); + BOOST_CHECK_EQUAL(bytes_read, (uint32_t)9); + + clear_triggers(); +} + +template +void test_read_part_available_in_chunks() { + CoupledTransports transports; + BOOST_REQUIRE(transports.in != nullptr); + BOOST_REQUIRE(transports.out != nullptr); + + uint8_t write_buf[16]; + uint8_t read_buf[16]; + memset(write_buf, 'a', sizeof(write_buf)); + + // Write 10 bytes (in a single frame, for transports that use framing) + transports.out->write(write_buf, 10); + transports.out->flush(); + + // Read 1 byte, to force the transport to read the frame + uint32_t bytes_read = transports.in->read(read_buf, 1); + BOOST_CHECK_EQUAL(bytes_read, 1u); + + // Read more than what is remaining and verify the transport does not block + set_trigger(3, transports.out, 1); + bytes_read = transports.in->read(read_buf, 10); + BOOST_CHECK_EQUAL(g_numTriggersFired, 0u); + BOOST_CHECK_EQUAL(bytes_read, 9u); + + clear_triggers(); +} + +template +void test_read_partial_midframe() { + CoupledTransports transports; + BOOST_REQUIRE(transports.in != nullptr); + BOOST_REQUIRE(transports.out != nullptr); + + uint8_t write_buf[16]; + uint8_t read_buf[16]; + memset(write_buf, 'a', sizeof(write_buf)); + + // Attempt to read 10 bytes, when only 9 are available, but after we have + // already read part of the data that is available. This exercises a + // different code path for several of the transports. + // + // For transports that add their own framing (e.g., TFramedTransport and + // TFileTransport), the two flush calls break up the data in to a 10 byte + // frame and a 3 byte frame. The first read then puts us partway through the + // first frame, and then we attempt to read past the end of that frame, and + // through the next frame, too. + // + // For buffered transports that perform read-ahead (e.g., + // TBufferedTransport), the read-ahead will most likely see all 13 bytes + // written on the first read. The next read will then attempt to read past + // the end of the read-ahead buffer. + // + // Flush 10 bytes, then 3 bytes. This creates 2 separate frames for + // transports that track framing internally. + transports.out->write(write_buf, 10); + transports.out->flush(); + transports.out->write(write_buf, 3); + transports.out->flush(); + + // Now read 4 bytes, so that we are partway through the written data. + uint32_t bytes_read = transports.in->read(read_buf, 4); + BOOST_CHECK_EQUAL(bytes_read, (uint32_t)4); + + // Now attempt to read 10 bytes. Only 9 more are available. + // + // We should be able to get all 9 bytes, but it might take multiple read + // calls, since it is valid for read() to return fewer bytes than requested. + // (Most transports do immediately return 9 bytes, but the framing transports + // tend to only return to the end of the current frame, which is 6 bytes in + // this case.) + uint32_t total_read = 0; + while (total_read < 9) { + set_trigger(3, transports.out, 1); + bytes_read = transports.in->read(read_buf, 10); + BOOST_REQUIRE_EQUAL(g_numTriggersFired, (unsigned int)0); + BOOST_REQUIRE_GT(bytes_read, (uint32_t)0); + total_read += bytes_read; + BOOST_REQUIRE_LE(total_read, (uint32_t)9); + } + + BOOST_CHECK_EQUAL(total_read, (uint32_t)9); + + clear_triggers(); +} + +template +void test_borrow_part_available() { + CoupledTransports transports; + BOOST_REQUIRE(transports.in != nullptr); + BOOST_REQUIRE(transports.out != nullptr); + + uint8_t write_buf[16]; + uint8_t read_buf[16]; + memset(write_buf, 'a', sizeof(write_buf)); + + // Attemping to borrow 10 bytes when only 9 are available should return NULL + // immediately. + transports.out->write(write_buf, 9); + transports.out->flush(); + set_trigger(3, transports.out, 1); + uint32_t borrow_len = 10; + const uint8_t* borrowed_buf = transports.in->borrow(read_buf, &borrow_len); + BOOST_CHECK_EQUAL(g_numTriggersFired, (unsigned int)0); + BOOST_CHECK(borrowed_buf == nullptr); + + clear_triggers(); +} + +template +void test_read_none_available() { + CoupledTransports transports; + BOOST_REQUIRE(transports.in != nullptr); + BOOST_REQUIRE(transports.out != nullptr); + + uint8_t read_buf[16]; + + // Attempting to read when no data is available should either block until + // some data is available, or fail immediately. (e.g., TSocket blocks, + // TMemoryBuffer just fails.) + // + // If the transport blocks, it should succeed once some data is available, + // even if less than the amount requested becomes available. + set_trigger(1, transports.out, 2); + add_trigger(1, transports.out, 8); + uint32_t bytes_read = transports.in->read(read_buf, 10); + if (bytes_read == 0) { + BOOST_CHECK_EQUAL(g_numTriggersFired, (unsigned int)0); + clear_triggers(); + } else { + BOOST_CHECK_EQUAL(g_numTriggersFired, (unsigned int)1); + BOOST_CHECK_EQUAL(bytes_read, (uint32_t)2); + } + + clear_triggers(); +} + +template +void test_borrow_none_available() { + CoupledTransports transports; + BOOST_REQUIRE(transports.in != nullptr); + BOOST_REQUIRE(transports.out != nullptr); + + uint8_t write_buf[16]; + memset(write_buf, 'a', sizeof(write_buf)); + + // Attempting to borrow when no data is available should fail immediately + set_trigger(1, transports.out, 10); + uint32_t borrow_len = 10; + const uint8_t* borrowed_buf = transports.in->borrow(nullptr, &borrow_len); + BOOST_CHECK(borrowed_buf == nullptr); + BOOST_CHECK_EQUAL(g_numTriggersFired, (unsigned int)0); + + clear_triggers(); +} + +/************************************************************************** + * Test case generation + * + * Pretty ugly and annoying. This would be much easier if we the unit test + * framework didn't force each test to be a separate function. + * - Writing a completely separate function definition for each of these would + * result in a lot of repetitive boilerplate code. + * - Combining many tests into a single function makes it more difficult to + * tell precisely which tests failed. It also means you can't get a progress + * update after each test, and the tests are already fairly slow. + * - Similar registration could be achieved with BOOST_TEST_CASE_TEMPLATE, + * but it requires a lot of awkward MPL code, and results in useless test + * case names. (The names are generated from std::type_info::name(), which + * is compiler-dependent. gcc returns mangled names.) + **************************************************************************/ + +#define ADD_TEST_RW(CoupledTransports, totalSize, ...) \ + addTestRW(BOOST_STRINGIZE(CoupledTransports), totalSize, ##__VA_ARGS__); + +#define TEST_RW(CoupledTransports, totalSize, ...) \ + do { \ + /* Add the test as specified, to test the non-virtual function calls */ \ + ADD_TEST_RW(CoupledTransports, totalSize, ##__VA_ARGS__); \ + /* \ + * Also test using the transport as a TTransport*, to test \ + * the read_virt()/write_virt() calls \ + */ \ + ADD_TEST_RW(CoupledTTransports, totalSize, ##__VA_ARGS__); \ + /* Test wrapping the transport with TBufferedTransport */ \ + ADD_TEST_RW(CoupledBufferedTransportsT, totalSize, ##__VA_ARGS__); \ + /* Test wrapping the transport with TFramedTransports */ \ + ADD_TEST_RW(CoupledFramedTransportsT, totalSize, ##__VA_ARGS__); \ + /* Test wrapping the transport with TZlibTransport */ \ + ADD_TEST_RW(CoupledZlibTransportsT, totalSize, ##__VA_ARGS__); \ + } while (0) + +#define ADD_TEST_BLOCKING(CoupledTransports) \ + addTestBlocking(BOOST_STRINGIZE(CoupledTransports)); + +#define TEST_BLOCKING_BEHAVIOR(CoupledTransports) \ + ADD_TEST_BLOCKING(CoupledTransports); \ + ADD_TEST_BLOCKING(CoupledTTransports); \ + ADD_TEST_BLOCKING(CoupledBufferedTransportsT); \ + ADD_TEST_BLOCKING(CoupledFramedTransportsT); \ + ADD_TEST_BLOCKING(CoupledZlibTransportsT); + +class TransportTestGen { +public: + TransportTestGen(boost::unit_test::test_suite* suite, float sizeMultiplier) + : suite_(suite), sizeMultiplier_(sizeMultiplier) {} + + void generate() { + GenericSizeGenerator rand4k(1, 4096); + + /* + * We do the basically the same set of tests for each transport type, + * although we tweak the parameters in some places. + */ + + // TMemoryBuffer tests + TEST_RW(CoupledMemoryBuffers, 1024 * 1024, 0, 0); + TEST_RW(CoupledMemoryBuffers, 1024 * 256, rand4k, rand4k); + TEST_RW(CoupledMemoryBuffers, 1024 * 256, 167, 163); + TEST_RW(CoupledMemoryBuffers, 1024 * 16, 1, 1); + + TEST_RW(CoupledMemoryBuffers, 1024 * 256, 0, 0, rand4k, rand4k); + TEST_RW(CoupledMemoryBuffers, 1024 * 256, rand4k, rand4k, rand4k, rand4k); + TEST_RW(CoupledMemoryBuffers, 1024 * 256, 167, 163, rand4k, rand4k); + TEST_RW(CoupledMemoryBuffers, 1024 * 16, 1, 1, rand4k, rand4k); + + TEST_BLOCKING_BEHAVIOR(CoupledMemoryBuffers); + +#ifndef _WIN32 + // TFDTransport tests + // Since CoupledFDTransports tests with a pipe, writes will block + // if there is too much outstanding unread data in the pipe. + uint32_t fd_max_outstanding = 4096; + TEST_RW(CoupledFDTransports, 1024 * 1024, 0, 0, 0, 0, fd_max_outstanding); + TEST_RW(CoupledFDTransports, 1024 * 256, rand4k, rand4k, 0, 0, fd_max_outstanding); + TEST_RW(CoupledFDTransports, 1024 * 256, 167, 163, 0, 0, fd_max_outstanding); + TEST_RW(CoupledFDTransports, 1024 * 16, 1, 1, 0, 0, fd_max_outstanding); + + TEST_RW(CoupledFDTransports, 1024 * 256, 0, 0, rand4k, rand4k, fd_max_outstanding); + TEST_RW(CoupledFDTransports, 1024 * 256, rand4k, rand4k, rand4k, rand4k, fd_max_outstanding); + TEST_RW(CoupledFDTransports, 1024 * 256, 167, 163, rand4k, rand4k, fd_max_outstanding); + TEST_RW(CoupledFDTransports, 1024 * 16, 1, 1, rand4k, rand4k, fd_max_outstanding); + + TEST_BLOCKING_BEHAVIOR(CoupledFDTransports); +#else + // TPipe tests (WIN32 only) + TEST_RW(CoupledPipeTransports, 1024 * 1024, 0, 0); + TEST_RW(CoupledPipeTransports, 1024 * 256, rand4k, rand4k); + TEST_RW(CoupledPipeTransports, 1024 * 256, 167, 163); + TEST_RW(CoupledPipeTransports, 1024 * 16, 1, 1); + + TEST_RW(CoupledPipeTransports, 1024 * 256, 0, 0, rand4k, rand4k); + TEST_RW(CoupledPipeTransports, 1024 * 256, rand4k, rand4k, rand4k, rand4k); + TEST_RW(CoupledPipeTransports, 1024 * 256, 167, 163, rand4k, rand4k); + TEST_RW(CoupledPipeTransports, 1024 * 16, 1, 1, rand4k, rand4k); + + TEST_BLOCKING_BEHAVIOR(CoupledPipeTransports); +#endif //_WIN32 + + // TSocket tests + uint32_t socket_max_outstanding = 4096; + TEST_RW(CoupledSocketTransports, 1024 * 1024, 0, 0, 0, 0, socket_max_outstanding); + TEST_RW(CoupledSocketTransports, 1024 * 256, rand4k, rand4k, 0, 0, socket_max_outstanding); + TEST_RW(CoupledSocketTransports, 1024 * 256, 167, 163, 0, 0, socket_max_outstanding); + // Doh. Apparently writing to a socket has some additional overhead for + // each send() call. If we have more than ~400 outstanding 1-byte write + // requests, additional send() calls start blocking. + TEST_RW(CoupledSocketTransports, 1024 * 16, 1, 1, 0, 0, socket_max_outstanding); + TEST_RW(CoupledSocketTransports, 1024 * 256, 0, 0, rand4k, rand4k, socket_max_outstanding); + TEST_RW(CoupledSocketTransports, + 1024 * 256, + rand4k, + rand4k, + rand4k, + rand4k, + socket_max_outstanding); + TEST_RW(CoupledSocketTransports, 1024 * 256, 167, 163, rand4k, rand4k, socket_max_outstanding); + TEST_RW(CoupledSocketTransports, 1024 * 16, 1, 1, rand4k, rand4k, socket_max_outstanding); + + TEST_BLOCKING_BEHAVIOR(CoupledSocketTransports); + +// These could be made to work on Windows, but I don't care enough to make it happen +#ifndef _WIN32 + // TFileTransport tests + // We use smaller buffer sizes here, since TFileTransport is fairly slow. + // + // TFileTransport can't write more than 16MB at once + uint32_t max_write_at_once = 1024 * 1024 * 16 - 4; + TEST_RW(CoupledFileTransports, 1024 * 1024, max_write_at_once, 0); + TEST_RW(CoupledFileTransports, 1024 * 128, rand4k, rand4k); + TEST_RW(CoupledFileTransports, 1024 * 128, 167, 163); + TEST_RW(CoupledFileTransports, 1024 * 2, 1, 1); + + TEST_RW(CoupledFileTransports, 1024 * 64, 0, 0, rand4k, rand4k); + TEST_RW(CoupledFileTransports, 1024 * 64, rand4k, rand4k, rand4k, rand4k); + TEST_RW(CoupledFileTransports, 1024 * 64, 167, 163, rand4k, rand4k); + TEST_RW(CoupledFileTransports, 1024 * 2, 1, 1, rand4k, rand4k); + + TEST_BLOCKING_BEHAVIOR(CoupledFileTransports); +#endif + + // Add some tests that access TBufferedTransport and TFramedTransport + // via TTransport pointers and TBufferBase pointers. + ADD_TEST_RW(CoupledTTransports, + 1024 * 1024, + rand4k, + rand4k, + rand4k, + rand4k); + ADD_TEST_RW(CoupledBufferBases, + 1024 * 1024, + rand4k, + rand4k, + rand4k, + rand4k); + ADD_TEST_RW(CoupledTTransports, + 1024 * 1024, + rand4k, + rand4k, + rand4k, + rand4k); + ADD_TEST_RW(CoupledBufferBases, + 1024 * 1024, + rand4k, + rand4k, + rand4k, + rand4k); + + // Test using TZlibTransport via a TTransport pointer + ADD_TEST_RW(CoupledTTransports, + 1024 * 1024, + rand4k, + rand4k, + rand4k, + rand4k); + } + +#if (BOOST_VERSION >= 105900) +#define MAKE_TEST_CASE(_FUNC, _NAME) boost::unit_test::make_test_case(_FUNC, _NAME, __FILE__, __LINE__) +#else +#define MAKE_TEST_CASE(_FUNC, _NAME) boost::unit_test::make_test_case(_FUNC, _NAME) +#endif + +private: + template + void addTestRW(const char* transport_name, + uint32_t totalSize, + GenericSizeGenerator wSizeGen, + GenericSizeGenerator rSizeGen, + GenericSizeGenerator wChunkSizeGen = 0, + GenericSizeGenerator rChunkSizeGen = 0, + uint32_t maxOutstanding = 0, + uint32_t expectedFailures = 0) { + // adjust totalSize by the specified sizeMultiplier_ first + totalSize = static_cast(totalSize * sizeMultiplier_); + + std::ostringstream name; + name << transport_name << "::test_rw(" << totalSize << ", " << wSizeGen.describe() << ", " + << rSizeGen.describe() << ", " << wChunkSizeGen.describe() << ", " + << rChunkSizeGen.describe() << ", " << maxOutstanding << ")"; + +#if (BOOST_VERSION >= 105900) + std::function test_func +#else + boost::unit_test::callback0<> test_func +#endif + = std::bind(test_rw, + totalSize, + wSizeGen, + rSizeGen, + wChunkSizeGen, + rChunkSizeGen, + maxOutstanding); + suite_->add(MAKE_TEST_CASE(test_func, name.str()), expectedFailures); + } + + template + void addTestBlocking(const char* transportName, uint32_t expectedFailures = 0) { + char name[1024]; + + THRIFT_SNPRINTF(name, sizeof(name), "%s::test_read_part_available()", transportName); + suite_->add(MAKE_TEST_CASE(test_read_part_available, name), expectedFailures); + + THRIFT_SNPRINTF(name, sizeof(name), "%s::test_read_part_available_in_chunks()", transportName); + suite_->add(MAKE_TEST_CASE(test_read_part_available_in_chunks, name), expectedFailures); + + THRIFT_SNPRINTF(name, sizeof(name), "%s::test_read_partial_midframe()", transportName); + suite_->add(MAKE_TEST_CASE(test_read_partial_midframe, name), expectedFailures); + + THRIFT_SNPRINTF(name, sizeof(name), "%s::test_read_none_available()", transportName); + suite_->add(MAKE_TEST_CASE(test_read_none_available, name), expectedFailures); + + THRIFT_SNPRINTF(name, sizeof(name), "%s::test_borrow_part_available()", transportName); + suite_->add(MAKE_TEST_CASE(test_borrow_part_available, name), expectedFailures); + + THRIFT_SNPRINTF(name, sizeof(name), "%s::test_borrow_none_available()", transportName); + suite_->add(MAKE_TEST_CASE(test_borrow_none_available, name), expectedFailures); + } + + boost::unit_test::test_suite* suite_; + // sizeMultiplier_ is configurable via the command line, and allows the + // user to adjust between smaller buffers that can be tested quickly, + // or larger buffers that more thoroughly exercise the code, but take + // longer. + float sizeMultiplier_; +}; + +/************************************************************************** + * General Initialization + **************************************************************************/ + +struct global_fixture { + std::shared_ptr alarmThread_; + global_fixture() { +#if _WIN32 + apache::thrift::transport::TWinsockSingleton::create(); +#endif + + apache::thrift::concurrency::ThreadFactory factory; + factory.setDetached(false); + + alarmThread_ = factory.newThread( + apache::thrift::concurrency::FunctionRunner::create(alarm_handler_wrapper)); + alarmThread_->start(); + } + ~global_fixture() { + { + apache::thrift::concurrency::Synchronized s(g_alarm_monitor); + g_teardown = true; + g_alarm_monitor.notify(); + } + alarmThread_->join(); + } +}; + +#if (BOOST_VERSION >= 105900) +BOOST_GLOBAL_FIXTURE(global_fixture); +#else +BOOST_GLOBAL_FIXTURE(global_fixture) +#endif + +#ifdef BOOST_TEST_DYN_LINK +bool init_unit_test_suite() { + struct timeval tv; + THRIFT_GETTIMEOFDAY(&tv, nullptr); + int seed = tv.tv_sec ^ tv.tv_usec; + + initrand(seed); + + boost::unit_test::test_suite* suite = &boost::unit_test::framework::master_test_suite(); + suite->p_name.value = "TransportTest"; + TransportTestGen transport_test_generator(suite, 1); + transport_test_generator.generate(); + return true; +} + +int main( int argc, char* argv[] ) { + return ::boost::unit_test::unit_test_main(&init_unit_test_suite,argc,argv); +} +#else +boost::unit_test::test_suite* init_unit_test_suite(int argc, char* argv[]) { + THRIFT_UNUSED_VARIABLE(argc); + THRIFT_UNUSED_VARIABLE(argv); + struct timeval tv; + THRIFT_GETTIMEOFDAY(&tv, NULL); + int seed = tv.tv_sec ^ tv.tv_usec; + + initrand(seed); + + boost::unit_test::test_suite* suite = &boost::unit_test::framework::master_test_suite(); + suite->p_name.value = "TransportTest"; + TransportTestGen transport_test_generator(suite, 1); + transport_test_generator.generate(); + return NULL; +} +#endif diff --git a/src/jaegertracing/thrift/lib/cpp/test/TypedefTest.cpp b/src/jaegertracing/thrift/lib/cpp/test/TypedefTest.cpp new file mode 100644 index 000000000..24e9265e6 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/test/TypedefTest.cpp @@ -0,0 +1,28 @@ +/* + * 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. + */ + +#include +#include + +#include "gen-cpp/TypedefTest_types.h" + +BOOST_STATIC_ASSERT((boost::is_same::value)); +BOOST_STATIC_ASSERT((boost::is_same::value)); +BOOST_STATIC_ASSERT( + (boost::is_same::value)); diff --git a/src/jaegertracing/thrift/lib/cpp/test/UnitTestMain.cpp b/src/jaegertracing/thrift/lib/cpp/test/UnitTestMain.cpp new file mode 100644 index 000000000..f0ef1e4a6 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/test/UnitTestMain.cpp @@ -0,0 +1,21 @@ +/* + * 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. + */ + +#define BOOST_TEST_MODULE thrift +#include diff --git a/src/jaegertracing/thrift/lib/cpp/test/ZlibTest.cpp b/src/jaegertracing/thrift/lib/cpp/test/ZlibTest.cpp new file mode 100644 index 000000000..3e2eb816c --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/test/ZlibTest.cpp @@ -0,0 +1,475 @@ +/* + * 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. + */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE // needed for getopt_long +#endif + +#if defined(_MSC_VER) && (_MSC_VER <= 1700) +// polynomial and std::fill_t warning happens in MSVC 2010, 2013, maybe others +// https://svn.boost.org/trac/boost/ticket/11426 +#pragma warning(disable:4996) +#endif + +#ifdef HAVE_STDINT_H +#include +#endif +#ifdef HAVE_INTTYPES_H +#include +#endif +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +using namespace apache::thrift::transport; +using std::shared_ptr; +using std::string; + +boost::mt19937 rng; + +/* + * Utility code + */ + +class SizeGenerator { +public: + virtual ~SizeGenerator() = default; + virtual unsigned int getSize() = 0; +}; + +class ConstantSizeGenerator : public SizeGenerator { +public: + ConstantSizeGenerator(unsigned int value) : value_(value) {} + unsigned int getSize() override { return value_; } + +private: + unsigned int value_; +}; + +class LogNormalSizeGenerator : public SizeGenerator { +public: + LogNormalSizeGenerator(double mean, double std_dev) + : gen_(rng, boost::lognormal_distribution(mean, std_dev)) {} + + unsigned int getSize() override { + // Loop until we get a size of 1 or more + while (true) { + auto value = static_cast(gen_()); + if (value >= 1) { + return value; + } + } + } + +private: + boost::variate_generator > gen_; +}; + +boost::shared_array gen_uniform_buffer(uint32_t buf_len, uint8_t c) { + auto* buf = new uint8_t[buf_len]; + memset(buf, c, buf_len); + return boost::shared_array(buf); +} + +boost::shared_array gen_compressible_buffer(uint32_t buf_len) { + auto* buf = new uint8_t[buf_len]; + + // Generate small runs of alternately increasing and decreasing bytes + boost::uniform_smallint run_length_distribution(1, 64); + boost::uniform_smallint byte_distribution(0, UINT8_MAX); + boost::variate_generator > + byte_generator(rng, byte_distribution); + boost::variate_generator > + run_len_generator(rng, run_length_distribution); + + uint32_t idx = 0; + int8_t step = 1; + while (idx < buf_len) { + uint32_t run_length = run_len_generator(); + if (idx + run_length > buf_len) { + run_length = buf_len - idx; + } + + uint8_t byte = byte_generator(); + for (uint32_t n = 0; n < run_length; ++n) { + buf[idx] = byte; + ++idx; + byte += step; + } + + step *= -1; + } + + return boost::shared_array(buf); +} + +boost::shared_array gen_random_buffer(uint32_t buf_len) { + auto* buf = new uint8_t[buf_len]; + + boost::uniform_smallint distribution(0, UINT8_MAX); + boost::variate_generator > + generator(rng, distribution); + + for (uint32_t n = 0; n < buf_len; ++n) { + buf[n] = generator(); + } + + return boost::shared_array(buf); +} + +/* + * Test functions + */ + +void test_write_then_read(const boost::shared_array buf, uint32_t buf_len) { + shared_ptr membuf(new TMemoryBuffer()); + shared_ptr zlib_trans(new TZlibTransport(membuf)); + zlib_trans->write(buf.get(), buf_len); + zlib_trans->finish(); + + boost::shared_array mirror(new uint8_t[buf_len]); + uint32_t got = zlib_trans->readAll(mirror.get(), buf_len); + BOOST_REQUIRE_EQUAL(got, buf_len); + BOOST_CHECK_EQUAL(memcmp(mirror.get(), buf.get(), buf_len), 0); + zlib_trans->verifyChecksum(); +} + +void test_separate_checksum(const boost::shared_array buf, uint32_t buf_len) { + // This one is tricky. I separate the last byte of the stream out + // into a separate crbuf_. The last byte is part of the checksum, + // so the entire read goes fine, but when I go to verify the checksum + // it isn't there. The original implementation complained that + // the stream was not complete. I'm about to go fix that. + // It worked. Awesome. + shared_ptr membuf(new TMemoryBuffer()); + shared_ptr zlib_trans(new TZlibTransport(membuf)); + zlib_trans->write(buf.get(), buf_len); + zlib_trans->finish(); + string tmp_buf; + membuf->appendBufferToString(tmp_buf); + zlib_trans.reset(new TZlibTransport(membuf, + TZlibTransport::DEFAULT_URBUF_SIZE, + static_cast(tmp_buf.length() - 1))); + + boost::shared_array mirror(new uint8_t[buf_len]); + uint32_t got = zlib_trans->readAll(mirror.get(), buf_len); + BOOST_REQUIRE_EQUAL(got, buf_len); + BOOST_CHECK_EQUAL(memcmp(mirror.get(), buf.get(), buf_len), 0); + zlib_trans->verifyChecksum(); +} + +void test_incomplete_checksum(const boost::shared_array buf, uint32_t buf_len) { + // Make sure we still get that "not complete" error if + // it really isn't complete. + shared_ptr membuf(new TMemoryBuffer()); + shared_ptr zlib_trans(new TZlibTransport(membuf)); + zlib_trans->write(buf.get(), buf_len); + zlib_trans->finish(); + string tmp_buf; + membuf->appendBufferToString(tmp_buf); + tmp_buf.erase(tmp_buf.length() - 1); + membuf->resetBuffer(const_cast(reinterpret_cast(tmp_buf.data())), + static_cast(tmp_buf.length())); + + boost::shared_array mirror(new uint8_t[buf_len]); + uint32_t got = zlib_trans->readAll(mirror.get(), buf_len); + BOOST_REQUIRE_EQUAL(got, buf_len); + BOOST_CHECK_EQUAL(memcmp(mirror.get(), buf.get(), buf_len), 0); + try { + zlib_trans->verifyChecksum(); + BOOST_ERROR("verifyChecksum() did not report an error"); + } catch (TTransportException& ex) { + BOOST_CHECK_EQUAL(ex.getType(), TTransportException::CORRUPTED_DATA); + } +} + +void test_read_write_mix(const boost::shared_array buf, + uint32_t buf_len, + const shared_ptr& write_gen, + const shared_ptr& read_gen) { + // Try it with a mix of read/write sizes. + shared_ptr membuf(new TMemoryBuffer()); + shared_ptr zlib_trans(new TZlibTransport(membuf)); + unsigned int tot; + + tot = 0; + while (tot < buf_len) { + uint32_t write_len = write_gen->getSize(); + if (tot + write_len > buf_len) { + write_len = buf_len - tot; + } + zlib_trans->write(buf.get() + tot, write_len); + tot += write_len; + } + + zlib_trans->finish(); + + tot = 0; + boost::shared_array mirror(new uint8_t[buf_len]); + while (tot < buf_len) { + uint32_t read_len = read_gen->getSize(); + uint32_t expected_read_len = read_len; + if (tot + read_len > buf_len) { + expected_read_len = buf_len - tot; + } + uint32_t got = zlib_trans->read(mirror.get() + tot, read_len); + BOOST_REQUIRE_LE(got, expected_read_len); + BOOST_REQUIRE_NE(got, (uint32_t)0); + tot += got; + } + + BOOST_CHECK_EQUAL(memcmp(mirror.get(), buf.get(), buf_len), 0); + zlib_trans->verifyChecksum(); +} + +void test_invalid_checksum(const boost::shared_array buf, uint32_t buf_len) { + // Verify checksum checking. + shared_ptr membuf(new TMemoryBuffer()); + shared_ptr zlib_trans(new TZlibTransport(membuf)); + zlib_trans->write(buf.get(), buf_len); + zlib_trans->finish(); + string tmp_buf; + membuf->appendBufferToString(tmp_buf); + // Modify a byte at the end of the buffer (part of the checksum). + // On rare occasions, modifying a byte in the middle of the buffer + // isn't caught by the checksum. + // + // (This happens especially often for the uniform buffer. The + // re-inflated data is correct, however. I suspect in this case that + // we're more likely to modify bytes that are part of zlib metadata + // instead of the actual compressed data.) + // + // I've also seen some failure scenarios where a checksum failure isn't + // reported, but zlib keeps trying to decode past the end of the data. + // (When this occurs, verifyChecksum() throws an exception indicating + // that the end of the data hasn't been reached.) I haven't seen this + // error when only modifying checksum bytes. + int index = static_cast(tmp_buf.size() - 1); + tmp_buf[index]++; + membuf->resetBuffer(const_cast(reinterpret_cast(tmp_buf.data())), + static_cast(tmp_buf.length())); + + boost::shared_array mirror(new uint8_t[buf_len]); + try { + zlib_trans->readAll(mirror.get(), buf_len); + zlib_trans->verifyChecksum(); + BOOST_ERROR("verifyChecksum() did not report an error"); + } catch (TZlibTransportException& ex) { + BOOST_CHECK_EQUAL(ex.getType(), TTransportException::INTERNAL_ERROR); + } +} + +void test_write_after_flush(const boost::shared_array buf, uint32_t buf_len) { + // write some data + shared_ptr membuf(new TMemoryBuffer()); + shared_ptr zlib_trans(new TZlibTransport(membuf)); + zlib_trans->write(buf.get(), buf_len); + + // call finish() + zlib_trans->finish(); + + // make sure write() throws an error + try { + uint8_t write_buf[] = "a"; + zlib_trans->write(write_buf, 1); + BOOST_ERROR("write() after finish() did not raise an exception"); + } catch (TTransportException& ex) { + BOOST_CHECK_EQUAL(ex.getType(), TTransportException::BAD_ARGS); + } + + // make sure flush() throws an error + try { + zlib_trans->flush(); + BOOST_ERROR("flush() after finish() did not raise an exception"); + } catch (TTransportException& ex) { + BOOST_CHECK_EQUAL(ex.getType(), TTransportException::BAD_ARGS); + } + + // make sure finish() throws an error + try { + zlib_trans->finish(); + BOOST_ERROR("finish() after finish() did not raise an exception"); + } catch (TTransportException& ex) { + BOOST_CHECK_EQUAL(ex.getType(), TTransportException::BAD_ARGS); + } +} + +void test_no_write() { + // Verify that no data is written to the underlying transport if we + // never write data to the TZlibTransport. + shared_ptr membuf(new TMemoryBuffer()); + { + // Create a TZlibTransport object, and immediately destroy it + // when it goes out of scope. + TZlibTransport w_zlib_trans(membuf); + } + + BOOST_CHECK_EQUAL(membuf->available_read(), (uint32_t)0); +} + +void test_get_underlying_transport() { + shared_ptr membuf(new TMemoryBuffer()); + shared_ptr zlib_trans(new TZlibTransport(membuf)); + BOOST_CHECK_EQUAL(membuf.get(), zlib_trans->getUnderlyingTransport().get()); +} + +/* + * Initialization + */ + +#if (BOOST_VERSION >= 105900) +#define ADD_TEST_CASE(suite, name, _FUNC, ...) \ + do { \ + ::std::ostringstream name_ss; \ + name_ss << name << "-" << BOOST_STRINGIZE(_FUNC); \ + ::std::function test_func = \ + ::std::bind(_FUNC, ##__VA_ARGS__); \ + ::boost::unit_test::test_case* tc \ + = ::boost::unit_test::make_test_case(test_func, name_ss.str(), __FILE__, __LINE__); \ + (suite)->add(tc); \ + } while (0) +#else +#define ADD_TEST_CASE(suite, name, _FUNC, ...) \ + do { \ + ::std::ostringstream name_ss; \ + name_ss << name << "-" << BOOST_STRINGIZE(_FUNC); \ + ::boost::unit_test::test_case* tc \ + = ::boost::unit_test::make_test_case(::std::bind(_FUNC, \ + ##__VA_ARGS__), \ + name_ss.str()); \ + (suite)->add(tc); \ + } while (0) +#endif + +void add_tests(boost::unit_test::test_suite* suite, + const boost::shared_array& buf, + uint32_t buf_len, + const char* name) { + ADD_TEST_CASE(suite, name, test_write_then_read, buf, buf_len); + ADD_TEST_CASE(suite, name, test_separate_checksum, buf, buf_len); + ADD_TEST_CASE(suite, name, test_incomplete_checksum, buf, buf_len); + ADD_TEST_CASE(suite, name, test_invalid_checksum, buf, buf_len); + ADD_TEST_CASE(suite, name, test_write_after_flush, buf, buf_len); + + shared_ptr size_32k(new ConstantSizeGenerator(1 << 15)); + shared_ptr size_lognormal(new LogNormalSizeGenerator(20, 30)); + ADD_TEST_CASE(suite, name << "-constant", test_read_write_mix, buf, buf_len, size_32k, size_32k); + ADD_TEST_CASE(suite, + name << "-lognormal-write", + test_read_write_mix, + buf, + buf_len, + size_lognormal, + size_32k); + ADD_TEST_CASE(suite, + name << "-lognormal-read", + test_read_write_mix, + buf, + buf_len, + size_32k, + size_lognormal); + ADD_TEST_CASE(suite, + name << "-lognormal-both", + test_read_write_mix, + buf, + buf_len, + size_lognormal, + size_lognormal); + + // Test with a random size distribution, + // but use the exact same distribution for reading as for writing. + // + // Because the SizeGenerator makes a copy of the random number generator, + // both SizeGenerators should return the exact same set of values, since they + // both start with random number generators in the same state. + shared_ptr write_size_gen(new LogNormalSizeGenerator(20, 30)); + shared_ptr read_size_gen(new LogNormalSizeGenerator(20, 30)); + ADD_TEST_CASE(suite, + name << "-lognormal-same-distribution", + test_read_write_mix, + buf, + buf_len, + write_size_gen, + read_size_gen); +} + +void print_usage(FILE* f, const char* argv0) { + fprintf(f, "Usage: %s [boost_options] [options]\n", argv0); + fprintf(f, "Options:\n"); + fprintf(f, " --seed=, -s \n"); + fprintf(f, " --help\n"); +} + +#ifdef BOOST_TEST_DYN_LINK +bool init_unit_test_suite() { + auto seed = static_cast(time(nullptr)); +#ifdef HAVE_INTTYPES_H + printf("seed: %" PRIu32 "\n", seed); +#endif + rng.seed(seed); + + boost::unit_test::test_suite* suite = &boost::unit_test::framework::master_test_suite(); + suite->p_name.value = "ZlibTest"; + + uint32_t buf_len = 1024 * 32; + add_tests(suite, gen_uniform_buffer(buf_len, 'a'), buf_len, "uniform"); + add_tests(suite, gen_compressible_buffer(buf_len), buf_len, "compressible"); + add_tests(suite, gen_random_buffer(buf_len), buf_len, "random"); + + suite->add(BOOST_TEST_CASE(test_no_write)); + suite->add(BOOST_TEST_CASE(test_get_underlying_transport)); + + return true; +} + +int main( int argc, char* argv[] ) { + return ::boost::unit_test::unit_test_main(&init_unit_test_suite,argc,argv); +} +#else +boost::unit_test::test_suite* init_unit_test_suite(int argc, char* argv[]) { + THRIFT_UNUSED_VARIABLE(argc); + THRIFT_UNUSED_VARIABLE(argv); + uint32_t seed = static_cast(time(NULL)); +#ifdef HAVE_INTTYPES_H + printf("seed: %" PRIu32 "\n", seed); +#endif + rng.seed(seed); + + boost::unit_test::test_suite* suite = &boost::unit_test::framework::master_test_suite(); + suite->p_name.value = "ZlibTest"; + + uint32_t buf_len = 1024 * 32; + add_tests(suite, gen_uniform_buffer(buf_len, 'a'), buf_len, "uniform"); + add_tests(suite, gen_compressible_buffer(buf_len), buf_len, "compressible"); + add_tests(suite, gen_random_buffer(buf_len), buf_len, "random"); + + suite->add(BOOST_TEST_CASE(test_no_write)); + + return NULL; +} +#endif diff --git a/src/jaegertracing/thrift/lib/cpp/test/concurrency/Tests.cpp b/src/jaegertracing/thrift/lib/cpp/test/concurrency/Tests.cpp new file mode 100644 index 000000000..8c734c2d5 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/test/concurrency/Tests.cpp @@ -0,0 +1,224 @@ +/* + * 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. + */ + +#include +#include +#include + +#include "ThreadFactoryTests.h" +#include "TimerManagerTests.h" +#include "ThreadManagerTests.h" + +// The test weight, where 10 is 10 times more threads than baseline +// and the baseline is optimized for running in valgrind +static int WEIGHT = 10; + +int main(int argc, char** argv) { + + std::vector args(argc - 1 > 1 ? argc - 1 : 1); + + args[0] = "all"; + + for (int ix = 1; ix < argc; ix++) { + args[ix - 1] = std::string(argv[ix]); + } + + if (getenv("VALGRIND") != nullptr) { + // lower the scale of every test + WEIGHT = 1; + } + + bool runAll = args[0].compare("all") == 0; + + if (runAll || args[0].compare("thread-factory") == 0) { + + ThreadFactoryTests threadFactoryTests; + + std::cout << "ThreadFactory tests..." << std::endl; + + int reapLoops = 2 * WEIGHT; + int reapCount = 100 * WEIGHT; + size_t floodLoops = 3; + size_t floodCount = 500 * WEIGHT; + + std::cout << "\t\tThreadFactory reap N threads test: N = " << reapLoops << "x" << reapCount << std::endl; + + if (!threadFactoryTests.reapNThreads(reapLoops, reapCount)) { + std::cerr << "\t\ttThreadFactory reap N threads FAILED" << std::endl; + return 1; + } + + std::cout << "\t\tThreadFactory flood N threads test: N = " << floodLoops << "x" << floodCount << std::endl; + + if (!threadFactoryTests.floodNTest(floodLoops, floodCount)) { + std::cerr << "\t\ttThreadFactory flood N threads FAILED" << std::endl; + return 1; + } + + std::cout << "\t\tThreadFactory synchronous start test" << std::endl; + + if (!threadFactoryTests.synchStartTest()) { + std::cerr << "\t\ttThreadFactory synchronous start FAILED" << std::endl; + return 1; + } + + std::cout << "\t\tThreadFactory monitor timeout test" << std::endl; + + if (!threadFactoryTests.monitorTimeoutTest()) { + std::cerr << "\t\ttThreadFactory monitor timeout FAILED" << std::endl; + return 1; + } + } + + if (runAll || args[0].compare("util") == 0) { + + std::cout << "Util tests..." << std::endl; + + std::cout << "\t\tUtil minimum time" << std::endl; + + int64_t time00 = std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count(); + int64_t time01 = std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count(); + + std::cout << "\t\t\tMinimum time: " << time01 - time00 << "ms" << std::endl; + + time00 = std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count(); + time01 = time00; + size_t count = 0; + + while (time01 < time00 + 10) { + count++; + time01 = std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count(); + } + + std::cout << "\t\t\tscall per ms: " << count / (time01 - time00) << std::endl; + } + + if (runAll || args[0].compare("timer-manager") == 0) { + + std::cout << "TimerManager tests..." << std::endl; + + std::cout << "\t\tTimerManager test00" << std::endl; + + TimerManagerTests timerManagerTests; + + if (!timerManagerTests.test00()) { + std::cerr << "\t\tTimerManager tests FAILED" << std::endl; + return 1; + } + + std::cout << "\t\tTimerManager test01" << std::endl; + + if (!timerManagerTests.test01()) { + std::cerr << "\t\tTimerManager tests FAILED" << std::endl; + return 1; + } + + std::cout << "\t\tTimerManager test02" << std::endl; + + if (!timerManagerTests.test02()) { + std::cerr << "\t\tTimerManager tests FAILED" << std::endl; + return 1; + } + + std::cout << "\t\tTimerManager test03" << std::endl; + + if (!timerManagerTests.test03()) { + std::cerr << "\t\tTimerManager tests FAILED" << std::endl; + return 1; + } + + std::cout << "\t\tTimerManager test04" << std::endl; + + if (!timerManagerTests.test04()) { + std::cerr << "\t\tTimerManager tests FAILED" << std::endl; + return 1; + } + } + + if (runAll || args[0].compare("thread-manager") == 0) { + + std::cout << "ThreadManager tests..." << std::endl; + + { + size_t workerCount = 10 * WEIGHT; + size_t taskCount = 500 * WEIGHT; + int64_t delay = 10LL; + + ThreadManagerTests threadManagerTests; + + std::cout << "\t\tThreadManager api test:" << std::endl; + + if (!threadManagerTests.apiTest()) { + std::cerr << "\t\tThreadManager apiTest FAILED" << std::endl; + return 1; + } + + std::cout << "\t\tThreadManager load test: worker count: " << workerCount + << " task count: " << taskCount << " delay: " << delay << std::endl; + + if (!threadManagerTests.loadTest(taskCount, delay, workerCount)) { + std::cerr << "\t\tThreadManager loadTest FAILED" << std::endl; + return 1; + } + + std::cout << "\t\tThreadManager block test: worker count: " << workerCount + << " delay: " << delay << std::endl; + + if (!threadManagerTests.blockTest(delay, workerCount)) { + std::cerr << "\t\tThreadManager blockTest FAILED" << std::endl; + return 1; + } + } + } + + if (runAll || args[0].compare("thread-manager-benchmark") == 0) { + + std::cout << "ThreadManager benchmark tests..." << std::endl; + + { + + size_t minWorkerCount = 2; + + size_t maxWorkerCount = 8; + + size_t tasksPerWorker = 100 * WEIGHT; + + int64_t delay = 5LL; + + for (size_t workerCount = minWorkerCount; workerCount <= maxWorkerCount; workerCount *= 4) { + + size_t taskCount = workerCount * tasksPerWorker; + + std::cout << "\t\tThreadManager load test: worker count: " << workerCount + << " task count: " << taskCount << " delay: " << delay << std::endl; + + ThreadManagerTests threadManagerTests; + + if (!threadManagerTests.loadTest(taskCount, delay, workerCount)) + { + std::cerr << "\t\tThreadManager loadTest FAILED" << std::endl; + return 1; + } + } + } + } + + std::cout << "ALL TESTS PASSED" << std::endl; + return 0; +} diff --git a/src/jaegertracing/thrift/lib/cpp/test/concurrency/ThreadFactoryTests.h b/src/jaegertracing/thrift/lib/cpp/test/concurrency/ThreadFactoryTests.h new file mode 100644 index 000000000..febe3f8b3 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/test/concurrency/ThreadFactoryTests.h @@ -0,0 +1,308 @@ +/* + * 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. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace apache { +namespace thrift { +namespace concurrency { +namespace test { + +using std::shared_ptr; +using namespace apache::thrift::concurrency; + +/** + * ThreadManagerTests class + * + * @version $Id:$ + */ +class ThreadFactoryTests { + +public: + /** + * Reap N threads + */ + class ReapNTask : public Runnable { + + public: + ReapNTask(Monitor& monitor, int& activeCount) : _monitor(monitor), _count(activeCount) {} + + void run() override { + Synchronized s(_monitor); + + if (--_count == 0) { + _monitor.notify(); + } + } + + Monitor& _monitor; + int& _count; + }; + + bool reapNThreads(int loop = 1, int count = 10) { + + ThreadFactory threadFactory = ThreadFactory(); + shared_ptr monitor(new Monitor); + + for (int lix = 0; lix < loop; lix++) { + + int activeCount = 0; + + std::vector > threads; + int tix; + + for (tix = 0; tix < count; tix++) { + try { + ++activeCount; + threads.push_back( + threadFactory.newThread(shared_ptr(new ReapNTask(*monitor, activeCount)))); + } catch (SystemResourceException& e) { + std::cout << "\t\t\tfailed to create " << lix* count + tix << " thread " << e.what() + << std::endl; + throw e; + } + } + + tix = 0; + for (std::vector >::const_iterator thread = threads.begin(); + thread != threads.end(); + tix++, ++thread) { + + try { + (*thread)->start(); + } catch (SystemResourceException& e) { + std::cout << "\t\t\tfailed to start " << lix* count + tix << " thread " << e.what() + << std::endl; + throw e; + } + } + + { + Synchronized s(*monitor); + while (activeCount > 0) { + monitor->wait(1000); + } + } + + std::cout << "\t\t\treaped " << lix* count << " threads" << std::endl; + } + + std::cout << "\t\t\tSuccess!" << std::endl; + return true; + } + + class SynchStartTask : public Runnable { + + public: + enum STATE { UNINITIALIZED, STARTING, STARTED, STOPPING, STOPPED }; + + SynchStartTask(Monitor& monitor, volatile STATE& state) : _monitor(monitor), _state(state) {} + + void run() override { + { + Synchronized s(_monitor); + if (_state == SynchStartTask::STARTING) { + _state = SynchStartTask::STARTED; + _monitor.notify(); + } + } + + { + Synchronized s(_monitor); + while (_state == SynchStartTask::STARTED) { + _monitor.wait(); + } + + if (_state == SynchStartTask::STOPPING) { + _state = SynchStartTask::STOPPED; + _monitor.notifyAll(); + } + } + } + + private: + Monitor& _monitor; + volatile STATE& _state; + }; + + bool synchStartTest() { + + Monitor monitor; + + SynchStartTask::STATE state = SynchStartTask::UNINITIALIZED; + + shared_ptr task + = shared_ptr(new SynchStartTask(monitor, state)); + + ThreadFactory threadFactory = ThreadFactory(); + + shared_ptr thread = threadFactory.newThread(task); + + if (state == SynchStartTask::UNINITIALIZED) { + + state = SynchStartTask::STARTING; + + thread->start(); + } + + { + Synchronized s(monitor); + while (state == SynchStartTask::STARTING) { + monitor.wait(); + } + } + + assert(state != SynchStartTask::STARTING); + + { + Synchronized s(monitor); + + try { + monitor.wait(100); + } catch (TimedOutException&) { + } + + if (state == SynchStartTask::STARTED) { + + state = SynchStartTask::STOPPING; + + monitor.notify(); + } + + while (state == SynchStartTask::STOPPING) { + monitor.wait(); + } + } + + assert(state == SynchStartTask::STOPPED); + + bool success = true; + + std::cout << "\t\t\t" << (success ? "Success" : "Failure") << "!" << std::endl; + + return true; + } + + /** + * The only guarantee a monitor timeout can give you is that + * it will take "at least" as long as the timeout, no less. + * There is absolutely no guarantee around regaining execution + * near the timeout. On a busy system (like inside a third party + * CI environment) it could take quite a bit longer than the + * requested timeout, and that's ok. + */ + + bool monitorTimeoutTest(int64_t count = 1000, int64_t timeout = 2) { + + Monitor monitor; + + int64_t startTime = std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count(); + + for (int64_t ix = 0; ix < count; ix++) { + { + Synchronized s(monitor); + try { + monitor.wait(timeout); + } catch (TimedOutException&) { + } + } + } + + int64_t endTime = std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count(); + + bool success = (endTime - startTime) >= (count * timeout); + + std::cout << "\t\t\t" << (success ? "Success" : "Failure") + << ": minimum required time to elapse " << count * timeout + << "ms; actual elapsed time " << endTime - startTime << "ms" + << std::endl; + + return success; + } + + class FloodTask : public Runnable { + public: + FloodTask(const size_t id, Monitor& mon) : _id(id), _mon(mon) {} + ~FloodTask() override { + if (_id % 10000 == 0) { + Synchronized sync(_mon); + std::cout << "\t\tthread " << _id << " done" << std::endl; + } + } + + void run() override { + if (_id % 10000 == 0) { + Synchronized sync(_mon); + std::cout << "\t\tthread " << _id << " started" << std::endl; + } + } + const size_t _id; + Monitor& _mon; + }; + + void foo(ThreadFactory* tf) { (void)tf; } + + bool floodNTest(size_t loop = 1, size_t count = 100000) { + + bool success = false; + Monitor mon; + + for (size_t lix = 0; lix < loop; lix++) { + + ThreadFactory threadFactory = ThreadFactory(); + threadFactory.setDetached(true); + + for (size_t tix = 0; tix < count; tix++) { + + try { + + shared_ptr task(new FloodTask(lix * count + tix, mon)); + shared_ptr thread = threadFactory.newThread(task); + thread->start(); + + } catch (TException& e) { + + std::cout << "\t\t\tfailed to start " << lix* count + tix << " thread " << e.what() + << std::endl; + + return success; + } + } + + Synchronized sync(mon); + std::cout << "\t\t\tflooded " << (lix + 1) * count << " threads" << std::endl; + success = true; + } + + return success; + } +}; + +} +} +} +} // apache::thrift::concurrency::test diff --git a/src/jaegertracing/thrift/lib/cpp/test/concurrency/ThreadManagerTests.h b/src/jaegertracing/thrift/lib/cpp/test/concurrency/ThreadManagerTests.h new file mode 100644 index 000000000..fee7c7c51 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/test/concurrency/ThreadManagerTests.h @@ -0,0 +1,639 @@ +/* + * 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. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace apache { +namespace thrift { +namespace concurrency { +namespace test { + +using namespace apache::thrift::concurrency; + +static std::deque > m_expired; +static void expiredNotifier(std::shared_ptr runnable) +{ + m_expired.push_back(runnable); +} + +static void sleep_(int64_t millisec) { + Monitor _sleep; + Synchronized s(_sleep); + + try { + _sleep.wait(millisec); + } catch (TimedOutException&) { + ; + } catch (...) { + assert(0); + } +} + +class ThreadManagerTests { + +public: + class Task : public Runnable { + + public: + Task(Monitor& monitor, size_t& count, int64_t timeout) + : _monitor(monitor), _count(count), _timeout(timeout), _startTime(0), _endTime(0), _done(false) {} + + void run() override { + + _startTime = std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count(); + + sleep_(_timeout); + + _endTime = std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count(); + + _done = true; + + { + Synchronized s(_monitor); + + // std::cout << "Thread " << _count << " completed " << std::endl; + + _count--; + if (_count % 10000 == 0) { + _monitor.notify(); + } + } + } + + Monitor& _monitor; + size_t& _count; + int64_t _timeout; + int64_t _startTime; + int64_t _endTime; + bool _done; + Monitor _sleep; + }; + + /** + * Dispatch count tasks, each of which blocks for timeout milliseconds then + * completes. Verify that all tasks completed and that thread manager cleans + * up properly on delete. + */ + bool loadTest(size_t count = 100, int64_t timeout = 100LL, size_t workerCount = 4) { + + Monitor monitor; + + size_t activeCount = count; + + shared_ptr threadManager = ThreadManager::newSimpleThreadManager(workerCount); + + shared_ptr threadFactory + = shared_ptr(new ThreadFactory(false)); + + threadManager->threadFactory(threadFactory); + + threadManager->start(); + + std::set > tasks; + + for (size_t ix = 0; ix < count; ix++) { + + tasks.insert(shared_ptr( + new ThreadManagerTests::Task(monitor, activeCount, timeout))); + } + + int64_t time00 = std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count(); + + for (auto ix = tasks.begin(); + ix != tasks.end(); + ix++) { + + threadManager->add(*ix); + } + + std::cout << "\t\t\t\tloaded " << count << " tasks to execute" << std::endl; + + { + Synchronized s(monitor); + + while (activeCount > 0) { + std::cout << "\t\t\t\tactiveCount = " << activeCount << std::endl; + monitor.wait(); + } + } + + int64_t time01 = std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count(); + + int64_t firstTime = 9223372036854775807LL; + int64_t lastTime = 0; + + double averageTime = 0; + int64_t minTime = 9223372036854775807LL; + int64_t maxTime = 0; + + for (auto ix = tasks.begin(); + ix != tasks.end(); + ix++) { + + shared_ptr task = *ix; + + int64_t delta = task->_endTime - task->_startTime; + + assert(delta > 0); + + if (task->_startTime < firstTime) { + firstTime = task->_startTime; + } + + if (task->_endTime > lastTime) { + lastTime = task->_endTime; + } + + if (delta < minTime) { + minTime = delta; + } + + if (delta > maxTime) { + maxTime = delta; + } + + averageTime += delta; + } + + averageTime /= count; + + std::cout << "\t\t\tfirst start: " << firstTime << " Last end: " << lastTime + << " min: " << minTime << "ms max: " << maxTime << "ms average: " << averageTime + << "ms" << std::endl; + + bool success = (time01 - time00) >= ((int64_t)count * timeout) / (int64_t)workerCount; + + std::cout << "\t\t\t" << (success ? "Success" : "Failure") + << "! expected time: " << ((int64_t)count * timeout) / (int64_t)workerCount << "ms elapsed time: " << time01 - time00 + << "ms" << std::endl; + + return success; + } + + class BlockTask : public Runnable { + + public: + BlockTask(Monitor& entryMonitor, Monitor& blockMonitor, bool& blocked, Monitor& doneMonitor, size_t& count) + : _entryMonitor(entryMonitor), _entered(false), _blockMonitor(blockMonitor), _blocked(blocked), _doneMonitor(doneMonitor), _count(count) {} + + void run() override { + { + Synchronized s(_entryMonitor); + _entered = true; + _entryMonitor.notify(); + } + + { + Synchronized s(_blockMonitor); + while (_blocked) { + _blockMonitor.wait(); + } + } + + { + Synchronized s(_doneMonitor); + if (--_count == 0) { + _doneMonitor.notify(); + } + } + } + + Monitor& _entryMonitor; + bool _entered; + Monitor& _blockMonitor; + bool& _blocked; + Monitor& _doneMonitor; + size_t& _count; + }; + + /** + * Block test. Create pendingTaskCountMax tasks. Verify that we block adding the + * pendingTaskCountMax + 1th task. Verify that we unblock when a task completes */ + + bool blockTest(int64_t timeout = 100LL, size_t workerCount = 2) { + (void)timeout; + bool success = false; + + try { + + Monitor entryMonitor; // not used by this test + Monitor blockMonitor; + bool blocked[] = {true, true, true}; + Monitor doneMonitor; + + size_t pendingTaskMaxCount = workerCount; + + size_t activeCounts[] = {workerCount, pendingTaskMaxCount, 1}; + + shared_ptr threadManager + = ThreadManager::newSimpleThreadManager(workerCount, pendingTaskMaxCount); + + shared_ptr threadFactory + = shared_ptr(new ThreadFactory()); + + threadManager->threadFactory(threadFactory); + + threadManager->start(); + + std::vector > tasks; + tasks.reserve(workerCount + pendingTaskMaxCount); + + for (size_t ix = 0; ix < workerCount; ix++) { + + tasks.push_back(shared_ptr( + new ThreadManagerTests::BlockTask(entryMonitor, blockMonitor, blocked[0], doneMonitor, activeCounts[0]))); + } + + for (size_t ix = 0; ix < pendingTaskMaxCount; ix++) { + + tasks.push_back(shared_ptr( + new ThreadManagerTests::BlockTask(entryMonitor, blockMonitor, blocked[1], doneMonitor, activeCounts[1]))); + } + + for (auto ix = tasks.begin(); + ix != tasks.end(); + ix++) { + threadManager->add(*ix); + } + + if (!(success = (threadManager->totalTaskCount() == pendingTaskMaxCount + workerCount))) { + throw TException("Unexpected pending task count"); + } + + shared_ptr extraTask( + new ThreadManagerTests::BlockTask(entryMonitor, blockMonitor, blocked[2], doneMonitor, activeCounts[2])); + + try { + threadManager->add(extraTask, 1); + throw TException("Unexpected success adding task in excess of pending task count"); + } catch (TooManyPendingTasksException&) { + throw TException("Should have timed out adding task in excess of pending task count"); + } catch (TimedOutException&) { + // Expected result + } + + try { + threadManager->add(extraTask, -1); + throw TException("Unexpected success adding task in excess of pending task count"); + } catch (TimedOutException&) { + throw TException("Unexpected timeout adding task in excess of pending task count"); + } catch (TooManyPendingTasksException&) { + // Expected result + } + + std::cout << "\t\t\t" + << "Pending tasks " << threadManager->pendingTaskCount() << std::endl; + + { + Synchronized s(blockMonitor); + blocked[0] = false; + blockMonitor.notifyAll(); + } + + { + Synchronized s(doneMonitor); + while (activeCounts[0] != 0) { + doneMonitor.wait(); + } + } + + std::cout << "\t\t\t" + << "Pending tasks " << threadManager->pendingTaskCount() << std::endl; + + try { + threadManager->add(extraTask, 1); + } catch (TimedOutException&) { + std::cout << "\t\t\t" + << "add timed out unexpectedly" << std::endl; + throw TException("Unexpected timeout adding task"); + + } catch (TooManyPendingTasksException&) { + std::cout << "\t\t\t" + << "add encountered too many pending exepctions" << std::endl; + throw TException("Unexpected timeout adding task"); + } + + // Wake up tasks that were pending before and wait for them to complete + + { + Synchronized s(blockMonitor); + blocked[1] = false; + blockMonitor.notifyAll(); + } + + { + Synchronized s(doneMonitor); + while (activeCounts[1] != 0) { + doneMonitor.wait(); + } + } + + // Wake up the extra task and wait for it to complete + + { + Synchronized s(blockMonitor); + blocked[2] = false; + blockMonitor.notifyAll(); + } + + { + Synchronized s(doneMonitor); + while (activeCounts[2] != 0) { + doneMonitor.wait(); + } + } + + threadManager->stop(); + + if (!(success = (threadManager->totalTaskCount() == 0))) { + throw TException("Unexpected total task count"); + } + + } catch (TException& e) { + std::cout << "ERROR: " << e.what() << std::endl; + } + + std::cout << "\t\t\t" << (success ? "Success" : "Failure") << std::endl; + return success; + } + + + bool apiTest() { + + // prove currentTime has milliseconds granularity since many other things depend on it + int64_t a = std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count(); + sleep_(100); + int64_t b = std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count(); + if (b - a < 50 || b - a > 150) { + std::cerr << "\t\t\texpected 100ms gap, found " << (b-a) << "ms gap instead." << std::endl; + return false; + } + + return apiTestWithThreadFactory(shared_ptr(new ThreadFactory())); + + } + + bool apiTestWithThreadFactory(shared_ptr threadFactory) + { + shared_ptr threadManager = ThreadManager::newSimpleThreadManager(1); + threadManager->threadFactory(threadFactory); + + std::cout << "\t\t\t\tstarting.. " << std::endl; + + threadManager->start(); + threadManager->setExpireCallback(expiredNotifier); // std::bind(&ThreadManagerTests::expiredNotifier, this)); + +#define EXPECT(FUNC, COUNT) { size_t c = FUNC; if (c != COUNT) { std::cerr << "expected " #FUNC" to be " #COUNT ", but was " << c << std::endl; return false; } } + + EXPECT(threadManager->workerCount(), 1); + EXPECT(threadManager->idleWorkerCount(), 1); + EXPECT(threadManager->pendingTaskCount(), 0); + + std::cout << "\t\t\t\tadd 2nd worker.. " << std::endl; + + threadManager->addWorker(); + + EXPECT(threadManager->workerCount(), 2); + EXPECT(threadManager->idleWorkerCount(), 2); + EXPECT(threadManager->pendingTaskCount(), 0); + + std::cout << "\t\t\t\tremove 2nd worker.. " << std::endl; + + threadManager->removeWorker(); + + EXPECT(threadManager->workerCount(), 1); + EXPECT(threadManager->idleWorkerCount(), 1); + EXPECT(threadManager->pendingTaskCount(), 0); + + std::cout << "\t\t\t\tremove 1st worker.. " << std::endl; + + threadManager->removeWorker(); + + EXPECT(threadManager->workerCount(), 0); + EXPECT(threadManager->idleWorkerCount(), 0); + EXPECT(threadManager->pendingTaskCount(), 0); + + std::cout << "\t\t\t\tadd blocking task.. " << std::endl; + + // We're going to throw a blocking task into the mix + Monitor entryMonitor; // signaled when task is running + Monitor blockMonitor; // to be signaled to unblock the task + bool blocked(true); // set to false before notifying + Monitor doneMonitor; // signaled when count reaches zero + size_t activeCount = 1; + shared_ptr blockingTask( + new ThreadManagerTests::BlockTask(entryMonitor, blockMonitor, blocked, doneMonitor, activeCount)); + threadManager->add(blockingTask); + + EXPECT(threadManager->workerCount(), 0); + EXPECT(threadManager->idleWorkerCount(), 0); + EXPECT(threadManager->pendingTaskCount(), 1); + + std::cout << "\t\t\t\tadd other task.. " << std::endl; + + shared_ptr otherTask( + new ThreadManagerTests::Task(doneMonitor, activeCount, 0)); + + threadManager->add(otherTask); + + EXPECT(threadManager->workerCount(), 0); + EXPECT(threadManager->idleWorkerCount(), 0); + EXPECT(threadManager->pendingTaskCount(), 2); + + std::cout << "\t\t\t\tremove blocking task specifically.. " << std::endl; + + threadManager->remove(blockingTask); + + EXPECT(threadManager->workerCount(), 0); + EXPECT(threadManager->idleWorkerCount(), 0); + EXPECT(threadManager->pendingTaskCount(), 1); + + std::cout << "\t\t\t\tremove next pending task.." << std::endl; + + shared_ptr nextTask = threadManager->removeNextPending(); + if (nextTask != otherTask) { + std::cerr << "\t\t\t\t\texpected removeNextPending to return otherTask" << std::endl; + return false; + } + + EXPECT(threadManager->workerCount(), 0); + EXPECT(threadManager->idleWorkerCount(), 0); + EXPECT(threadManager->pendingTaskCount(), 0); + + std::cout << "\t\t\t\tremove next pending task (none left).." << std::endl; + + nextTask = threadManager->removeNextPending(); + if (nextTask) { + std::cerr << "\t\t\t\t\texpected removeNextPending to return an empty Runnable" << std::endl; + return false; + } + + std::cout << "\t\t\t\tadd 2 expired tasks and 1 not.." << std::endl; + + shared_ptr expiredTask( + new ThreadManagerTests::Task(doneMonitor, activeCount, 0)); + + threadManager->add(expiredTask, 0, 1); + threadManager->add(blockingTask); // add one that hasn't expired to make sure it gets skipped + threadManager->add(expiredTask, 0, 1); // add a second expired to ensure removeExpiredTasks removes both + + sleep_(50); // make sure enough time elapses for it to expire - the shortest expiration time is 1 millisecond + + EXPECT(threadManager->workerCount(), 0); + EXPECT(threadManager->idleWorkerCount(), 0); + EXPECT(threadManager->pendingTaskCount(), 3); + EXPECT(threadManager->expiredTaskCount(), 0); + + std::cout << "\t\t\t\tremove expired tasks.." << std::endl; + + if (!m_expired.empty()) { + std::cerr << "\t\t\t\t\texpected m_expired to be empty" << std::endl; + return false; + } + + threadManager->removeExpiredTasks(); + + if (m_expired.size() != 2) { + std::cerr << "\t\t\t\t\texpected m_expired to be set" << std::endl; + return false; + } + + if (m_expired.front() != expiredTask) { + std::cerr << "\t\t\t\t\texpected m_expired[0] to be the expired task" << std::endl; + return false; + } + m_expired.pop_front(); + + if (m_expired.front() != expiredTask) { + std::cerr << "\t\t\t\t\texpected m_expired[1] to be the expired task" << std::endl; + return false; + } + + m_expired.clear(); + + threadManager->remove(blockingTask); + + EXPECT(threadManager->workerCount(), 0); + EXPECT(threadManager->idleWorkerCount(), 0); + EXPECT(threadManager->pendingTaskCount(), 0); + EXPECT(threadManager->expiredTaskCount(), 2); + + std::cout << "\t\t\t\tadd expired task (again).." << std::endl; + + threadManager->add(expiredTask, 0, 1); // expires in 1ms + sleep_(50); // make sure enough time elapses for it to expire - the shortest expiration time is 1ms + + std::cout << "\t\t\t\tadd worker to consume expired task.." << std::endl; + + threadManager->addWorker(); + sleep_(100); // make sure it has time to spin up and expire the task + + if (m_expired.empty()) { + std::cerr << "\t\t\t\t\texpected m_expired to be set" << std::endl; + return false; + } + + if (m_expired.front() != expiredTask) { + std::cerr << "\t\t\t\t\texpected m_expired to be the expired task" << std::endl; + return false; + } + + m_expired.clear(); + + EXPECT(threadManager->workerCount(), 1); + EXPECT(threadManager->idleWorkerCount(), 1); + EXPECT(threadManager->pendingTaskCount(), 0); + EXPECT(threadManager->expiredTaskCount(), 3); + + std::cout << "\t\t\t\ttry to remove too many workers" << std::endl; + try { + threadManager->removeWorker(2); + std::cerr << "\t\t\t\t\texpected InvalidArgumentException" << std::endl; + return false; + } catch (const InvalidArgumentException&) { + /* expected */ + } + + std::cout << "\t\t\t\tremove worker.. " << std::endl; + + threadManager->removeWorker(); + + EXPECT(threadManager->workerCount(), 0); + EXPECT(threadManager->idleWorkerCount(), 0); + EXPECT(threadManager->pendingTaskCount(), 0); + EXPECT(threadManager->expiredTaskCount(), 3); + + std::cout << "\t\t\t\tadd blocking task.. " << std::endl; + + threadManager->add(blockingTask); + + EXPECT(threadManager->workerCount(), 0); + EXPECT(threadManager->idleWorkerCount(), 0); + EXPECT(threadManager->pendingTaskCount(), 1); + + std::cout << "\t\t\t\tadd worker.. " << std::endl; + + threadManager->addWorker(); + { + Synchronized s(entryMonitor); + while (!blockingTask->_entered) { + entryMonitor.wait(); + } + } + + EXPECT(threadManager->workerCount(), 1); + EXPECT(threadManager->idleWorkerCount(), 0); + EXPECT(threadManager->pendingTaskCount(), 0); + + std::cout << "\t\t\t\tunblock task and remove worker.. " << std::endl; + + { + Synchronized s(blockMonitor); + blocked = false; + blockMonitor.notifyAll(); + } + threadManager->removeWorker(); + + EXPECT(threadManager->workerCount(), 0); + EXPECT(threadManager->idleWorkerCount(), 0); + EXPECT(threadManager->pendingTaskCount(), 0); + + std::cout << "\t\t\t\tcleanup.. " << std::endl; + + blockingTask.reset(); + threadManager.reset(); + return true; + } +}; + +} +} +} +} // apache::thrift::concurrency + +using namespace apache::thrift::concurrency::test; diff --git a/src/jaegertracing/thrift/lib/cpp/test/concurrency/TimerManagerTests.h b/src/jaegertracing/thrift/lib/cpp/test/concurrency/TimerManagerTests.h new file mode 100644 index 000000000..2d1a2620a --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/test/concurrency/TimerManagerTests.h @@ -0,0 +1,273 @@ +/* + * 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. + */ + +#include +#include +#include + +#include +#include +#include +#include + +namespace apache { +namespace thrift { +namespace concurrency { +namespace test { + +using namespace apache::thrift::concurrency; + +class TimerManagerTests { + +public: + class Task : public Runnable { + public: + Task(Monitor& monitor, uint64_t timeout) + : _timeout(timeout), + _startTime(std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count()), + _endTime(0), + _monitor(monitor), + _success(false), + _done(false) {} + + ~Task() override { std::cerr << this << std::endl; } + + void run() override { + + _endTime = std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count(); + _success = (_endTime - _startTime) >= _timeout; + + { + Synchronized s(_monitor); + _done = true; + _monitor.notifyAll(); + } + } + + int64_t _timeout; + int64_t _startTime; + int64_t _endTime; + Monitor& _monitor; + bool _success; + bool _done; + }; + + /** + * This test creates two tasks and waits for the first to expire within 10% + * of the expected expiration time. It then verifies that the timer manager + * properly clean up itself and the remaining orphaned timeout task when the + * manager goes out of scope and its destructor is called. + */ + bool test00(uint64_t timeout = 1000LL) { + + shared_ptr orphanTask + = shared_ptr(new TimerManagerTests::Task(_monitor, 10 * timeout)); + + { + TimerManager timerManager; + timerManager.threadFactory(shared_ptr(new ThreadFactory())); + timerManager.start(); + if (timerManager.state() != TimerManager::STARTED) { + std::cerr << "timerManager is not in the STARTED state, but should be" << std::endl; + return false; + } + + // Don't create task yet, because its constructor sets the expected completion time, and we + // need to delay between inserting the two tasks into the run queue. + shared_ptr task; + + { + Synchronized s(_monitor); + timerManager.add(orphanTask, 10 * timeout); + + std::this_thread::sleep_for(std::chrono::milliseconds(timeout)); + + task.reset(new TimerManagerTests::Task(_monitor, timeout)); + timerManager.add(task, timeout); + _monitor.wait(); + } + + if (!task->_done) { + std::cerr << "task is not done, but it should have executed" << std::endl; + return false; + } + + std::cout << "\t\t\t" << (task->_success ? "Success" : "Failure") << "!" << std::endl; + } + + if (orphanTask->_done) { + std::cerr << "orphan task is done, but it should not have executed" << std::endl; + return false; + } + + return true; + } + + /** + * This test creates two tasks, removes the first one then waits for the second one. It then + * verifies that the timer manager properly clean up itself and the remaining orphaned timeout + * task when the manager goes out of scope and its destructor is called. + */ + bool test01(uint64_t timeout = 1000LL) { + TimerManager timerManager; + timerManager.threadFactory(shared_ptr(new ThreadFactory())); + timerManager.start(); + assert(timerManager.state() == TimerManager::STARTED); + + Synchronized s(_monitor); + + // Setup the two tasks + shared_ptr taskToRemove + = shared_ptr(new TimerManagerTests::Task(_monitor, timeout / 2)); + timerManager.add(taskToRemove, taskToRemove->_timeout); + + shared_ptr task + = shared_ptr(new TimerManagerTests::Task(_monitor, timeout)); + timerManager.add(task, task->_timeout); + + // Remove one task and wait until the other has completed + timerManager.remove(taskToRemove); + _monitor.wait(timeout * 2); + + assert(!taskToRemove->_done); + assert(task->_done); + + return true; + } + + /** + * This test creates two tasks with the same callback and another one, then removes the two + * duplicated then waits for the last one. It then verifies that the timer manager properly + * clean up itself and the remaining orphaned timeout task when the manager goes out of scope + * and its destructor is called. + */ + bool test02(uint64_t timeout = 1000LL) { + TimerManager timerManager; + timerManager.threadFactory(shared_ptr(new ThreadFactory())); + timerManager.start(); + assert(timerManager.state() == TimerManager::STARTED); + + Synchronized s(_monitor); + + // Setup the one tasks and add it twice + shared_ptr taskToRemove + = shared_ptr(new TimerManagerTests::Task(_monitor, timeout / 3)); + timerManager.add(taskToRemove, taskToRemove->_timeout); + timerManager.add(taskToRemove, taskToRemove->_timeout * 2); + + shared_ptr task + = shared_ptr(new TimerManagerTests::Task(_monitor, timeout)); + timerManager.add(task, task->_timeout); + + // Remove the first task (e.g. two timers) and wait until the other has completed + timerManager.remove(taskToRemove); + _monitor.wait(timeout * 2); + + assert(!taskToRemove->_done); + assert(task->_done); + + return true; + } + + /** + * This test creates two tasks, removes the first one then waits for the second one. It then + * verifies that the timer manager properly clean up itself and the remaining orphaned timeout + * task when the manager goes out of scope and its destructor is called. + */ + bool test03(uint64_t timeout = 1000LL) { + TimerManager timerManager; + timerManager.threadFactory(shared_ptr(new ThreadFactory())); + timerManager.start(); + assert(timerManager.state() == TimerManager::STARTED); + + Synchronized s(_monitor); + + // Setup the two tasks + shared_ptr taskToRemove + = shared_ptr(new TimerManagerTests::Task(_monitor, timeout / 2)); + TimerManager::Timer timer = timerManager.add(taskToRemove, taskToRemove->_timeout); + + shared_ptr task + = shared_ptr(new TimerManagerTests::Task(_monitor, timeout)); + timerManager.add(task, task->_timeout); + + // Remove one task and wait until the other has completed + timerManager.remove(timer); + _monitor.wait(timeout * 2); + + assert(!taskToRemove->_done); + assert(task->_done); + + // Verify behavior when removing the removed task + try { + timerManager.remove(timer); + assert(nullptr == "ERROR: This remove should send a NoSuchTaskException exception."); + } catch (NoSuchTaskException&) { + } + + return true; + } + + /** + * This test creates one task, and tries to remove it after it has expired. + */ + bool test04(uint64_t timeout = 1000LL) { + TimerManager timerManager; + timerManager.threadFactory(shared_ptr(new ThreadFactory())); + timerManager.start(); + assert(timerManager.state() == TimerManager::STARTED); + + Synchronized s(_monitor); + + // Setup the task + shared_ptr task + = shared_ptr(new TimerManagerTests::Task(_monitor, timeout / 10)); + TimerManager::Timer timer = timerManager.add(task, task->_timeout); + task.reset(); + + // Wait until the task has completed + _monitor.wait(timeout); + + // Verify behavior when removing the expired task + // notify is called inside the task so the task may still + // be running when we get here, so we need to loop... + for (;;) { + try { + timerManager.remove(timer); + assert(nullptr == "ERROR: This remove should throw NoSuchTaskException, or UncancellableTaskException."); + } catch (const NoSuchTaskException&) { + break; + } catch (const UncancellableTaskException&) { + // the thread was still exiting; try again... + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + } + + return true; + } + + friend class TestTask; + + Monitor _monitor; +}; + +} +} +} +} // apache::thrift::concurrency diff --git a/src/jaegertracing/thrift/lib/cpp/test/link/LinkTest.cpp b/src/jaegertracing/thrift/lib/cpp/test/link/LinkTest.cpp new file mode 100644 index 000000000..18e14d10c --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/test/link/LinkTest.cpp @@ -0,0 +1,22 @@ +/* + * 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. + */ + +int main(int, char**) { + return 0; +} diff --git a/src/jaegertracing/thrift/lib/cpp/test/link/TemplatedService1.cpp b/src/jaegertracing/thrift/lib/cpp/test/link/TemplatedService1.cpp new file mode 100644 index 000000000..da1790be2 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/test/link/TemplatedService1.cpp @@ -0,0 +1,26 @@ +/* + * 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. + */ + +/* + * This file is a part of a link test that makes sure generated + * templated service headers can be included from multiple + * implementation files. + */ + +#include "gen-cpp/ParentService.h" diff --git a/src/jaegertracing/thrift/lib/cpp/test/link/TemplatedService2.cpp b/src/jaegertracing/thrift/lib/cpp/test/link/TemplatedService2.cpp new file mode 100644 index 000000000..da1790be2 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/test/link/TemplatedService2.cpp @@ -0,0 +1,26 @@ +/* + * 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. + */ + +/* + * This file is a part of a link test that makes sure generated + * templated service headers can be included from multiple + * implementation files. + */ + +#include "gen-cpp/ParentService.h" diff --git a/src/jaegertracing/thrift/lib/cpp/test/processor/EventLog.cpp b/src/jaegertracing/thrift/lib/cpp/test/processor/EventLog.cpp new file mode 100644 index 000000000..c75955d27 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/test/processor/EventLog.cpp @@ -0,0 +1,135 @@ +/* + * 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. + */ +#include "EventLog.h" + +#include +#include + +using namespace apache::thrift::concurrency; + +namespace { + +// Define environment variable DEBUG_EVENTLOG to enable debug logging +// ex: $ DEBUG_EVENTLOG=1 processor_test +static const char * DEBUG_EVENTLOG = getenv("DEBUG_EVENTLOG"); + +void debug(const char* fmt, ...) { + if (DEBUG_EVENTLOG) { + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + fprintf(stderr, "\n"); + } +} +} + +namespace apache { +namespace thrift { +namespace test { + +uint32_t EventLog::nextId_ = 0; + +#define EVENT_TYPE(value) EventType EventLog::value = #value +EVENT_TYPE(ET_LOG_END); +EVENT_TYPE(ET_CONN_CREATED); +EVENT_TYPE(ET_CONN_DESTROYED); +EVENT_TYPE(ET_CALL_STARTED); +EVENT_TYPE(ET_CALL_FINISHED); +EVENT_TYPE(ET_PROCESS); +EVENT_TYPE(ET_PRE_READ); +EVENT_TYPE(ET_POST_READ); +EVENT_TYPE(ET_PRE_WRITE); +EVENT_TYPE(ET_POST_WRITE); +EVENT_TYPE(ET_ASYNC_COMPLETE); +EVENT_TYPE(ET_HANDLER_ERROR); + +EVENT_TYPE(ET_CALL_INCREMENT_GENERATION); +EVENT_TYPE(ET_CALL_GET_GENERATION); +EVENT_TYPE(ET_CALL_ADD_STRING); +EVENT_TYPE(ET_CALL_GET_STRINGS); +EVENT_TYPE(ET_CALL_GET_DATA_WAIT); +EVENT_TYPE(ET_CALL_ONEWAY_WAIT); +EVENT_TYPE(ET_CALL_EXCEPTION_WAIT); +EVENT_TYPE(ET_CALL_UNEXPECTED_EXCEPTION_WAIT); +EVENT_TYPE(ET_CALL_SET_VALUE); +EVENT_TYPE(ET_CALL_GET_VALUE); +EVENT_TYPE(ET_WAIT_RETURN); + +EventLog::EventLog() { + id_ = nextId_++; + debug("New log: %d", id_); +} + +void EventLog::append(EventType type, + uint32_t connectionId, + uint32_t callId, + const std::string& message) { + Synchronized s(monitor_); + debug("%d <-- %u, %u, %s \"%s\"", id_, connectionId, callId, type, message.c_str()); + + Event e(type, connectionId, callId, message); + events_.push_back(e); + + monitor_.notify(); +} + +Event EventLog::waitForEvent(int64_t timeout) { + Synchronized s(monitor_); + + try { + while (events_.empty()) { + monitor_.wait(timeout); + } + } catch (const TimedOutException &) { + return Event(ET_LOG_END, 0, 0, ""); + } + + Event event = events_.front(); + events_.pop_front(); + return event; +} + +Event EventLog::waitForConnEvent(uint32_t connId, int64_t timeout) { + Synchronized s(monitor_); + + auto it = events_.begin(); + while (true) { + try { + // TODO: it would be nicer to honor timeout for the duration of this + // call, rather than restarting it for each call to wait(). It shouldn't + // be a big problem in practice, though. + while (it == events_.end()) { + monitor_.wait(timeout); + } + } catch (const TimedOutException &) { + return Event(ET_LOG_END, 0, 0, ""); + } + + if (it->connectionId == connId) { + Event event = *it; + events_.erase(it); + return event; + } + } +} +} +} +} // apache::thrift::test diff --git a/src/jaegertracing/thrift/lib/cpp/test/processor/EventLog.h b/src/jaegertracing/thrift/lib/cpp/test/processor/EventLog.h new file mode 100644 index 000000000..4f8275db9 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/test/processor/EventLog.h @@ -0,0 +1,95 @@ +/* + * 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. + */ +#ifndef _THRIFT_TEST_EVENTLOG_H_ +#define _THRIFT_TEST_EVENTLOG_H_ 1 + +#include + +namespace apache { +namespace thrift { +namespace test { + +// Initially I made EventType an enum, but using char* results +// in much more readable error messages when there is a mismatch. +// It also lets users of EventLog easily define their own new types. +// Comparing the literal pointer values should be safe, barring any strange +// linking setup that results in duplicate symbols. +typedef const char* EventType; + +struct Event { + Event(EventType type, uint32_t connectionId, uint32_t callId, const std::string& message) + : type(type), connectionId(connectionId), callId(callId), message(message) {} + + EventType type; + uint32_t connectionId; + uint32_t callId; + std::string message; +}; + +class EventLog { +public: + static EventType ET_LOG_END; + static EventType ET_CONN_CREATED; + static EventType ET_CONN_DESTROYED; + static EventType ET_CALL_STARTED; + static EventType ET_CALL_FINISHED; + static EventType ET_PROCESS; + static EventType ET_PRE_READ; + static EventType ET_POST_READ; + static EventType ET_PRE_WRITE; + static EventType ET_POST_WRITE; + static EventType ET_ASYNC_COMPLETE; + static EventType ET_HANDLER_ERROR; + + static EventType ET_CALL_INCREMENT_GENERATION; + static EventType ET_CALL_GET_GENERATION; + static EventType ET_CALL_ADD_STRING; + static EventType ET_CALL_GET_STRINGS; + static EventType ET_CALL_GET_DATA_WAIT; + static EventType ET_CALL_ONEWAY_WAIT; + static EventType ET_CALL_UNEXPECTED_EXCEPTION_WAIT; + static EventType ET_CALL_EXCEPTION_WAIT; + static EventType ET_WAIT_RETURN; + static EventType ET_CALL_SET_VALUE; + static EventType ET_CALL_GET_VALUE; + + EventLog(); + + void append(EventType type, + uint32_t connectionId, + uint32_t callId, + const std::string& message = ""); + + Event waitForEvent(int64_t timeout = 500); + Event waitForConnEvent(uint32_t connId, int64_t timeout = 500); + +protected: + typedef std::list EventList; + + concurrency::Monitor monitor_; + EventList events_; + uint32_t id_; + + static uint32_t nextId_; +}; +} +} +} // apache::thrift::test + +#endif // _THRIFT_TEST_EVENTLOG_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/test/processor/Handlers.h b/src/jaegertracing/thrift/lib/cpp/test/processor/Handlers.h new file mode 100644 index 000000000..05d19edd9 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/test/processor/Handlers.h @@ -0,0 +1,338 @@ +/* + * 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. + */ +#ifndef _THRIFT_PROCESSOR_TEST_HANDLERS_H_ +#define _THRIFT_PROCESSOR_TEST_HANDLERS_H_ 1 + +#include "EventLog.h" +#include "gen-cpp/ParentService.h" +#include "gen-cpp/ChildService.h" + +namespace apache { +namespace thrift { +namespace test { + +class ParentHandler : virtual public ParentServiceIf { +public: + ParentHandler(const std::shared_ptr& log) + : triggerMonitor(&mutex_), generation_(0), wait_(false), log_(log) {} + + int32_t incrementGeneration() override { + concurrency::Guard g(mutex_); + log_->append(EventLog::ET_CALL_INCREMENT_GENERATION, 0, 0); + return ++generation_; + } + + int32_t getGeneration() override { + concurrency::Guard g(mutex_); + log_->append(EventLog::ET_CALL_GET_GENERATION, 0, 0); + return generation_; + } + + void addString(const std::string& s) override { + concurrency::Guard g(mutex_); + log_->append(EventLog::ET_CALL_ADD_STRING, 0, 0); + strings_.push_back(s); + } + + void getStrings(std::vector& _return) override { + concurrency::Guard g(mutex_); + log_->append(EventLog::ET_CALL_GET_STRINGS, 0, 0); + _return = strings_; + } + + void getDataWait(std::string& _return, const int32_t length) override { + concurrency::Guard g(mutex_); + log_->append(EventLog::ET_CALL_GET_DATA_WAIT, 0, 0); + + blockUntilTriggered(); + + _return.append(length, 'a'); + } + + void onewayWait() override { + concurrency::Guard g(mutex_); + log_->append(EventLog::ET_CALL_ONEWAY_WAIT, 0, 0); + + blockUntilTriggered(); + } + + void exceptionWait(const std::string& message) override { + concurrency::Guard g(mutex_); + log_->append(EventLog::ET_CALL_EXCEPTION_WAIT, 0, 0); + + blockUntilTriggered(); + + MyError e; + e.message = message; + throw e; + } + + void unexpectedExceptionWait(const std::string& message) override { + concurrency::Guard g(mutex_); + log_->append(EventLog::ET_CALL_UNEXPECTED_EXCEPTION_WAIT, 0, 0); + + blockUntilTriggered(); + + MyError e; + e.message = message; + throw e; + } + + /** + * After prepareTriggeredCall() is invoked, calls to any of the *Wait() + * functions won't return until triggerPendingCalls() is invoked + * + * This has to be a separate function invoked by the main test thread + * in order to to avoid race conditions. + */ + void prepareTriggeredCall() { + concurrency::Guard g(mutex_); + wait_ = true; + } + + /** + * Wake up all calls waiting in blockUntilTriggered() + */ + void triggerPendingCalls() { + concurrency::Guard g(mutex_); + wait_ = false; + triggerMonitor.notifyAll(); + } + +protected: + /** + * blockUntilTriggered() won't return until triggerPendingCalls() is invoked + * in another thread. + * + * This should only be called when already holding mutex_. + */ + void blockUntilTriggered() { + while (wait_) { + triggerMonitor.waitForever(); + } + + // Log an event when we return + log_->append(EventLog::ET_WAIT_RETURN, 0, 0); + } + + concurrency::Mutex mutex_; + concurrency::Monitor triggerMonitor; + int32_t generation_; + bool wait_; + std::vector strings_; + std::shared_ptr log_; +}; + +#ifdef _WIN32 + #pragma warning( push ) + #pragma warning (disable : 4250 ) //inheriting methods via dominance +#endif + +class ChildHandler : public ParentHandler, virtual public ChildServiceIf { +public: + ChildHandler(const std::shared_ptr& log) : ParentHandler(log), value_(0) {} + + int32_t setValue(const int32_t value) override { + concurrency::Guard g(mutex_); + log_->append(EventLog::ET_CALL_SET_VALUE, 0, 0); + + int32_t oldValue = value_; + value_ = value; + return oldValue; + } + + int32_t getValue() override { + concurrency::Guard g(mutex_); + log_->append(EventLog::ET_CALL_GET_VALUE, 0, 0); + + return value_; + } + +protected: + int32_t value_; +}; + +#ifdef _WIN32 + #pragma warning( pop ) +#endif + +struct ConnContext { +public: + ConnContext(std::shared_ptr in, + std::shared_ptr out, + uint32_t id) + : input(in), output(out), id(id) {} + + std::shared_ptr input; + std::shared_ptr output; + uint32_t id; +}; + +struct CallContext { +public: + CallContext(ConnContext* context, uint32_t id, const std::string& name) + : connContext(context), name(name), id(id) {} + + ConnContext* connContext; + std::string name; + uint32_t id; +}; + +class ServerEventHandler : public server::TServerEventHandler { +public: + ServerEventHandler(const std::shared_ptr& log) : nextId_(1), log_(log) {} + + void preServe() override {} + + void* createContext(std::shared_ptr input, + std::shared_ptr output) override { + ConnContext* context = new ConnContext(input, output, nextId_); + ++nextId_; + log_->append(EventLog::ET_CONN_CREATED, context->id, 0); + return context; + } + + void deleteContext(void* serverContext, + std::shared_ptr input, + std::shared_ptr output) override { + auto* context = reinterpret_cast(serverContext); + + if (input != context->input) { + abort(); + } + if (output != context->output) { + abort(); + } + + log_->append(EventLog::ET_CONN_DESTROYED, context->id, 0); + + delete context; + } + + void processContext(void* serverContext, + std::shared_ptr transport) override { +// TODO: We currently don't test the behavior of the processContext() +// calls. The various server implementations call processContext() at +// slightly different times, and it is too annoying to try and account for +// their various differences. +// +// TThreadedServer, TThreadPoolServer, and TSimpleServer usually wait until +// they see the first byte of a request before calling processContext(). +// However, they don't wait for the first byte of the very first request, +// and instead immediately call processContext() before any data is +// received. +// +// TNonblockingServer always waits until receiving the full request before +// calling processContext(). +#if 0 + ConnContext* context = reinterpret_cast(serverContext); + log_->append(EventLog::ET_PROCESS, context->id, 0); +#else + THRIFT_UNUSED_VARIABLE(serverContext); + THRIFT_UNUSED_VARIABLE(transport); +#endif + } + +protected: + uint32_t nextId_; + std::shared_ptr log_; +}; + +class ProcessorEventHandler : public TProcessorEventHandler { +public: + ProcessorEventHandler(const std::shared_ptr& log) : nextId_(1), log_(log) {} + + void* getContext(const char* fnName, void* serverContext) override { + auto* connContext = reinterpret_cast(serverContext); + + CallContext* context = new CallContext(connContext, nextId_, fnName); + ++nextId_; + + log_->append(EventLog::ET_CALL_STARTED, connContext->id, context->id, fnName); + return context; + } + + void freeContext(void* ctx, const char* fnName) override { + auto* context = reinterpret_cast(ctx); + checkName(context, fnName); + log_->append(EventLog::ET_CALL_FINISHED, context->connContext->id, context->id, fnName); + delete context; + } + + void preRead(void* ctx, const char* fnName) override { + auto* context = reinterpret_cast(ctx); + checkName(context, fnName); + log_->append(EventLog::ET_PRE_READ, context->connContext->id, context->id, fnName); + } + + void postRead(void* ctx, const char* fnName, uint32_t bytes) override { + THRIFT_UNUSED_VARIABLE(bytes); + auto* context = reinterpret_cast(ctx); + checkName(context, fnName); + log_->append(EventLog::ET_POST_READ, context->connContext->id, context->id, fnName); + } + + void preWrite(void* ctx, const char* fnName) override { + auto* context = reinterpret_cast(ctx); + checkName(context, fnName); + log_->append(EventLog::ET_PRE_WRITE, context->connContext->id, context->id, fnName); + } + + void postWrite(void* ctx, const char* fnName, uint32_t bytes) override { + THRIFT_UNUSED_VARIABLE(bytes); + auto* context = reinterpret_cast(ctx); + checkName(context, fnName); + log_->append(EventLog::ET_POST_WRITE, context->connContext->id, context->id, fnName); + } + + void asyncComplete(void* ctx, const char* fnName) override { + auto* context = reinterpret_cast(ctx); + checkName(context, fnName); + log_->append(EventLog::ET_ASYNC_COMPLETE, context->connContext->id, context->id, fnName); + } + + void handlerError(void* ctx, const char* fnName) override { + auto* context = reinterpret_cast(ctx); + checkName(context, fnName); + log_->append(EventLog::ET_HANDLER_ERROR, context->connContext->id, context->id, fnName); + } + +protected: + void checkName(const CallContext* context, const char* fnName) { + // Note: we can't use BOOST_CHECK_EQUAL here, since the handler runs in a + // different thread from the test functions. Just abort if the names are + // different + if (context->name != fnName) { + fprintf(stderr, + "call context name mismatch: \"%s\" != \"%s\"\n", + context->name.c_str(), + fnName); + fflush(stderr); + abort(); + } + } + + uint32_t nextId_; + std::shared_ptr log_; +}; +} +} +} // apache::thrift::test + +#endif // _THRIFT_PROCESSOR_TEST_HANDLERS_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/test/processor/ProcessorTest.cpp b/src/jaegertracing/thrift/lib/cpp/test/processor/ProcessorTest.cpp new file mode 100644 index 000000000..a36ef3eec --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/test/processor/ProcessorTest.cpp @@ -0,0 +1,929 @@ +/* + * 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. + */ + +/* + * This file contains tests that ensure TProcessorEventHandler and + * TServerEventHandler are invoked properly by the various server + * implementations. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "EventLog.h" +#include "ServerThread.h" +#include "Handlers.h" +#include "gen-cpp/ChildService.h" + +using namespace apache::thrift; +using namespace apache::thrift::concurrency; +using namespace apache::thrift::protocol; +using namespace apache::thrift::server; +using namespace apache::thrift::test; +using namespace apache::thrift::transport; +using std::string; +using std::vector; + +/* + * Traits classes that encapsulate how to create various types of servers. + */ + +class TSimpleServerTraits { +public: + typedef TSimpleServer ServerType; + + std::shared_ptr createServer( + const std::shared_ptr& processor, + uint16_t port, + const std::shared_ptr& transportFactory, + const std::shared_ptr& protocolFactory) { + std::shared_ptr socket(new TServerSocket(port)); + return std::shared_ptr( + new TSimpleServer(processor, socket, transportFactory, protocolFactory)); + } +}; + +class TThreadedServerTraits { +public: + typedef TThreadedServer ServerType; + + std::shared_ptr createServer( + const std::shared_ptr& processor, + uint16_t port, + const std::shared_ptr& transportFactory, + const std::shared_ptr& protocolFactory) { + std::shared_ptr socket(new TServerSocket(port)); + return std::shared_ptr( + new TThreadedServer(processor, socket, transportFactory, protocolFactory)); + } +}; + +class TThreadPoolServerTraits { +public: + typedef TThreadPoolServer ServerType; + + std::shared_ptr createServer( + const std::shared_ptr& processor, + uint16_t port, + const std::shared_ptr& transportFactory, + const std::shared_ptr& protocolFactory) { + std::shared_ptr socket(new TServerSocket(port)); + + std::shared_ptr threadFactory(new ThreadFactory); + std::shared_ptr threadManager = ThreadManager::newSimpleThreadManager(8); + threadManager->threadFactory(threadFactory); + threadManager->start(); + + return std::shared_ptr( + new TThreadPoolServer(processor, socket, transportFactory, protocolFactory, threadManager)); + } +}; + +class TNonblockingServerTraits { +public: + typedef TNonblockingServer ServerType; + + std::shared_ptr createServer( + const std::shared_ptr& processor, + uint16_t port, + const std::shared_ptr& transportFactory, + const std::shared_ptr& protocolFactory) { + // TNonblockingServer automatically uses TFramedTransport. + // Raise an exception if the supplied transport factory is not a + // TFramedTransportFactory + auto* framedFactory + = dynamic_cast(transportFactory.get()); + if (framedFactory == nullptr) { + throw TException("TNonblockingServer must use TFramedTransport"); + } + + std::shared_ptr socket(new TNonblockingServerSocket(port)); + std::shared_ptr threadFactory(new ThreadFactory); + std::shared_ptr threadManager = ThreadManager::newSimpleThreadManager(8); + threadManager->threadFactory(threadFactory); + threadManager->start(); + + return std::shared_ptr( + new TNonblockingServer(processor, protocolFactory, socket, threadManager)); + } +}; + +class TNonblockingServerNoThreadsTraits { +public: + typedef TNonblockingServer ServerType; + + std::shared_ptr createServer( + const std::shared_ptr& processor, + uint16_t port, + const std::shared_ptr& transportFactory, + const std::shared_ptr& protocolFactory) { + // TNonblockingServer automatically uses TFramedTransport. + // Raise an exception if the supplied transport factory is not a + // TFramedTransportFactory + auto* framedFactory + = dynamic_cast(transportFactory.get()); + if (framedFactory == nullptr) { + throw TException("TNonblockingServer must use TFramedTransport"); + } + + std::shared_ptr socket(new TNonblockingServerSocket(port)); + // Use a NULL ThreadManager + std::shared_ptr threadManager; + return std::shared_ptr( + new TNonblockingServer(processor, protocolFactory, socket, threadManager)); + } +}; + +/* + * Traits classes for controlling if we instantiate templated or generic + * protocol factories, processors, clients, etc. + * + * The goal is to allow the outer test code to select which server type is + * being tested, and whether or not we are testing the templated classes, or + * the generic classes. + * + * Each specific test case can control whether we create a child or parent + * server, and whether we use TFramedTransport or TBufferedTransport. + */ + +class UntemplatedTraits { +public: + typedef TBinaryProtocolFactory ProtocolFactory; + typedef TBinaryProtocol Protocol; + + typedef ParentServiceProcessor ParentProcessor; + typedef ChildServiceProcessor ChildProcessor; + typedef ParentServiceClient ParentClient; + typedef ChildServiceClient ChildClient; +}; + +class TemplatedTraits { +public: + typedef TBinaryProtocolFactoryT ProtocolFactory; + typedef TBinaryProtocolT Protocol; + + typedef ParentServiceProcessorT ParentProcessor; + typedef ChildServiceProcessorT ChildProcessor; + typedef ParentServiceClientT ParentClient; + typedef ChildServiceClientT ChildClient; +}; + +template +class ParentServiceTraits { +public: + typedef typename TemplateTraits_::ParentProcessor Processor; + typedef typename TemplateTraits_::ParentClient Client; + typedef ParentHandler Handler; + + typedef typename TemplateTraits_::ProtocolFactory ProtocolFactory; + typedef typename TemplateTraits_::Protocol Protocol; +}; + +template +class ChildServiceTraits { +public: + typedef typename TemplateTraits_::ChildProcessor Processor; + typedef typename TemplateTraits_::ChildClient Client; + typedef ChildHandler Handler; + + typedef typename TemplateTraits_::ProtocolFactory ProtocolFactory; + typedef typename TemplateTraits_::Protocol Protocol; +}; + +// TODO: It would be nicer if the TTransportFactory types defined a typedef, +// to allow us to figure out the exact transport type without having to pass it +// in as a separate template parameter here. +// +// It would also be niec if they used covariant return types. Unfortunately, +// since they return shared_ptr instead of raw pointers, covariant return types +// won't work. +template +class ServiceState : public ServerState { +public: + typedef typename ServiceTraits_::Processor Processor; + typedef typename ServiceTraits_::Client Client; + typedef typename ServiceTraits_::Handler Handler; + + ServiceState() + : port_(0), + log_(new EventLog), + handler_(new Handler(log_)), + processor_(new Processor(handler_)), + transportFactory_(new TransportFactory_), + protocolFactory_(new typename ServiceTraits_::ProtocolFactory), + serverEventHandler_(new ServerEventHandler(log_)), + processorEventHandler_(new ProcessorEventHandler(log_)) { + processor_->setEventHandler(processorEventHandler_); + } + + std::shared_ptr createServer(uint16_t port) override { + ServerTraits_ serverTraits; + return serverTraits.createServer(processor_, port, transportFactory_, protocolFactory_); + } + + std::shared_ptr getServerEventHandler() override { return serverEventHandler_; } + + void bindSuccessful(uint16_t port) override { port_ = port; } + + uint16_t getPort() const { return port_; } + + const std::shared_ptr& getLog() const { return log_; } + + const std::shared_ptr& getHandler() const { return handler_; } + + std::shared_ptr createClient() { + typedef typename ServiceTraits_::Protocol Protocol; + + std::shared_ptr socket(new TSocket("127.0.0.1", port_)); + std::shared_ptr transport(new Transport_(socket)); + std::shared_ptr protocol(new Protocol(transport)); + transport->open(); + + std::shared_ptr client(new Client(protocol)); + return client; + } + +private: + uint16_t port_; + std::shared_ptr log_; + std::shared_ptr handler_; + std::shared_ptr processor_; + std::shared_ptr transportFactory_; + std::shared_ptr protocolFactory_; + std::shared_ptr serverEventHandler_; + std::shared_ptr processorEventHandler_; +}; + +/** + * Check that there are no more events in the log + */ +void checkNoEvents(const std::shared_ptr& log) { + // Wait for an event with a very short timeout period. We don't expect + // anything to be present, so we will normally wait for the full timeout. + // On the other hand, a non-zero timeout is nice since it does give a short + // window for events to arrive in case there is a problem. + Event event = log->waitForEvent(10); + BOOST_CHECK_EQUAL(EventLog::ET_LOG_END, event.type); +} + +/** + * Check for the events that should be logged when a new connection is created. + * + * Returns the connection ID allocated by the server. + */ +uint32_t checkNewConnEvents(const std::shared_ptr& log) { + // Check for an ET_CONN_CREATED event + Event event = log->waitForEvent(2500); + BOOST_CHECK_EQUAL(EventLog::ET_CONN_CREATED, event.type); + + // Some servers call the processContext() hook immediately. + // Others (TNonblockingServer) only call it once a full request is received. + // We don't check for it yet, to allow either behavior. + + return event.connectionId; +} + +/** + * Check for the events that should be logged when a connection is closed. + */ +void checkCloseEvents(const std::shared_ptr& log, uint32_t connId) { + // Check for an ET_CONN_DESTROYED event + Event event = log->waitForEvent(); + BOOST_CHECK_EQUAL(EventLog::ET_CONN_DESTROYED, event.type); + BOOST_CHECK_EQUAL(connId, event.connectionId); + + // Make sure there are no more events + checkNoEvents(log); +} + +/** + * Check for the events that should be logged when a call is received + * and the handler is invoked. + * + * It does not check for anything after the handler invocation. + * + * Returns the call ID allocated by the server. + */ +uint32_t checkCallHandlerEvents(const std::shared_ptr& log, + uint32_t connId, + EventType callType, + const string& callName) { + // Call started + Event event = log->waitForEvent(); + BOOST_CHECK_EQUAL(EventLog::ET_CALL_STARTED, event.type); + BOOST_CHECK_EQUAL(connId, event.connectionId); + BOOST_CHECK_EQUAL(callName, event.message); + uint32_t callId = event.callId; + + // Pre-read + event = log->waitForEvent(); + BOOST_CHECK_EQUAL(EventLog::ET_PRE_READ, event.type); + BOOST_CHECK_EQUAL(connId, event.connectionId); + BOOST_CHECK_EQUAL(callId, event.callId); + BOOST_CHECK_EQUAL(callName, event.message); + + // Post-read + event = log->waitForEvent(); + BOOST_CHECK_EQUAL(EventLog::ET_POST_READ, event.type); + BOOST_CHECK_EQUAL(connId, event.connectionId); + BOOST_CHECK_EQUAL(callId, event.callId); + BOOST_CHECK_EQUAL(callName, event.message); + + // Handler invocation + event = log->waitForEvent(); + BOOST_CHECK_EQUAL(callType, event.type); + // The handler doesn't have any connection or call context, + // so the connectionId and callId in this event aren't valid + + return callId; +} + +/** + * Check for the events that should be after a handler returns. + */ +void checkCallPostHandlerEvents(const std::shared_ptr& log, + uint32_t connId, + uint32_t callId, + const string& callName) { + // Pre-write + Event event = log->waitForEvent(); + BOOST_CHECK_EQUAL(EventLog::ET_PRE_WRITE, event.type); + BOOST_CHECK_EQUAL(connId, event.connectionId); + BOOST_CHECK_EQUAL(callId, event.callId); + BOOST_CHECK_EQUAL(callName, event.message); + + // Post-write + event = log->waitForEvent(); + BOOST_CHECK_EQUAL(EventLog::ET_POST_WRITE, event.type); + BOOST_CHECK_EQUAL(connId, event.connectionId); + BOOST_CHECK_EQUAL(callId, event.callId); + BOOST_CHECK_EQUAL(callName, event.message); + + // Call finished + event = log->waitForEvent(); + BOOST_CHECK_EQUAL(EventLog::ET_CALL_FINISHED, event.type); + BOOST_CHECK_EQUAL(connId, event.connectionId); + BOOST_CHECK_EQUAL(callId, event.callId); + BOOST_CHECK_EQUAL(callName, event.message); + + // It is acceptable for servers to call processContext() again immediately + // to start waiting on the next request. However, some servers wait before + // getting either a partial request or the full request before calling + // processContext(). We don't check for the next call to processContext() + // yet. +} + +/** + * Check for the events that should be logged when a call is made. + * + * This just calls checkCallHandlerEvents() followed by + * checkCallPostHandlerEvents(). + * + * Returns the call ID allocated by the server. + */ +uint32_t checkCallEvents(const std::shared_ptr& log, + uint32_t connId, + EventType callType, + const string& callName) { + uint32_t callId = checkCallHandlerEvents(log, connId, callType, callName); + checkCallPostHandlerEvents(log, connId, callId, callName); + + return callId; +} + +/* + * Test functions + */ + +template +void testParentService(const std::shared_ptr& state) { + std::shared_ptr client = state->createClient(); + + int32_t gen = client->getGeneration(); + int32_t newGen = client->incrementGeneration(); + BOOST_CHECK_EQUAL(gen + 1, newGen); + newGen = client->getGeneration(); + BOOST_CHECK_EQUAL(gen + 1, newGen); + + client->addString("foo"); + client->addString("bar"); + client->addString("asdf"); + + vector strings; + client->getStrings(strings); + BOOST_REQUIRE_EQUAL(3, strings.size()); + BOOST_REQUIRE_EQUAL("foo", strings[0]); + BOOST_REQUIRE_EQUAL("bar", strings[1]); + BOOST_REQUIRE_EQUAL("asdf", strings[2]); +} + +template +void testChildService(const std::shared_ptr& state) { + std::shared_ptr client = state->createClient(); + + // Test calling some of the parent methids via the a child client + int32_t gen = client->getGeneration(); + int32_t newGen = client->incrementGeneration(); + BOOST_CHECK_EQUAL(gen + 1, newGen); + newGen = client->getGeneration(); + BOOST_CHECK_EQUAL(gen + 1, newGen); + + // Test some of the child methods + client->setValue(10); + BOOST_CHECK_EQUAL(10, client->getValue()); + BOOST_CHECK_EQUAL(10, client->setValue(99)); + BOOST_CHECK_EQUAL(99, client->getValue()); +} + +template +void testBasicService() { + typedef ServiceState > State; + + // Start the server + std::shared_ptr state(new State); + ServerThread serverThread(state, true); + + testParentService(state); +} + +template +void testInheritedService() { + typedef ServiceState > State; + + // Start the server + std::shared_ptr state(new State); + ServerThread serverThread(state, true); + + testParentService(state); + testChildService(state); +} + +/** + * Test to make sure that the TServerEventHandler and TProcessorEventHandler + * methods are invoked in the correct order with the actual events. + */ +template +void testEventSequencing() { + // We use TBufferedTransport for this test, instead of TFramedTransport. + // This way the server will start processing data as soon as it is received, + // instead of waiting for the full request. This is necessary so we can + // separate the preRead() and postRead() events. + typedef ServiceState, + TBufferedTransportFactory, + TBufferedTransport> State; + + // Start the server + std::shared_ptr state(new State); + ServerThread serverThread(state, true); + + const std::shared_ptr& log = state->getLog(); + + // Make sure we're at the end of the log + checkNoEvents(log); + + state->getHandler()->prepareTriggeredCall(); + + // Make sure createContext() is called after a connection has been + // established. We open a plain socket instead of creating a client. + std::shared_ptr socket(new TSocket("127.0.0.1", state->getPort())); + socket->open(); + + // Make sure the proper events occurred after a new connection + uint32_t connId = checkNewConnEvents(log); + + // Send a message header. We manually construct the request so that we + // can test the timing for the preRead() call. + string requestName = "getDataWait"; + string eventName = "ParentService.getDataWait"; + auto seqid = int32_t(time(nullptr)); + TBinaryProtocol protocol(socket); + protocol.writeMessageBegin(requestName, T_CALL, seqid); + socket->flush(); + + // Make sure we saw the call started and pre-read events + Event event = log->waitForEvent(); + BOOST_CHECK_EQUAL(EventLog::ET_CALL_STARTED, event.type); + BOOST_CHECK_EQUAL(eventName, event.message); + BOOST_CHECK_EQUAL(connId, event.connectionId); + uint32_t callId = event.callId; + + event = log->waitForEvent(); + BOOST_CHECK_EQUAL(EventLog::ET_PRE_READ, event.type); + BOOST_CHECK_EQUAL(eventName, event.message); + BOOST_CHECK_EQUAL(connId, event.connectionId); + BOOST_CHECK_EQUAL(callId, event.callId); + + // Make sure there are no new events + checkNoEvents(log); + + // Send the rest of the request + protocol.writeStructBegin("ParentService_getDataNotified_pargs"); + protocol.writeFieldBegin("length", apache::thrift::protocol::T_I32, 1); + protocol.writeI32(8 * 1024 * 1024); + protocol.writeFieldEnd(); + protocol.writeFieldStop(); + protocol.writeStructEnd(); + protocol.writeMessageEnd(); + socket->writeEnd(); + socket->flush(); + + // We should then see postRead() + event = log->waitForEvent(); + BOOST_CHECK_EQUAL(EventLog::ET_POST_READ, event.type); + BOOST_CHECK_EQUAL(eventName, event.message); + BOOST_CHECK_EQUAL(connId, event.connectionId); + BOOST_CHECK_EQUAL(callId, event.callId); + + // Then the handler should be invoked + event = log->waitForEvent(); + BOOST_CHECK_EQUAL(EventLog::ET_CALL_GET_DATA_WAIT, event.type); + + // The handler won't respond until we notify it. + // Make sure there are no more events. + checkNoEvents(log); + + // Notify the handler that it should return + // We just use a global lock for now, since it is easiest + state->getHandler()->triggerPendingCalls(); + + // The handler will log a separate event before it returns + event = log->waitForEvent(); + BOOST_CHECK_EQUAL(EventLog::ET_WAIT_RETURN, event.type); + + // We should then see preWrite() + event = log->waitForEvent(); + BOOST_CHECK_EQUAL(EventLog::ET_PRE_WRITE, event.type); + BOOST_CHECK_EQUAL(eventName, event.message); + BOOST_CHECK_EQUAL(connId, event.connectionId); + BOOST_CHECK_EQUAL(callId, event.callId); + + // We requested more data than can be buffered, and we aren't reading it, + // so the server shouldn't be able to finish its write yet. + // Make sure there are no more events. + checkNoEvents(log); + + // Read the response header + string responseName; + int32_t responseSeqid = 0; + apache::thrift::protocol::TMessageType responseType; + protocol.readMessageBegin(responseName, responseType, responseSeqid); + BOOST_CHECK_EQUAL(responseSeqid, seqid); + BOOST_CHECK_EQUAL(requestName, responseName); + BOOST_CHECK_EQUAL(responseType, T_REPLY); + // Read the body. We just ignore it for now. + protocol.skip(T_STRUCT); + + // Now that we have read, the server should have finished sending the data + // and called the postWrite() handler + event = log->waitForEvent(); + BOOST_CHECK_EQUAL(EventLog::ET_POST_WRITE, event.type); + BOOST_CHECK_EQUAL(eventName, event.message); + BOOST_CHECK_EQUAL(connId, event.connectionId); + BOOST_CHECK_EQUAL(callId, event.callId); + + // Call finished should be last + event = log->waitForEvent(); + BOOST_CHECK_EQUAL(EventLog::ET_CALL_FINISHED, event.type); + BOOST_CHECK_EQUAL(eventName, event.message); + BOOST_CHECK_EQUAL(connId, event.connectionId); + BOOST_CHECK_EQUAL(callId, event.callId); + + // There should be no more events + checkNoEvents(log); + + // Close the connection, and make sure we get a connection destroyed event + socket->close(); + event = log->waitForEvent(); + BOOST_CHECK_EQUAL(EventLog::ET_CONN_DESTROYED, event.type); + BOOST_CHECK_EQUAL(connId, event.connectionId); + + // There should be no more events + checkNoEvents(log); +} + +template +void testSeparateConnections() { + typedef ServiceState > State; + + // Start the server + std::shared_ptr state(new State); + ServerThread serverThread(state, true); + + const std::shared_ptr& log = state->getLog(); + + // Create a client + std::shared_ptr client1 = state->createClient(); + + // Make sure the expected events were logged + uint32_t client1Id = checkNewConnEvents(log); + + // Create a second client + std::shared_ptr client2 = state->createClient(); + + // Make sure the expected events were logged + uint32_t client2Id = checkNewConnEvents(log); + + // The two connections should have different IDs + BOOST_CHECK_NE(client1Id, client2Id); + + // Make a call, and check for the proper events + int32_t value = 5; + client1->setValue(value); + uint32_t call1 + = checkCallEvents(log, client1Id, EventLog::ET_CALL_SET_VALUE, "ChildService.setValue"); + + // Make a call with client2 + int32_t v = client2->getValue(); + BOOST_CHECK_EQUAL(value, v); + checkCallEvents(log, client2Id, EventLog::ET_CALL_GET_VALUE, "ChildService.getValue"); + + // Make another call with client1 + v = client1->getValue(); + BOOST_CHECK_EQUAL(value, v); + uint32_t call2 + = checkCallEvents(log, client1Id, EventLog::ET_CALL_GET_VALUE, "ChildService.getValue"); + BOOST_CHECK_NE(call1, call2); + + // Close the second client, and check for the appropriate events + client2.reset(); + checkCloseEvents(log, client2Id); +} + +template +void testOnewayCall() { + typedef ServiceState > State; + + // Start the server + std::shared_ptr state(new State); + ServerThread serverThread(state, true); + + const std::shared_ptr& log = state->getLog(); + + // Create a client + std::shared_ptr client = state->createClient(); + uint32_t connId = checkNewConnEvents(log); + + // Make a oneway call + // It should return immediately, even though the server's handler + // won't return right away + state->getHandler()->prepareTriggeredCall(); + client->onewayWait(); + string callName = "ParentService.onewayWait"; + uint32_t callId = checkCallHandlerEvents(log, connId, EventLog::ET_CALL_ONEWAY_WAIT, callName); + + // There shouldn't be any more events + checkNoEvents(log); + + // Trigger the handler to return + state->getHandler()->triggerPendingCalls(); + + // The handler will log an ET_WAIT_RETURN event when it wakes up + Event event = log->waitForEvent(); + BOOST_CHECK_EQUAL(EventLog::ET_WAIT_RETURN, event.type); + + // Now we should see the async complete event, then call finished + event = log->waitForEvent(); + BOOST_CHECK_EQUAL(EventLog::ET_ASYNC_COMPLETE, event.type); + BOOST_CHECK_EQUAL(connId, event.connectionId); + BOOST_CHECK_EQUAL(callId, event.callId); + BOOST_CHECK_EQUAL(callName, event.message); + + event = log->waitForEvent(); + BOOST_CHECK_EQUAL(EventLog::ET_CALL_FINISHED, event.type); + BOOST_CHECK_EQUAL(connId, event.connectionId); + BOOST_CHECK_EQUAL(callId, event.callId); + BOOST_CHECK_EQUAL(callName, event.message); + + // Destroy the client, and check for connection closed events + client.reset(); + checkCloseEvents(log, connId); + + checkNoEvents(log); +} + +template +void testExpectedError() { + typedef ServiceState > State; + + // Start the server + std::shared_ptr state(new State); + ServerThread serverThread(state, true); + + const std::shared_ptr& log = state->getLog(); + + // Create a client + std::shared_ptr client = state->createClient(); + uint32_t connId = checkNewConnEvents(log); + + // Send the exceptionWait() call + state->getHandler()->prepareTriggeredCall(); + string message = "test 1234 test"; + client->send_exceptionWait(message); + string callName = "ParentService.exceptionWait"; + uint32_t callId = checkCallHandlerEvents(log, connId, EventLog::ET_CALL_EXCEPTION_WAIT, callName); + + // There shouldn't be any more events + checkNoEvents(log); + + // Trigger the handler to return + state->getHandler()->triggerPendingCalls(); + + // The handler will log an ET_WAIT_RETURN event when it wakes up + Event event = log->waitForEvent(); + BOOST_CHECK_EQUAL(EventLog::ET_WAIT_RETURN, event.type); + + // Now receive the response + try { + client->recv_exceptionWait(); + BOOST_FAIL("expected MyError to be thrown"); + } catch (const MyError& e) { + BOOST_CHECK_EQUAL(message, e.message); + // Check if std::exception::what() is handled properly + size_t message_pos = string(e.what()).find("TException - service has thrown: MyError"); + BOOST_CHECK_NE(message_pos, string::npos); + } + + // Now we should see the events for a normal call finish + checkCallPostHandlerEvents(log, connId, callId, callName); + + // There shouldn't be any more events + checkNoEvents(log); + + // Destroy the client, and check for connection closed events + client.reset(); + checkCloseEvents(log, connId); + + checkNoEvents(log); +} + +template +void testUnexpectedError() { + typedef ServiceState > State; + + // Start the server + std::shared_ptr state(new State); + ServerThread serverThread(state, true); + + const std::shared_ptr& log = state->getLog(); + + // Create a client + std::shared_ptr client = state->createClient(); + uint32_t connId = checkNewConnEvents(log); + + // Send the unexpectedExceptionWait() call + state->getHandler()->prepareTriggeredCall(); + string message = "1234 test 5678"; + client->send_unexpectedExceptionWait(message); + string callName = "ParentService.unexpectedExceptionWait"; + uint32_t callId + = checkCallHandlerEvents(log, connId, EventLog::ET_CALL_UNEXPECTED_EXCEPTION_WAIT, callName); + + // There shouldn't be any more events + checkNoEvents(log); + + // Trigger the handler to return + state->getHandler()->triggerPendingCalls(); + + // The handler will log an ET_WAIT_RETURN event when it wakes up + Event event = log->waitForEvent(); + BOOST_CHECK_EQUAL(EventLog::ET_WAIT_RETURN, event.type); + + // Now receive the response + try { + client->recv_unexpectedExceptionWait(); + BOOST_FAIL("expected TApplicationError to be thrown"); + } catch (const TApplicationException&) { + } + + // Now we should see a handler error event + event = log->waitForEvent(); + BOOST_CHECK_EQUAL(EventLog::ET_HANDLER_ERROR, event.type); + BOOST_CHECK_EQUAL(connId, event.connectionId); + BOOST_CHECK_EQUAL(callId, event.callId); + BOOST_CHECK_EQUAL(callName, event.message); + + // pre-write and post-write events aren't generated after a handler error + // (Even for non-oneway calls where a response is written.) + // + // A call finished event is logged when the call context is destroyed + event = log->waitForEvent(); + BOOST_CHECK_EQUAL(EventLog::ET_CALL_FINISHED, event.type); + BOOST_CHECK_EQUAL(connId, event.connectionId); + BOOST_CHECK_EQUAL(callId, event.callId); + BOOST_CHECK_EQUAL(callName, event.message); + + // There shouldn't be any more events + checkNoEvents(log); + + // Destroy the client, and check for connection closed events + client.reset(); + checkCloseEvents(log, connId); + + checkNoEvents(log); +} + +// Macro to define simple tests that can be used with all server types +#define DEFINE_SIMPLE_TESTS(Server, Template) \ + BOOST_AUTO_TEST_CASE(Server##_##Template##_basicService) { \ + testBasicService(); \ + } \ + BOOST_AUTO_TEST_CASE(Server##_##Template##_inheritedService) { \ + testInheritedService(); \ + } \ + BOOST_AUTO_TEST_CASE(Server##_##Template##_oneway) { \ + testOnewayCall(); \ + } \ + BOOST_AUTO_TEST_CASE(Server##_##Template##_exception) { \ + testExpectedError(); \ + } \ + BOOST_AUTO_TEST_CASE(Server##_##Template##_unexpectedException) { \ + testUnexpectedError(); \ + } + +// Tests that require the server to process multiple connections concurrently +// (i.e., not TSimpleServer) +#define DEFINE_CONCURRENT_SERVER_TESTS(Server, Template) \ + BOOST_AUTO_TEST_CASE(Server##_##Template##_separateConnections) { \ + testSeparateConnections(); \ + } + +// The testEventSequencing() test manually generates a request for the server, +// and doesn't work with TFramedTransport. Therefore we can't test it with +// TNonblockingServer. +#define DEFINE_NOFRAME_TESTS(Server, Template) \ + BOOST_AUTO_TEST_CASE(Server##_##Template##_eventSequencing) { \ + testEventSequencing(); \ + } + +#define DEFINE_TNONBLOCKINGSERVER_TESTS(Server, Template) \ + DEFINE_SIMPLE_TESTS(Server, Template) \ + DEFINE_CONCURRENT_SERVER_TESTS(Server, Template) + +#define DEFINE_ALL_SERVER_TESTS(Server, Template) \ + DEFINE_SIMPLE_TESTS(Server, Template) \ + DEFINE_CONCURRENT_SERVER_TESTS(Server, Template) \ + DEFINE_NOFRAME_TESTS(Server, Template) + +DEFINE_ALL_SERVER_TESTS(TThreadedServer, Templated) +DEFINE_ALL_SERVER_TESTS(TThreadedServer, Untemplated) +DEFINE_ALL_SERVER_TESTS(TThreadPoolServer, Templated) +DEFINE_ALL_SERVER_TESTS(TThreadPoolServer, Untemplated) + +DEFINE_TNONBLOCKINGSERVER_TESTS(TNonblockingServer, Templated) +DEFINE_TNONBLOCKINGSERVER_TESTS(TNonblockingServer, Untemplated) +DEFINE_TNONBLOCKINGSERVER_TESTS(TNonblockingServerNoThreads, Templated) +DEFINE_TNONBLOCKINGSERVER_TESTS(TNonblockingServerNoThreads, Untemplated) + +DEFINE_SIMPLE_TESTS(TSimpleServer, Templated) +DEFINE_SIMPLE_TESTS(TSimpleServer, Untemplated) +DEFINE_NOFRAME_TESTS(TSimpleServer, Templated) +DEFINE_NOFRAME_TESTS(TSimpleServer, Untemplated) + +// TODO: We should test TEventServer in the future. +// For now, it is known not to work correctly with TProcessorEventHandler. +#ifdef BOOST_TEST_DYN_LINK +bool init_unit_test_suite() { + ::boost::unit_test::framework::master_test_suite().p_name.value = "ProcessorTest"; + return true; +} + +int main( int argc, char* argv[] ) { + return ::boost::unit_test::unit_test_main(&init_unit_test_suite,argc,argv); +} +#else +::boost::unit_test::test_suite* init_unit_test_suite(int argc, char* argv[]) { + THRIFT_UNUSED_VARIABLE(argc); + THRIFT_UNUSED_VARIABLE(argv); + ::boost::unit_test::framework::master_test_suite().p_name.value = "ProcessorTest"; + return NULL; +} +#endif diff --git a/src/jaegertracing/thrift/lib/cpp/test/processor/ServerThread.cpp b/src/jaegertracing/thrift/lib/cpp/test/processor/ServerThread.cpp new file mode 100644 index 000000000..b0505005b --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/test/processor/ServerThread.cpp @@ -0,0 +1,152 @@ +/* + * 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. + */ +#ifndef _THRIFT_TEST_SERVERTHREAD_TCC_ +#define _THRIFT_TEST_SERVERTHREAD_TCC_ 1 + +#include "ServerThread.h" + +#include +#include +#include +#include +#include + +namespace apache { +namespace thrift { +namespace test { + +void ServerThread::start() { + assert(!running_); + running_ = true; + + helper_.reset(new Helper(this)); + + // Start the other thread + concurrency::ThreadFactory threadFactory; + threadFactory.setDetached(false); + thread_ = threadFactory.newThread(helper_); + + thread_->start(); + + // Wait on the other thread to tell us that it has successfully + // bound to the port and started listening (or until an error occurs). + concurrency::Synchronized s(serverMonitor_); + while (!serving_ && !error_) { + serverMonitor_.waitForever(); + } + + if (error_) { + throw transport::TTransportException(transport::TTransportException::NOT_OPEN, + "failed to bind on server socket"); + } +} + +void ServerThread::stop() { + if (!running_) { + return; + } + + // Tell the server to stop + server_->stop(); + running_ = false; + + // Wait for the server thread to exit + // + // Note: this only works if all client connections have closed. The servers + // generally wait for everything to be closed before exiting; there currently + // isn't a way to tell them to just exit now, and shut down existing + // connections. + thread_->join(); +} + +void ServerThread::run() { + /* + * Try binding to several ports, in case the one we want is already in use. + */ + port_ = 12345; + unsigned int maxRetries = 10; + for (unsigned int n = 0; n < maxRetries; ++n) { + // Create the server + server_ = serverState_->createServer(port_); + // Install our helper as the server event handler, so that our + // preServe() method will be called once we've successfully bound to + // the port and are about to start listening. + server_->setServerEventHandler(helper_); + + try { + // Try to serve requests + server_->serve(); + } catch (const TException&) { + // TNonblockingServer throws a generic TException if it fails to bind. + // If we get a TException, we'll optimistically assume the bind failed. + ++port_; + continue; + } + + // Seriously? serve() is pretty lame. If it fails to start serving it + // just returns rather than throwing an exception. + // + // We have to use our preServe() hook to tell if serve() successfully + // started serving and is returning because stop() is called, or if it just + // failed to start serving in the first place. + concurrency::Synchronized s(serverMonitor_); + if (serving_) { + // Oh good, we started serving and are exiting because + // we're trying to stop. + serving_ = false; + return; + } else { + // We never started serving, probably because we failed to bind to the + // port. Increment the port number and try again. + ++port_; + continue; + } + } + + // We failed to bind on any port. + concurrency::Synchronized s(serverMonitor_); + error_ = true; + serverMonitor_.notify(); +} + +void ServerThread::preServe() { + // We bound to the port successfully, and are about to start serving requests + serverState_->bindSuccessful(port_); + + // Set the real server event handler (replacing ourself) + std::shared_ptr serverEventHandler + = serverState_->getServerEventHandler(); + server_->setServerEventHandler(serverEventHandler); + + // Notify the main thread that we have successfully started serving requests + concurrency::Synchronized s(serverMonitor_); + serving_ = true; + serverMonitor_.notify(); + + // Invoke preServe() on the real event handler, since we ate + // the original preServe() event. + if (serverEventHandler) { + serverEventHandler->preServe(); + } +} +} +} +} // apache::thrift::test + +#endif // _THRIFT_TEST_SERVERTHREAD_TCC_ diff --git a/src/jaegertracing/thrift/lib/cpp/test/processor/ServerThread.h b/src/jaegertracing/thrift/lib/cpp/test/processor/ServerThread.h new file mode 100644 index 000000000..9cca2d600 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/test/processor/ServerThread.h @@ -0,0 +1,135 @@ +/* + * 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. + */ +#ifndef _THRIFT_TEST_SERVERTHREAD_H_ +#define _THRIFT_TEST_SERVERTHREAD_H_ 1 + +#include +#include +#include +#include + +#include "EventLog.h" + +namespace apache { +namespace thrift { +namespace test { + +/** + * A helper class to tell ServerThread how to create the server + */ +class ServerState { +public: + virtual ~ServerState() = default; + + /** + * Create a server to listen on the specified port. + * + * If the server returned fails to bind to the specified port when serve() is + * called on it, createServer() may be called again on a different port. + */ + virtual std::shared_ptr createServer(uint16_t port) = 0; + + /** + * Get the TServerEventHandler to set on the server. + * + * This is only called after the server successfully binds and is about to + * start serving traffic. It is invoked from the server thread, rather than + * the main thread. + */ + virtual std::shared_ptr getServerEventHandler() { + return std::shared_ptr(); + } + + /** + * This method is called in the server thread after server binding succeeds. + * + * Subclasses may override this method if they wish to record the final + * port that was used for the server. + */ + virtual void bindSuccessful(uint16_t /*port*/) {} +}; + +/** + * ServerThread starts a thrift server running in a separate thread. + */ +class ServerThread { +public: + ServerThread(const std::shared_ptr& state, bool autoStart) + : port_(0), + running_(false), + serving_(false), + error_(false), + serverState_(state) { + if (autoStart) { + start(); + } + } + + void start(); + void stop(); + + uint16_t getPort() const { return port_; } + + ~ServerThread() { + if (running_) { + try { + stop(); + } catch (...) { + GlobalOutput.printf("error shutting down server"); + } + } + } + +protected: + // Annoying. thrift forces us to use shared_ptr, so we have to use + // a helper class that we can allocate on the heap and give to thrift. + // It would be simpler if we could just make Runnable and TServerEventHandler + // private base classes of ServerThread. + class Helper : public concurrency::Runnable, public server::TServerEventHandler { + public: + Helper(ServerThread* serverThread) : serverThread_(serverThread) {} + + void run() override { serverThread_->run(); } + + void preServe() override { serverThread_->preServe(); } + + private: + ServerThread* serverThread_; + }; + + void run(); + void preServe(); + + std::shared_ptr helper_; + + uint16_t port_; + bool running_; + bool serving_; + bool error_; + concurrency::Monitor serverMonitor_; + + std::shared_ptr serverState_; + std::shared_ptr server_; + std::shared_ptr thread_; +}; +} +} +} // apache::thrift::test + +#endif // _THRIFT_TEST_SERVERTHREAD_H_ diff --git a/src/jaegertracing/thrift/lib/cpp/test/processor/proc.thrift b/src/jaegertracing/thrift/lib/cpp/test/processor/proc.thrift new file mode 100644 index 000000000..ac3c5f953 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/test/processor/proc.thrift @@ -0,0 +1,22 @@ +namespace cpp apache.thrift.test + +exception MyError { + 1: string message +} + +service ParentService { + i32 incrementGeneration() + i32 getGeneration() + void addString(1: string s) + list getStrings() + + binary getDataWait(1: i32 length) + oneway void onewayWait() + void exceptionWait(1: string message) throws (2: MyError error) + void unexpectedExceptionWait(1: string message) +} + +service ChildService extends ParentService { + i32 setValue(1: i32 value) + i32 getValue() +} diff --git a/src/jaegertracing/thrift/lib/cpp/test/qt/CMakeLists.txt b/src/jaegertracing/thrift/lib/cpp/test/qt/CMakeLists.txt new file mode 100644 index 000000000..7f341cc4c --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/test/qt/CMakeLists.txt @@ -0,0 +1,32 @@ +# +# 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. +# + +set(CMAKE_AUTOMOC ON) +find_package(Qt5 REQUIRED COMPONENTS Test Network) +set(TQTcpServerTest_Qt5_SOURCES + TQTcpServerTest.cpp +) +add_executable(TQTcpServerTest_Qt5 ${TQTcpServerTest_Qt5_SOURCES}) +target_link_libraries(TQTcpServerTest_Qt5 testgencpp_cob) +LINK_AGAINST_THRIFT_LIBRARY(TQTcpServerTest_Qt5 thriftqt5) +LINK_AGAINST_THRIFT_LIBRARY(TQTcpServerTest_Qt5 thrift) +target_link_libraries(TQTcpServerTest_Qt5 Qt5::Test Qt5::Network) + +add_test(NAME TQTcpServerTest_Qt5 COMMAND TQTcpServerTest_Qt5) + diff --git a/src/jaegertracing/thrift/lib/cpp/test/qt/TQTcpServerTest.cpp b/src/jaegertracing/thrift/lib/cpp/test/qt/TQTcpServerTest.cpp new file mode 100644 index 000000000..3371a9ae8 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/test/qt/TQTcpServerTest.cpp @@ -0,0 +1,113 @@ +#define BOOST_TEST_MODULE TQTcpServerTest +#include +#include + +#include +#include +#include +#include + +#ifndef Q_MOC_RUN + #include "thrift/protocol/TBinaryProtocol.h" + #include "thrift/async/TAsyncProcessor.h" + #include "thrift/qt/TQTcpServer.h" + #include "thrift/qt/TQIODeviceTransport.h" + + #include "gen-cpp/ParentService.h" +#endif + +using namespace apache::thrift; + +struct AsyncHandler : public test::ParentServiceCobSvIf { + std::vector strings; + void addString(std::function cob, const std::string& s) override { + strings.push_back(s); + cob(); + } + void getStrings(std::function const& _return)> cob) override { + cob(strings); + } + + // Overrides not used in this test + void incrementGeneration(std::function cob) override {} + void getGeneration(std::function cob) override {} + void getDataWait(std::function cob, + const int32_t length) override {} + void onewayWait(std::function cob) override {} + void exceptionWait( + std::function cob, + std::function /* exn_cob */, + const std::string& message) override {} + void unexpectedExceptionWait(std::function cob, const std::string& message) override {} +}; + +class TQTcpServerTest : public QObject { + Q_OBJECT + +private slots: + void initTestCase(); + void cleanupTestCase(); + void test_communicate(); + +private: + std::shared_ptr serverThread; + std::shared_ptr server; + std::shared_ptr client; +}; + +void TQTcpServerTest::initTestCase() { + // setup server + std::shared_ptr serverSocket = std::make_shared(); + server.reset(new async::TQTcpServer(serverSocket, + std::make_shared( + std::make_shared()), + std::make_shared())); + QVERIFY(serverSocket->listen(QHostAddress::LocalHost)); + int port = serverSocket->serverPort(); + QVERIFY(port > 0); + + //setup server thread and move server to it + serverThread.reset(new QThread()); + serverSocket->moveToThread(serverThread.get()); + server->moveToThread(serverThread.get()); + serverThread->start(); + + // setup client + std::shared_ptr socket = std::make_shared(); + client.reset(new test::ParentServiceClient(std::make_shared( + std::make_shared(socket)))); + socket->connectToHost(QHostAddress::LocalHost, port); + QVERIFY(socket->waitForConnected()); +} + +void TQTcpServerTest::cleanupTestCase() { + //first, stop the thread which holds the server + serverThread->quit(); + serverThread->wait(); + // now, it is safe to delete the server + server.reset(); + // delete thread now + serverThread.reset(); + + // cleanup client + client.reset(); +} + +void TQTcpServerTest::test_communicate() { + client->addString("foo"); + client->addString("bar"); + + std::vector reply; + client->getStrings(reply); + QCOMPARE(QString::fromStdString(reply[0]), QString("foo")); + QCOMPARE(QString::fromStdString(reply[1]), QString("bar")); +} + + +#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)) +QTEST_GUILESS_MAIN(TQTcpServerTest); +#else +#undef QT_GUI_LIB +QTEST_MAIN(TQTcpServerTest); +#endif +#include "TQTcpServerTest.moc" diff --git a/src/jaegertracing/thrift/lib/cpp/thrift-nb.pc.in b/src/jaegertracing/thrift/lib/cpp/thrift-nb.pc.in new file mode 100755 index 000000000..2c6a96973 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/thrift-nb.pc.in @@ -0,0 +1,30 @@ +# +# 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. +# + +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: Thrift +Description: Thrift Nonblocking API +Version: @VERSION@ +Requires: thrift = @VERSION@ +Libs: -L${libdir} -lthriftnb +Cflags: -I${includedir} diff --git a/src/jaegertracing/thrift/lib/cpp/thrift-qt5.pc.in b/src/jaegertracing/thrift/lib/cpp/thrift-qt5.pc.in new file mode 100755 index 000000000..a8b16663e --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/thrift-qt5.pc.in @@ -0,0 +1,30 @@ +# +# 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. +# + +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: Thrift +Description: Thrift Qt5 API +Version: @VERSION@ +Requires: thrift = @VERSION@ +Libs: -L${libdir} -lthriftqt5 +Cflags: -I${includedir} diff --git a/src/jaegertracing/thrift/lib/cpp/thrift-z.pc.in b/src/jaegertracing/thrift/lib/cpp/thrift-z.pc.in new file mode 100755 index 000000000..467d2e11c --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/thrift-z.pc.in @@ -0,0 +1,30 @@ +# +# 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. +# + +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: Thrift +Description: Thrift Zlib API +Version: @VERSION@ +Requires: thrift = @VERSION@ +Libs: -L${libdir} -lthriftz +Cflags: -I${includedir} diff --git a/src/jaegertracing/thrift/lib/cpp/thrift.pc.in b/src/jaegertracing/thrift/lib/cpp/thrift.pc.in new file mode 100755 index 000000000..d11e6db29 --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/thrift.pc.in @@ -0,0 +1,29 @@ +# +# 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. +# + +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: Thrift +Description: Thrift C++ API +Version: @VERSION@ +Libs: -L${libdir} -lthrift +Cflags: -I${includedir} diff --git a/src/jaegertracing/thrift/lib/cpp/thrift.sln b/src/jaegertracing/thrift/lib/cpp/thrift.sln new file mode 100644 index 000000000..cb0f4559b --- /dev/null +++ b/src/jaegertracing/thrift/lib/cpp/thrift.sln @@ -0,0 +1,61 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libthriftnb", "libthriftnb.vcxproj", "{D8696CCE-7D46-4659-B432-91754A41DEB0}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libthrift", "libthrift.vcxproj", "{DD26F57E-60F2-4F37-A616-D219A9BF338F}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "doc", "doc", "{006984E0-7CC1-47E2-ACD2-A40FE4D38693}" + ProjectSection(SolutionItems) = preProject + README.md = README.md + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Debug-mt|Win32 = Debug-mt|Win32 + Debug-mt|x64 = Debug-mt|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + Release-mt|Win32 = Release-mt|Win32 + Release-mt|x64 = Release-mt|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D8696CCE-7D46-4659-B432-91754A41DEB0}.Debug|Win32.ActiveCfg = Debug|Win32 + {D8696CCE-7D46-4659-B432-91754A41DEB0}.Debug|Win32.Build.0 = Debug|Win32 + {D8696CCE-7D46-4659-B432-91754A41DEB0}.Debug|x64.ActiveCfg = Debug|x64 + {D8696CCE-7D46-4659-B432-91754A41DEB0}.Debug|x64.Build.0 = Debug|x64 + {D8696CCE-7D46-4659-B432-91754A41DEB0}.Debug-mt|Win32.ActiveCfg = Debug-mt|Win32 + {D8696CCE-7D46-4659-B432-91754A41DEB0}.Debug-mt|Win32.Build.0 = Debug-mt|Win32 + {D8696CCE-7D46-4659-B432-91754A41DEB0}.Debug-mt|x64.ActiveCfg = Debug-mt|x64 + {D8696CCE-7D46-4659-B432-91754A41DEB0}.Debug-mt|x64.Build.0 = Debug-mt|x64 + {D8696CCE-7D46-4659-B432-91754A41DEB0}.Release|Win32.ActiveCfg = Release|Win32 + {D8696CCE-7D46-4659-B432-91754A41DEB0}.Release|Win32.Build.0 = Release|Win32 + {D8696CCE-7D46-4659-B432-91754A41DEB0}.Release|x64.ActiveCfg = Release|x64 + {D8696CCE-7D46-4659-B432-91754A41DEB0}.Release|x64.Build.0 = Release|x64 + {D8696CCE-7D46-4659-B432-91754A41DEB0}.Release-mt|Win32.ActiveCfg = Release-mt|Win32 + {D8696CCE-7D46-4659-B432-91754A41DEB0}.Release-mt|Win32.Build.0 = Release-mt|Win32 + {D8696CCE-7D46-4659-B432-91754A41DEB0}.Release-mt|x64.ActiveCfg = Release-mt|x64 + {D8696CCE-7D46-4659-B432-91754A41DEB0}.Release-mt|x64.Build.0 = Release-mt|x64 + {DD26F57E-60F2-4F37-A616-D219A9BF338F}.Debug|Win32.ActiveCfg = Debug|Win32 + {DD26F57E-60F2-4F37-A616-D219A9BF338F}.Debug|Win32.Build.0 = Debug|Win32 + {DD26F57E-60F2-4F37-A616-D219A9BF338F}.Debug|x64.ActiveCfg = Debug|x64 + {DD26F57E-60F2-4F37-A616-D219A9BF338F}.Debug|x64.Build.0 = Debug|x64 + {DD26F57E-60F2-4F37-A616-D219A9BF338F}.Debug-mt|Win32.ActiveCfg = Debug-mt|Win32 + {DD26F57E-60F2-4F37-A616-D219A9BF338F}.Debug-mt|Win32.Build.0 = Debug-mt|Win32 + {DD26F57E-60F2-4F37-A616-D219A9BF338F}.Debug-mt|x64.ActiveCfg = Debug-mt|x64 + {DD26F57E-60F2-4F37-A616-D219A9BF338F}.Debug-mt|x64.Build.0 = Debug-mt|x64 + {DD26F57E-60F2-4F37-A616-D219A9BF338F}.Release|Win32.ActiveCfg = Release|Win32 + {DD26F57E-60F2-4F37-A616-D219A9BF338F}.Release|Win32.Build.0 = Release|Win32 + {DD26F57E-60F2-4F37-A616-D219A9BF338F}.Release|x64.ActiveCfg = Release|x64 + {DD26F57E-60F2-4F37-A616-D219A9BF338F}.Release|x64.Build.0 = Release|x64 + {DD26F57E-60F2-4F37-A616-D219A9BF338F}.Release-mt|Win32.ActiveCfg = Release-mt|Win32 + {DD26F57E-60F2-4F37-A616-D219A9BF338F}.Release-mt|Win32.Build.0 = Release-mt|Win32 + {DD26F57E-60F2-4F37-A616-D219A9BF338F}.Release-mt|x64.ActiveCfg = Release-mt|x64 + {DD26F57E-60F2-4F37-A616-D219A9BF338F}.Release-mt|x64.Build.0 = Release-mt|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal -- cgit v1.2.3