include/boost/corosio/detail/continuation_op.hpp
53.6% Lines (15/28)
71.4% List of functions (5/7)
25.0% Branches (2/8)
Functions (7)
Function
Calls
Lines
Branches
Blocks
boost::corosio::detail::continuation_op::continuation_op(boost::corosio::detail::continuation_op&&)
:37
14558x
100.0%
–
100.0%
boost::corosio::detail::continuation_op::~continuation_op()
:37
160396x
100.0%
–
100.0%
boost::corosio::detail::continuation_op::continuation_op()
:44
145838x
100.0%
–
100.0%
boost::corosio::detail::continuation_op::operator()()
:49
7241x
100.0%
–
100.0%
boost::corosio::detail::continuation_op::destroy()
:55
0
0.0%
0.0%
0.0%
boost::corosio::detail::continuation_op::do_complete(void*, boost::corosio::detail::scheduler_op*, unsigned int, unsigned int)
:63
0
0.0%
0.0%
0.0%
boost::corosio::detail::continuation_op::try_from_continuation(boost::capy::continuation&)
:85
12646x
100.0%
100.0%
100.0%
| Line | Branch | 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 | 72919x | std::uint32_t tag_ = magic_; | ||
| 42 | capy::continuation cont; | |||
| 43 | ||||
| 44 | 218757x | 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 | 7241x | void operator()() override | ||
| 50 | { | |||
| 51 | 7241x | std::atomic_thread_fence(std::memory_order_acquire); | ||
| 52 | 7241x | cont.h.resume(); | ||
| 53 | 7241x | } | ||
| 54 | ||||
| 55 | ✗ | void destroy() override | ||
| 56 | { | |||
| 57 | ✗ | if (cont.h) | ||
| 58 | ✗ | cont.h.destroy(); | ||
| 59 | ✗ | } | ||
| 60 | ||||
| 61 | private: | |||
| 62 | // IOCP completion entry point. owner == nullptr means destroy. | |||
| 63 | ✗ | static void do_complete( | ||
| 64 | void* owner, | |||
| 65 | scheduler_op* base, | |||
| 66 | std::uint32_t, | |||
| 67 | std::uint32_t) | |||
| 68 | { | |||
| 69 | ✗ | auto* self = static_cast<continuation_op*>(base); | ||
| 70 | ✗ | if (!owner) | ||
| 71 | { | |||
| 72 | ✗ | if (self->cont.h) | ||
| 73 | ✗ | self->cont.h.destroy(); | ||
| 74 | ✗ | return; | ||
| 75 | } | |||
| 76 | ✗ | std::atomic_thread_fence(std::memory_order_acquire); | ||
| 77 | ✗ | self->cont.h.resume(); | ||
| 78 | ✗ | } | ||
| 79 | ||||
| 80 | public: | |||
| 81 | ||||
| 82 | // Recover the enclosing continuation_op from its cont member. | |||
| 83 | // Returns nullptr if the continuation is not tagged (bare | |||
| 84 | // capy::continuation from capy internals like run_async). | |||
| 85 | 12646x | static continuation_op* try_from_continuation( | ||
| 86 | capy::continuation& c) noexcept | |||
| 87 | { | |||
| 88 | // offsetof on non-standard-layout is conditionally-supported; | |||
| 89 | // suppress the warning — all targeted compilers handle this | |||
| 90 | // correctly and the self-relative arithmetic is move-safe. | |||
| 91 | #if defined(__GNUC__) || defined(__clang__) | |||
| 92 | #pragma GCC diagnostic push | |||
| 93 | #pragma GCC diagnostic ignored "-Winvalid-offsetof" | |||
| 94 | #endif | |||
| 95 | 12646x | constexpr auto cont_off = offsetof(continuation_op, cont); | ||
| 96 | 12646x | constexpr auto tag_off = offsetof(continuation_op, tag_); | ||
| 97 | #if defined(__GNUC__) || defined(__clang__) | |||
| 98 | #pragma GCC diagnostic pop | |||
| 99 | #endif | |||
| 100 | // Read the tag through memcpy from a char*, not through a | |||
| 101 | // continuation_op*. This avoids UBSan's vptr check when | |||
| 102 | // the continuation is not actually inside a continuation_op. | |||
| 103 | 12646x | auto* base = reinterpret_cast<char*>(&c) - cont_off; | ||
| 104 | std::uint32_t tag; | |||
| 105 | 12646x | std::memcpy(&tag, base + tag_off, sizeof(tag)); | ||
| 106 |
2/2✓ Branch 0 taken 5405 times.
✓ Branch 1 taken 7241 times.
|
12646x | if (tag != magic_) | |
| 107 | 5405x | return nullptr; | ||
| 108 | 7241x | return reinterpret_cast<continuation_op*>(base); | ||
| 109 | 12646x | } | ||
| 110 | }; | |||
| 111 | ||||
| 112 | } // namespace boost::corosio::detail | |||
| 113 | ||||
| 114 | #endif | |||
| 115 |