include/boost/corosio/detail/continuation_op.hpp
69.6% Lines (16/23)
80.0% List of functions (4/5)
Functions (5)
Function
Calls
Lines
Blocks
boost::corosio::detail::continuation_op::continuation_op()
:44
163510x
100.0%
100.0%
boost::corosio::detail::continuation_op::operator()()
:49
8960x
100.0%
100.0%
boost::corosio::detail::continuation_op::destroy()
:60
4x
100.0%
100.0%
boost::corosio::detail::continuation_op::do_complete(void*, boost::corosio::detail::scheduler_op*, unsigned int, unsigned int)
:68
0
0.0%
0.0%
boost::corosio::detail::continuation_op::try_from_continuation(boost::capy::continuation&)
:93
10443x
100.0%
100.0%
| Line | TLA | Hits | Source Code |
|---|---|---|---|
| 1 | // | ||
| 2 | // Copyright (c) 2026 Michael Vandeberg | ||
| 3 | // | ||
| 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying | ||
| 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | ||
| 6 | // | ||
| 7 | // Official repository: https://github.com/cppalliance/corosio | ||
| 8 | // | ||
| 9 | |||
| 10 | #ifndef BOOST_COROSIO_DETAIL_CONTINUATION_OP_HPP | ||
| 11 | #define BOOST_COROSIO_DETAIL_CONTINUATION_OP_HPP | ||
| 12 | |||
| 13 | #include <boost/corosio/detail/scheduler_op.hpp> | ||
| 14 | #include <boost/capy/continuation.hpp> | ||
| 15 | |||
| 16 | #include <atomic> | ||
| 17 | #include <cstdint> | ||
| 18 | #include <cstring> | ||
| 19 | |||
| 20 | namespace boost::corosio::detail { | ||
| 21 | |||
| 22 | /* Scheduler operation that resumes a capy::continuation. | ||
| 23 | |||
| 24 | Embeds a continuation alongside a scheduler_op so the | ||
| 25 | scheduler can queue it in the same FIFO as I/O completions | ||
| 26 | without a heap allocation. The continuation lives in the | ||
| 27 | caller's coroutine frame (awaitable or op struct); this | ||
| 28 | wrapper gives it a scheduler_op identity. | ||
| 29 | |||
| 30 | io_context::executor_type::post(continuation&) uses | ||
| 31 | try_from_continuation() to recover the enclosing | ||
| 32 | continuation_op via a magic tag. The tag is read through | ||
| 33 | memcpy (not through a continuation_op*) so that UBSan | ||
| 34 | does not flag the speculative pointer arithmetic when the | ||
| 35 | continuation is not actually inside a continuation_op. | ||
| 36 | */ | ||
| 37 | struct continuation_op final : scheduler_op | ||
| 38 | { | ||
| 39 | static constexpr std::uint32_t magic_ = 0xC0710Au; | ||
| 40 | |||
| 41 | std::uint32_t tag_ = magic_; | ||
| 42 | capy::continuation cont; | ||
| 43 | |||
| 44 | 163510x | continuation_op() noexcept : scheduler_op(&do_complete) {} | |
| 45 | |||
| 46 | // Reactor backends (epoll, select, kqueue) dispatch through | ||
| 47 | // virtual operator()(). IOCP dispatches through func_ which | ||
| 48 | // routes to do_complete below. | ||
| 49 | 8960x | void operator()() override | |
| 50 | { | ||
| 51 | // ThreadSanitizer cannot instrument standalone fences; this acquire | ||
| 52 | // fence pairs with the scheduler's release and is intentional. | ||
| 53 | BOOST_COROSIO_GCC_WARNING_PUSH | ||
| 54 | BOOST_COROSIO_GCC_WARNING_DISABLE("-Wtsan") | ||
| 55 | std::atomic_thread_fence(std::memory_order_acquire); | ||
| 56 | BOOST_COROSIO_GCC_WARNING_POP | ||
| 57 | 8960x | cont.h.resume(); | |
| 58 | 8960x | } | |
| 59 | |||
| 60 | 4x | void destroy() override | |
| 61 | { | ||
| 62 | 4x | if (cont.h) | |
| 63 | 4x | cont.h.destroy(); | |
| 64 | 4x | } | |
| 65 | |||
| 66 | private: | ||
| 67 | // IOCP completion entry point. owner == nullptr means destroy. | ||
| 68 | ✗ | static void do_complete( | |
| 69 | void* owner, | ||
| 70 | scheduler_op* base, | ||
| 71 | std::uint32_t, | ||
| 72 | std::uint32_t) | ||
| 73 | { | ||
| 74 | ✗ | auto* self = static_cast<continuation_op*>(base); | |
| 75 | ✗ | if (!owner) | |
| 76 | { | ||
| 77 | ✗ | if (self->cont.h) | |
| 78 | ✗ | self->cont.h.destroy(); | |
| 79 | ✗ | return; | |
| 80 | } | ||
| 81 | BOOST_COROSIO_GCC_WARNING_PUSH | ||
| 82 | BOOST_COROSIO_GCC_WARNING_DISABLE("-Wtsan") | ||
| 83 | std::atomic_thread_fence(std::memory_order_acquire); | ||
| 84 | BOOST_COROSIO_GCC_WARNING_POP | ||
| 85 | ✗ | self->cont.h.resume(); | |
| 86 | } | ||
| 87 | |||
| 88 | public: | ||
| 89 | |||
| 90 | // Recover the enclosing continuation_op from its cont member. | ||
| 91 | // Returns nullptr if the continuation is not tagged (bare | ||
| 92 | // capy::continuation from capy internals like run_async). | ||
| 93 | 10443x | static continuation_op* try_from_continuation( | |
| 94 | capy::continuation& c) noexcept | ||
| 95 | { | ||
| 96 | // offsetof on non-standard-layout is conditionally-supported; | ||
| 97 | // suppress the warning — all targeted compilers handle this | ||
| 98 | // correctly and the self-relative arithmetic is move-safe. | ||
| 99 | #if defined(__GNUC__) || defined(__clang__) | ||
| 100 | #pragma GCC diagnostic push | ||
| 101 | #pragma GCC diagnostic ignored "-Winvalid-offsetof" | ||
| 102 | #endif | ||
| 103 | 10443x | constexpr auto cont_off = offsetof(continuation_op, cont); | |
| 104 | 10443x | constexpr auto tag_off = offsetof(continuation_op, tag_); | |
| 105 | #if defined(__GNUC__) || defined(__clang__) | ||
| 106 | #pragma GCC diagnostic pop | ||
| 107 | #endif | ||
| 108 | // Read the tag through memcpy from a char*, not through a | ||
| 109 | // continuation_op*. This avoids UBSan's vptr check when | ||
| 110 | // the continuation is not actually inside a continuation_op. | ||
| 111 | 10443x | auto* base = reinterpret_cast<char*>(&c) - cont_off; | |
| 112 | std::uint32_t tag; | ||
| 113 | 10443x | std::memcpy(&tag, base + tag_off, sizeof(tag)); | |
| 114 | 10443x | if (tag != magic_) | |
| 115 | 1479x | return nullptr; | |
| 116 | 8964x | return reinterpret_cast<continuation_op*>(base); | |
| 117 | } | ||
| 118 | }; | ||
| 119 | |||
| 120 | } // namespace boost::corosio::detail | ||
| 121 | |||
| 122 | #endif | ||
| 123 |