From: Casey Bodley Date: Mon, 24 Mar 2025 16:50:16 +0000 (-0400) Subject: common/async: yield_waiter overloads for unique_lock X-Git-Tag: v21.0.1~14^2~20 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=604fad5692c61d23a22e0512b2b83c1e3253b2aa;p=ceph.git common/async: yield_waiter overloads for unique_lock if async_wait() can race with complete() across threads, the yield_waiter's handler_state needs to be protected by a mutex. add an async_wait() overload for unique_lock that behaves like condition_variable::wait(): the lock is released immediately before suspending, and reacquired immediately before calling its completion handler Signed-off-by: Casey Bodley --- diff --git a/src/common/async/yield_waiter.h b/src/common/async/yield_waiter.h index 9206ac1f505..6ace1f2efc4 100644 --- a/src/common/async/yield_waiter.h +++ b/src/common/async/yield_waiter.h @@ -16,6 +16,7 @@ #pragma once #include +#include #include #include #include @@ -25,6 +26,29 @@ namespace ceph::async { +namespace detail { + +// handler wrapper that reacquires a lock immediately before completion +template +struct lock_handler { + Handler handler; + BasicLockable* lock = nullptr; + + template + void operator()(Args&& ...args) { + if (lock) { + lock->lock(); + } + std::move(handler)(std::forward(args)...); + } +}; + +// deduction guide required by windows? +template +lock_handler(Handler&&, BasicLockable*) -> lock_handler; + +} // namespace detail + /// Captures a yield_context handler for deferred completion or cancellation. template class yield_waiter { @@ -52,7 +76,25 @@ class yield_waiter { if (slot.is_connected()) { slot.template emplace(this); } - state.emplace(std::move(h)); + constexpr std::unique_lock* lock = nullptr; + state.emplace(std::move(h), lock); + }, token); + } + + /// Suspends the given yield_context until the captured handler is invoked + /// via complete() or cancel(). The given lock is released immediately before + /// it suspends and reacquired immediately after it resumes. + template + auto async_wait(std::unique_lock& lock, CompletionToken&& token) + { + return boost::asio::async_initiate( + [this, &lock] (handler_type h) { + auto slot = get_associated_cancellation_slot(h); + if (slot.is_connected()) { + slot.template emplace(this); + } + state.emplace(std::move(h), &lock); + lock.unlock(); // unlock before suspend }, token); } @@ -61,8 +103,10 @@ class yield_waiter { { auto s = std::move(*state); state.reset(); - auto h = boost::asio::append(std::move(s.handler), ec, std::move(value)); - boost::asio::dispatch(std::move(h)); + boost::asio::dispatch( + boost::asio::append( + detail::lock_handler{std::move(s.handler), s.lock}, + ec, std::move(value))); } /// Destroy the completion handler. @@ -80,10 +124,10 @@ class yield_waiter { struct handler_state { handler_type handler; work_guard work; + std::unique_lock* lock = nullptr; - explicit handler_state(handler_type&& h) - : handler(std::move(h)), - work(make_work_guard(handler)) + handler_state(handler_type&& h, std::unique_lock* lock) + : handler(std::move(h)), work(make_work_guard(handler)), lock(lock) {} }; std::optional state; @@ -134,7 +178,25 @@ class yield_waiter { if (slot.is_connected()) { slot.template emplace(this); } - state.emplace(std::move(h)); + constexpr std::unique_lock* lock = nullptr; + state.emplace(std::move(h), lock); + }, token); + } + + /// Suspends the given yield_context until the captured handler is invoked + /// via complete() or cancel(). The given lock is released immediately before + /// it suspends and reacquired immediately after it resumes. + template + auto async_wait(std::unique_lock& lock, CompletionToken&& token) + { + return boost::asio::async_initiate( + [this, &lock] (handler_type h) { + auto slot = get_associated_cancellation_slot(h); + if (slot.is_connected()) { + slot.template emplace(this); + } + state.emplace(std::move(h), &lock); + lock.unlock(); // unlock before suspend }, token); } @@ -143,7 +205,9 @@ class yield_waiter { { auto s = std::move(*state); state.reset(); - boost::asio::dispatch(boost::asio::append(std::move(s.handler), ec)); + boost::asio::dispatch( + boost::asio::append( + detail::lock_handler{std::move(s.handler), s.lock}, ec)); } /// Destroy the completion handler. @@ -161,10 +225,10 @@ class yield_waiter { struct handler_state { handler_type handler; work_guard work; + std::unique_lock* lock = nullptr; - explicit handler_state(handler_type&& h) - : handler(std::move(h)), - work(make_work_guard(handler)) + handler_state(handler_type&& h, std::unique_lock* lock) + : handler(std::move(h)), work(make_work_guard(handler)), lock(lock) {} }; std::optional state; @@ -189,3 +253,23 @@ class yield_waiter { }; } // namespace ceph::async + +namespace boost::asio { + +// forward the handler's associated executor, allocator, cancellation slot, etc +template