include/boost/capy/ex/frame_allocator.hpp
100.0% Lines (12/0/12)
100.0% List of functions (4/0/4)
Functions (4)
Function
Calls
Lines
Blocks
boost::capy::detail::current_frame_allocator_ref()
:41
115425x
100.0%
100.0%
boost::capy::get_current_frame_allocator()
:85
55536x
100.0%
100.0%
boost::capy::set_current_frame_allocator(std::pmr::memory_resource*)
:108
59889x
100.0%
100.0%
boost::capy::safe_resume(std::__n4861::coroutine_handle<void>)
:141
50405x
100.0%
100.0%
| 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 | inline std::pmr::memory_resource*& | ||
| 41 | 115425x | current_frame_allocator_ref() noexcept | |
| 42 | { | ||
| 43 | static thread_local std::pmr::memory_resource* mr = nullptr; | ||
| 44 | 115425x | return mr; | |
| 45 | } | ||
| 46 | |||
| 47 | } // namespace detail | ||
| 48 | |||
| 49 | /** Return the current frame allocator for this thread. | ||
| 50 | |||
| 51 | These accessors exist to implement the allocator | ||
| 52 | propagation portion of the @ref IoAwaitable protocol. | ||
| 53 | Launcher functions (`run_async`, `run`) set the | ||
| 54 | thread-local value before invoking a child coroutine. | ||
| 55 | The child's `promise_type::operator new` reads it to | ||
| 56 | allocate the coroutine frame from the correct resource. | ||
| 57 | |||
| 58 | The value is only valid during a narrow execution | ||
| 59 | window. Between a coroutine's resumption | ||
| 60 | and the next suspension point, the protocol guarantees | ||
| 61 | that TLS contains the allocator associated with the | ||
| 62 | currently running chain. Outside that window the value | ||
| 63 | is indeterminate. Only code that implements an | ||
| 64 | @ref IoAwaitable should call these functions. | ||
| 65 | |||
| 66 | A return value of `nullptr` means "not specified" - | ||
| 67 | no allocator is established for this chain. | ||
| 68 | The awaitable is free to use whatever allocation | ||
| 69 | strategy makes best sense (e.g. | ||
| 70 | `std::pmr::new_delete_resource()`). | ||
| 71 | |||
| 72 | Use of the frame allocator is optional. An awaitable | ||
| 73 | that does not consult this value to allocate its | ||
| 74 | coroutine frame is never wrong. However, a conforming | ||
| 75 | awaitable must still propagate the allocator faithfully | ||
| 76 | so that downstream coroutines can use it. | ||
| 77 | |||
| 78 | @return The thread-local memory_resource pointer, | ||
| 79 | or `nullptr` if none is set. | ||
| 80 | |||
| 81 | @see set_current_frame_allocator, IoAwaitable | ||
| 82 | */ | ||
| 83 | inline | ||
| 84 | std::pmr::memory_resource* | ||
| 85 | 55536x | get_current_frame_allocator() noexcept | |
| 86 | { | ||
| 87 | 55536x | return detail::current_frame_allocator_ref(); | |
| 88 | } | ||
| 89 | |||
| 90 | /** Set the current frame allocator for this thread. | ||
| 91 | |||
| 92 | Installs @p mr as the frame allocator read by the | ||
| 93 | next coroutine's `promise_type::operator new` on | ||
| 94 | this thread. Only launcher functions and | ||
| 95 | @ref IoAwaitable machinery should call this; see | ||
| 96 | @ref get_current_frame_allocator for the full protocol | ||
| 97 | description. | ||
| 98 | |||
| 99 | Passing `nullptr` means "not specified" - no | ||
| 100 | particular allocator is established for the chain. | ||
| 101 | |||
| 102 | @param mr The memory_resource to install, or | ||
| 103 | `nullptr` to clear. | ||
| 104 | |||
| 105 | @see get_current_frame_allocator, IoAwaitable | ||
| 106 | */ | ||
| 107 | inline void | ||
| 108 | 59889x | set_current_frame_allocator( | |
| 109 | std::pmr::memory_resource* mr) noexcept | ||
| 110 | { | ||
| 111 | 59889x | detail::current_frame_allocator_ref() = mr; | |
| 112 | 59889x | } | |
| 113 | |||
| 114 | /** Resume a coroutine handle with frame-allocator TLS protection. | ||
| 115 | |||
| 116 | Saves the current thread-local frame allocator before | ||
| 117 | calling `h.resume()`, then restores it after the call | ||
| 118 | returns. This prevents a resumed coroutine's | ||
| 119 | `await_resume` from permanently overwriting the caller's | ||
| 120 | allocator value. | ||
| 121 | |||
| 122 | Between a coroutine's resumption and its next child | ||
| 123 | invocation, arbitrary user code may run. If that code | ||
| 124 | resumes a coroutine from a different chain on this | ||
| 125 | thread, the other coroutine's `await_resume` overwrites | ||
| 126 | TLS with its own allocator. Without save/restore, the | ||
| 127 | original coroutine's next child would allocate from | ||
| 128 | the wrong resource. | ||
| 129 | |||
| 130 | Event loops, strand dispatch loops, and any code that | ||
| 131 | calls `.resume()` on a coroutine handle should use | ||
| 132 | this function instead of calling `.resume()` directly. | ||
| 133 | See the @ref Executor concept documentation for details. | ||
| 134 | |||
| 135 | @param h The coroutine handle to resume. | ||
| 136 | |||
| 137 | @see get_current_frame_allocator, set_current_frame_allocator | ||
| 138 | */ | ||
| 139 | // tag::safe_resume[] | ||
| 140 | inline void | ||
| 141 | 50405x | safe_resume(std::coroutine_handle<> h) noexcept | |
| 142 | { | ||
| 143 | 50405x | auto* saved = get_current_frame_allocator(); | |
| 144 | 50405x | h.resume(); | |
| 145 | 50405x | set_current_frame_allocator(saved); | |
| 146 | 50405x | } | |
| 147 | // end::safe_resume[] | ||
| 148 | |||
| 149 | } // namespace capy | ||
| 150 | } // namespace boost | ||
| 151 | |||
| 152 | #endif | ||
| 153 |