From c1d9a2618df6c6a94eeafc9ff58fab33b83b2463 Mon Sep 17 00:00:00 2001 From: "Adam C. Emerson" Date: Thu, 14 Aug 2025 22:43:25 -0400 Subject: [PATCH] common/async: `blocked_completion` uses our `redirect_error` Generalizing it to exceptions, etc. Signed-off-by: Adam C. Emerson --- src/common/async/blocked_completion.h | 28 ++++++++++---- src/test/common/test_blocked_completion.cc | 45 +++++++++++++++++++++- 2 files changed, 65 insertions(+), 8 deletions(-) diff --git a/src/common/async/blocked_completion.h b/src/common/async/blocked_completion.h index bdf75d906e5..e78f1db7c44 100644 --- a/src/common/async/blocked_completion.h +++ b/src/common/async/blocked_completion.h @@ -16,30 +16,44 @@ #ifndef CEPH_COMMON_ASYNC_BLOCKED_COMPLETION_H #define CEPH_COMMON_ASYNC_BLOCKED_COMPLETION_H +/// \file common/async/blocked_completion.h +/// +/// \brief A blocking `completion token` +/// +/// This completion token will actually hard-block your thread. Do not +/// do so within an `io_context` thread. + #include #include #include #include -#include +#include #include -#include +#include "common/async/redirect_error.h" namespace ceph::async { - -namespace bs = boost::system; - +/// A \ref `completion token` type that blocks the current thread +/// until the operation completes. +/// +/// \warning Keep deadlocks in mind. Do not use in threads that expect +/// to be asynchronous. class use_blocked_t { public: use_blocked_t() = default; - auto operator [](bs::error_code& ec) const { - return boost::asio::redirect_error(use_blocked_t{}, ec); + /// Redirect the \ref `disposition` of the operation to a + /// variable. (A `disposition` is a concept generalizing error codes + /// and exceptions.) + template + auto operator [](Disposition& d) const { + return redirect_error(use_blocked_t{}, d); } }; +/// The archetypical instance of `use_blocked_t` inline constexpr use_blocked_t use_blocked; namespace detail { diff --git a/src/test/common/test_blocked_completion.cc b/src/test/common/test_blocked_completion.cc index 72def49a1f4..c2e298deb1a 100644 --- a/src/test/common/test_blocked_completion.cc +++ b/src/test/common/test_blocked_completion.cc @@ -12,9 +12,14 @@ * */ +#include "common/async/blocked_completion.h" + +#include #include +#include #include +#include #include #include #include @@ -23,7 +28,7 @@ #include -#include "common/async/blocked_completion.h" + using namespace std::literals; namespace asio = boost::asio; @@ -152,6 +157,44 @@ TEST(BlockedCompletion, AnError) EXPECT_EQ(sys::error_code(EDOM, sys::system_category()), ec); } +TEST(BlockedCompletion, AnException) +{ + context_thread t; + std::exception_ptr e = nullptr; + + ASSERT_FALSE(e); + ASSERT_THROW(asio::co_spawn(t.get_executor(), + []() -> asio::awaitable { + throw std::exception{}; + co_return; + }, async::use_blocked), + std::exception); + // Have yet to divert an exception + ASSERT_FALSE(e); + + ASSERT_NO_THROW(asio::co_spawn(t.get_executor(), + []() -> asio::awaitable { + throw std::exception{}; + co_return; + }, async::use_blocked[e])); + // Exception diverted + ASSERT_TRUE(e); + + ASSERT_NO_THROW(asio::co_spawn(t.get_executor(), + []() -> asio::awaitable { + co_return; + }, async::use_blocked)); + // Previously diverted exception not overwritten + ASSERT_TRUE(e); + + ASSERT_NO_THROW(asio::co_spawn(t.get_executor(), + []() -> asio::awaitable { + co_return; + }, async::use_blocked[e])); + // Success diverted to overwrite previous exception + ASSERT_FALSE(e); +} + TEST(BlockedCompletion, MoveOnly) { context_thread t; -- 2.39.5