|
13 | 13 | # include <numeric> |
14 | 14 | #endif // ENABLE_OTLP_COMPRESSION_PREVIEW |
15 | 15 |
|
| 16 | +#include <algorithm> |
16 | 17 | #include <atomic> |
17 | 18 | #include <chrono> |
18 | 19 | #include <condition_variable> |
|
34 | 35 | #include "opentelemetry/ext/http/server/http_server.h" |
35 | 36 | #include "opentelemetry/nostd/function_ref.h" |
36 | 37 | #include "opentelemetry/nostd/string_view.h" |
| 38 | +#include "opentelemetry/sdk/common/global_log_handler.h" |
37 | 39 | #include "opentelemetry/version.h" |
38 | 40 |
|
39 | 41 | constexpr int HTTP_PORT{19000}; |
40 | 42 |
|
41 | 43 | namespace curl = opentelemetry::ext::http::client::curl; |
42 | 44 | namespace http_client = opentelemetry::ext::http::client; |
| 45 | +namespace sdk_common = opentelemetry::sdk::common; |
43 | 46 | namespace nostd = opentelemetry::nostd; |
44 | 47 |
|
45 | 48 | OPENTELEMETRY_BEGIN_NAMESPACE |
@@ -783,6 +786,110 @@ TEST_F(BasicCurlHttpTests, AnUnscheduledSessionTellsAHandlerNothingElseHolds) |
783 | 786 | http_client::curl::HttpClientTestPeer::RemoveSessions(client); |
784 | 787 | } |
785 | 788 |
|
| 789 | +// Restores the global log handler and level whatever the case does, since both are process wide |
| 790 | +// and gtest runs one process per case only under CTest. |
| 791 | +class ScopedLogHandler |
| 792 | +{ |
| 793 | +public: |
| 794 | + explicit ScopedLogHandler(nostd::shared_ptr<sdk_common::internal_log::LogHandler> handler) |
| 795 | + : previous_handler_(sdk_common::internal_log::GlobalLogHandler::GetLogHandler()), |
| 796 | + previous_level_(sdk_common::internal_log::GlobalLogHandler::GetLogLevel()) |
| 797 | + { |
| 798 | + sdk_common::internal_log::GlobalLogHandler::SetLogHandler(handler); |
| 799 | + sdk_common::internal_log::GlobalLogHandler::SetLogLevel( |
| 800 | + sdk_common::internal_log::LogLevel::Debug); |
| 801 | + } |
| 802 | + |
| 803 | + ~ScopedLogHandler() |
| 804 | + { |
| 805 | + sdk_common::internal_log::GlobalLogHandler::SetLogHandler(previous_handler_); |
| 806 | + sdk_common::internal_log::GlobalLogHandler::SetLogLevel(previous_level_); |
| 807 | + } |
| 808 | + |
| 809 | + ScopedLogHandler(const ScopedLogHandler &) = delete; |
| 810 | + ScopedLogHandler &operator=(const ScopedLogHandler &) = delete; |
| 811 | + |
| 812 | +private: |
| 813 | + nostd::shared_ptr<sdk_common::internal_log::LogHandler> previous_handler_; |
| 814 | + sdk_common::internal_log::LogLevel previous_level_; |
| 815 | +}; |
| 816 | + |
| 817 | +// Runs whatever the case gives it, on the thread that wrote the log line. |
| 818 | +class ObservingLogHandler : public sdk_common::internal_log::LogHandler |
| 819 | +{ |
| 820 | +public: |
| 821 | + explicit ObservingLogHandler(std::function<void()> observe) : observe_(std::move(observe)) {} |
| 822 | + |
| 823 | + void Handle(sdk_common::internal_log::LogLevel /* level */, |
| 824 | + const char * /* file */, |
| 825 | + int /* line */, |
| 826 | + const char * /* msg */, |
| 827 | + const sdk_common::AttributeMap & /* attributes */) noexcept override |
| 828 | + { |
| 829 | + observe_(); |
| 830 | + } |
| 831 | + |
| 832 | +private: |
| 833 | + std::function<void()> observe_; |
| 834 | +}; |
| 835 | + |
| 836 | +// The refusal is reported through the global log handler, which is application code that can call |
| 837 | +// back into this client. Reporting before the operation is settled leaves a handler that answers |
| 838 | +// with FinishSession() waiting on a promise only this thread can fulfil. |
| 839 | +TEST_F(BasicCurlHttpTests, ARejectedSessionIsSettledBeforeItIsReported) |
| 840 | +{ |
| 841 | + curl::HttpClient client; |
| 842 | + |
| 843 | + auto session = client.CreateSession("http://127.0.0.1:19000"); |
| 844 | + ASSERT_TRUE(session != nullptr); |
| 845 | + auto curl_session = std::static_pointer_cast<curl::Session>(session); |
| 846 | + |
| 847 | + auto handler = std::make_shared<RecordingHandler>(); |
| 848 | + http_client::HttpSslOptions no_ssl; |
| 849 | + http_client::Body body; |
| 850 | + http_client::Headers headers; |
| 851 | + http_client::RetryPolicy no_retry{}; |
| 852 | + http_client::Compression compression = http_client::Compression::kNone; |
| 853 | + |
| 854 | + curl_session->GetOperation().reset(new curl::HttpOperation( |
| 855 | + http_client::Method::Get, "http://127.0.0.1:19000/get/", no_ssl, handler.get(), headers, body, |
| 856 | + compression, false, curl::kDefaultHttpConnTimeout, false, false, no_retry)); |
| 857 | + |
| 858 | + ASSERT_EQ(CURLE_OK, curl_session->GetOperation()->SendAsync( |
| 859 | + curl_session.get(), [](curl::HttpOperation & /* operation */) {})); |
| 860 | + |
| 861 | + std::atomic<int> log_calls{0}; |
| 862 | + std::atomic<int> settled_when_logged{0}; |
| 863 | + auto observer = nostd::shared_ptr<sdk_common::internal_log::LogHandler>( |
| 864 | + new ObservingLogHandler([&handler, &log_calls, &settled_when_logged]() { |
| 865 | + log_calls.fetch_add(1, std::memory_order_release); |
| 866 | + |
| 867 | + // The terminal event specifically. Created arrives during setup, so "the handler has |
| 868 | + // seen something" is true in either order. |
| 869 | + const std::vector<http_client::SessionState> states = handler->States(); |
| 870 | + if (std::find(states.begin(), states.end(), http_client::SessionState::CreateFailed) != |
| 871 | + states.end()) |
| 872 | + { |
| 873 | + settled_when_logged.fetch_add(1, std::memory_order_release); |
| 874 | + } |
| 875 | + })); |
| 876 | + |
| 877 | + { |
| 878 | + ScopedLogHandler scoped{observer}; |
| 879 | + http_client::curl::HttpClientTestPeer::RefuseAdds(client); |
| 880 | + http_client::curl::HttpClientTestPeer::AddSessions(client); |
| 881 | + http_client::curl::HttpClientTestPeer::AllowAdds(client); |
| 882 | + } |
| 883 | + |
| 884 | + ASSERT_EQ(1, log_calls.load(std::memory_order_acquire)) |
| 885 | + << "the refusal was not reported once, so this case is not observing what it says it is"; |
| 886 | + EXPECT_EQ(1, settled_when_logged.load(std::memory_order_acquire)) |
| 887 | + << "the refusal was reported while the operation was still unsettled, so a log handler " |
| 888 | + << "calling FinishSession() from here would wait on a promise only this thread fulfils"; |
| 889 | + |
| 890 | + http_client::curl::HttpClientTestPeer::RemoveSessions(client); |
| 891 | +} |
| 892 | + |
786 | 893 | // The third way an operation ends up with nothing to run it. SendAsync does the whole async |
787 | 894 | // setup and Session::SendRequest is what spawns the worker, so the add runs here on one thread |
788 | 895 | // against a multi handle that refuses it, which is what a multi handle that failed to initialize |
|
0 commit comments