include/boost/capy/ex/frame_allocator.hpp
100.0% Lines (10 / 10)
100.0% Functions (3 / 3)
Functions (3)
| Line | TLA | Hits | Source Code |
|---|---|---|---|
| 1 | // | ||
| 2 | // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com) | ||
| 3 | // Copyright (c) 2026 Michael Vandeberg | ||
| 4 | // | ||
| 5 | // Distributed under the Boost Software License, Version 1.0. (See accompanying | ||
| 6 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | ||
| 7 | // | ||
| 8 | // Official repository: https://github.com/cppalliance/capy | ||
| 9 | // | ||
| 10 | |||
| 11 | #ifndef BOOST_CAPY_FRAME_ALLOCATOR_HPP | ||
| 12 | #define BOOST_CAPY_FRAME_ALLOCATOR_HPP | ||
| 13 | |||
| 14 | #include <boost/capy/detail/config.hpp> | ||
| 15 | |||
| 16 | #include <coroutine> | ||
| 17 | #include <memory_resource> | ||
| 18 | |||
| 19 | /* Design rationale (pdimov): | ||
| 20 | |||
| 21 | This accessor is a thin wrapper over a thread-local pointer. | ||
| 22 | It returns exactly what was stored, including nullptr. No | ||
| 23 | dynamic initializer on the thread-local; a dynamic TLS | ||
| 24 | initializer moves you into a costlier implementation bucket | ||
| 25 | on some platforms - avoid it. | ||
| 26 | |||
| 27 | Null handling is the caller's responsibility (e.g. in | ||
| 28 | promise_type::operator new). The accessor must not substitute | ||
| 29 | a default, because there are multiple valid choices | ||
| 30 | (new_delete_resource, the default pmr resource, etc.). If | ||
| 31 | the allocator is not set, it reports "not set" and the | ||
| 32 | caller interprets that however it wants. | ||
| 33 | */ | ||
| 34 | |||
| 35 | namespace boost { | ||
| 36 | namespace capy { | ||
| 37 | |||
| 38 | namespace detail { | ||
| 39 | |||
| 40 | #if (defined(BOOST_CAPY_DYN_LINK) || defined(BOOST_ALL_DYN_LINK)) && \ | ||
| 41 | !defined(BOOST_CAPY_STATIC_LINK) | ||
| 42 | |||
| 43 | /** Return this thread's frame allocator slot. | ||
| 44 | |||
| 45 | A shared library build defines it in the library: an inline | ||
| 46 | definition gives each module its own slot, and a coroutine created | ||
| 47 | in one would not see the allocator a launcher in another installed. | ||
| 48 | A static build keeps it inline, since resuming a coroutine writes | ||
| 49 | it. | ||
| 50 | |||
| 51 | @return A reference to the calling thread's slot. | ||
| 52 | */ | ||
| 53 | BOOST_CAPY_DECL | ||
| 54 | std::pmr::memory_resource*& | ||
| 55 | current_frame_allocator_ref() noexcept; | ||
| 56 | |||
| 57 | #else | ||
| 58 | |||
| 59 | // MSVC may keep an inlined thread_local's address in a coroutine | ||
| 60 | // frame across a suspension, so a resumption on another thread | ||
| 61 | // would write the first thread's slot. | ||
| 62 | #if BOOST_CAPY_WORKAROUND(_MSC_VER, >= 1) && !defined(__clang__) | ||
| 63 | BOOST_CAPY_NOINLINE | ||
| 64 | #endif | ||
| 65 | inline std::pmr::memory_resource*& | ||
| 66 | current_frame_allocator_ref() noexcept | ||
| 67 | { | ||
| 68 | static thread_local std::pmr::memory_resource* mr = nullptr; | ||
| 69 | return mr; | ||
| 70 | } | ||
| 71 | |||
| 72 | #endif | ||
| 73 | |||
| 74 | } // namespace detail | ||
| 75 | |||
| 76 | /** Return the current frame allocator for this thread. | ||
| 77 | |||
| 78 | These accessors exist to implement the allocator | ||
| 79 | propagation portion of the @ref ContextAwaitable protocol. | ||
| 80 | Launcher functions (`run_async`, `run`) set the | ||
| 81 | thread-local value before invoking a child coroutine. | ||
| 82 | The child's `promise_type::operator new` reads it to | ||
| 83 | allocate the coroutine frame from the correct resource. | ||
| 84 | |||
| 85 | The value is only valid during a narrow execution | ||
| 86 | window. Between a coroutine's resumption | ||
| 87 | and the next suspension point, the protocol guarantees | ||
| 88 | that TLS contains the allocator associated with the | ||
| 89 | currently running chain. Outside that window the value | ||
| 90 | is indeterminate. Only code that implements an | ||
| 91 | @ref ContextAwaitable should call these functions. | ||
| 92 | |||
| 93 | A return value of `nullptr` means "not specified" - | ||
| 94 | no allocator is established for this chain. | ||
| 95 | The awaitable is free to use whatever allocation | ||
| 96 | strategy makes best sense (e.g. | ||
| 97 | `std::pmr::new_delete_resource()`). | ||
| 98 | |||
| 99 | Use of the frame allocator is optional. An awaitable | ||
| 100 | that does not consult this value to allocate its | ||
| 101 | coroutine frame is never wrong. However, a conforming | ||
| 102 | awaitable must still propagate the allocator faithfully | ||
| 103 | so that downstream coroutines can use it. | ||
| 104 | |||
| 105 | @return The thread-local memory_resource pointer, | ||
| 106 | or `nullptr` if none is set. | ||
| 107 | |||
| 108 | @see set_current_frame_allocator, ContextAwaitable | ||
| 109 | */ | ||
| 110 | inline | ||
| 111 | std::pmr::memory_resource* | ||
| 112 | 373205x | get_current_frame_allocator() noexcept | |
| 113 | { | ||
| 114 | 373205x | return detail::current_frame_allocator_ref(); | |
| 115 | } | ||
| 116 | |||
| 117 | /** Set the current frame allocator for this thread. | ||
| 118 | |||
| 119 | Installs @p mr as the frame allocator read by the | ||
| 120 | next coroutine's `promise_type::operator new` on | ||
| 121 | this thread. Only launcher functions and | ||
| 122 | @ref ContextAwaitable machinery should call this; see | ||
| 123 | @ref get_current_frame_allocator for the full protocol | ||
| 124 | description. | ||
| 125 | |||
| 126 | Passing `nullptr` means "not specified" - no | ||
| 127 | particular allocator is established for the chain. | ||
| 128 | |||
| 129 | @param mr The memory_resource to install, or | ||
| 130 | `nullptr` to clear. | ||
| 131 | |||
| 132 | @see get_current_frame_allocator, ContextAwaitable | ||
| 133 | */ | ||
| 134 | inline void | ||
| 135 | 478865x | set_current_frame_allocator( | |
| 136 | std::pmr::memory_resource* mr) noexcept | ||
| 137 | { | ||
| 138 | 478865x | detail::current_frame_allocator_ref() = mr; | |
| 139 | 478865x | } | |
| 140 | |||
| 141 | /** Resume a coroutine handle with frame-allocator TLS protection. | ||
| 142 | |||
| 143 | Saves the current thread-local frame allocator before | ||
| 144 | calling `h.resume()`, then restores it after the call | ||
| 145 | returns. This prevents a resumed coroutine's | ||
| 146 | `await_resume` from permanently overwriting the caller's | ||
| 147 | allocator value. | ||
| 148 | |||
| 149 | Between a coroutine's resumption and its next child | ||
| 150 | invocation, arbitrary user code may run. If that code | ||
| 151 | resumes a coroutine from a different chain on this | ||
| 152 | thread, the other coroutine's `await_resume` overwrites | ||
| 153 | TLS with its own allocator. Without save/restore, the | ||
| 154 | original coroutine's next child would allocate from | ||
| 155 | the wrong resource. | ||
| 156 | |||
| 157 | Event loops, strand dispatch loops, and any code that | ||
| 158 | calls `.resume()` on a coroutine handle should use | ||
| 159 | this function instead of calling `.resume()` directly. | ||
| 160 | See the @ref Executor concept documentation for details. | ||
| 161 | |||
| 162 | @param h The coroutine handle to resume. | ||
| 163 | |||
| 164 | @see get_current_frame_allocator, set_current_frame_allocator | ||
| 165 | */ | ||
| 166 | // tag::safe_resume[] | ||
| 167 | inline void | ||
| 168 | 154907x | safe_resume(std::coroutine_handle<> h) noexcept | |
| 169 | { | ||
| 170 | 154907x | auto* saved = get_current_frame_allocator(); | |
| 171 | 154907x | h.resume(); | |
| 172 | 154907x | set_current_frame_allocator(saved); | |
| 173 | 154907x | } | |
| 174 | // end::safe_resume[] | ||
| 175 | |||
| 176 | } // namespace capy | ||
| 177 | } // namespace boost | ||
| 178 | |||
| 179 | #endif | ||
| 180 |