Skip to content

Commit 765f147

Browse files
committed
[BUG] Finish an unregistered operation before telling its handler
The branch added for a session the client never registered reported the failure and then finished the operation. The handler is replaceable, and one that calls FinishSession() from that event waits on a promise that only the Cleanup() below the dispatch can fulfil, on the same stack. Measured: the case hung for the whole 60 second bound, twice, exit 124. With the order reversed it returns in under a second, 3 of 3. The terminal state goes in before Cleanup() so it still reports one event rather than a manual cancel, and the report now happens after the operation is done, which is the order the exporters already follow: retire, then call anything replaceable. The case stays, because the failure mode is a hang. Nothing else in the file would notice the order being put back. 32 tests pass, 3 of 3. Signed-off-by: thc1006 <84045975+thc1006@users.noreply.github.com>
1 parent b87e7bc commit 765f147

2 files changed

Lines changed: 58 additions & 3 deletions

File tree

‎ext/src/http/client/curl/http_operation_curl.cc‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1489,11 +1489,16 @@ CURLcode HttpOperation::SendAsync(Session *session, std::function<void(HttpOpera
14891489
}
14901490
else if (!session->GetHttpClient().ScheduleAddSession(session->GetSessionId()))
14911491
{
1492-
// The same, except nobody cancelled anything. Name the failure first: Cleanup() would report
1493-
// a manual cancel, with a reason read from a curl result that is still CURLE_OK.
1492+
// The same, except nobody cancelled anything. The terminal state goes in first so Cleanup()
1493+
// does not report a manual cancel, with a reason read from a curl result that is still
1494+
// CURLE_OK, and the operation is finished before the handler hears about it: a handler that
1495+
// calls FinishSession() from this event would otherwise wait on the promise that the
1496+
// Cleanup() below it is the only thing able to fulfil.
1497+
session_state_.store(opentelemetry::ext::http::client::SessionState::CreateFailed,
1498+
std::memory_order_release);
1499+
Cleanup();
14941500
DispatchEvent(opentelemetry::ext::http::client::SessionState::CreateFailed,
14951501
"the session is not registered with this client");
1496-
Cleanup();
14971502
}
14981503

14991504
return CURLE_OK;

‎ext/test/http/curl_http_test.cc‎

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -783,6 +783,56 @@ TEST_F(BasicCurlHttpTests, UnparsableUrlsReleaseTheirOwnResources)
783783
EXPECT_EQ(1, second_handler->terminal_count_.load(std::memory_order_acquire));
784784
}
785785

786+
namespace
787+
{
788+
// The event is dispatched after the operation is finished, so a handler is free to call
789+
// FinishSession() from it. Reporting first instead leaves this waiting on a promise that only the
790+
// Cleanup() below the dispatch can fulfil, which hangs rather than fails, so this case has to run.
791+
class FinishFromEventHandler : public TerminalCountingHandler
792+
{
793+
public:
794+
void OnEvent(http_client::SessionState state, nostd::string_view reason) noexcept override
795+
{
796+
TerminalCountingHandler::OnEvent(state, reason);
797+
if (state == http_client::SessionState::CreateFailed && finish_target_ != nullptr)
798+
{
799+
auto *target = finish_target_;
800+
finish_target_ = nullptr;
801+
entered_.store(true, std::memory_order_release);
802+
target->FinishSession();
803+
returned_.store(true, std::memory_order_release);
804+
}
805+
}
806+
807+
http_client::Session *finish_target_ = nullptr;
808+
std::atomic<bool> entered_{false};
809+
std::atomic<bool> returned_{false};
810+
};
811+
} // namespace
812+
813+
TEST_F(BasicCurlHttpTests, FinishFromTheCreateFailedEventReturns)
814+
{
815+
auto session_manager = std::make_shared<http_client::curl::HttpCurlClientFactory>()->Create();
816+
ASSERT_TRUE(session_manager != nullptr);
817+
818+
auto session = session_manager->CreateSession("http://127.0.0.1:not-a-port");
819+
auto request = session->CreateRequest();
820+
request->SetUri("get/");
821+
822+
auto handler = std::make_shared<FinishFromEventHandler>();
823+
handler->finish_target_ = session.get();
824+
825+
session->SendRequest(handler);
826+
827+
ASSERT_TRUE(handler->entered_.load(std::memory_order_acquire))
828+
<< "the failure never reached the handler, so nothing was tested";
829+
EXPECT_TRUE(handler->returned_.load(std::memory_order_acquire))
830+
<< "FinishSession from this event waited on a promise only its own caller can set";
831+
832+
session->FinishSession();
833+
session_manager->FinishAllSessions();
834+
}
835+
786836
TEST_F(BasicCurlHttpTests, InvalidUrlCompletes)
787837
{
788838
auto session_manager = std::make_shared<http_client::curl::HttpCurlClientFactory>()->Create();

0 commit comments

Comments
 (0)