include/boost/capy/ex/frame_allocator.hpp
100.0% Lines (12/12)
100.0% List of functions (4/4)
Functions (4)
Function
Calls
Lines
Blocks
boost::capy::detail::current_frame_allocator_ref()
:40
31694x
100.0%
100.0%
boost::capy::get_current_frame_allocator()
:84
9703x
100.0%
100.0%
boost::capy::set_current_frame_allocator(std::pmr::memory_resource*)
:107
21991x
100.0%
100.0%
boost::capy::safe_resume(std::__n4861::coroutine_handle<void>)
:139
1374x
100.0%
100.0%
| Line | TLA | Hits | Source Code |
|---|---|---|---|
| 1 | // | ||
| 2 | // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com) | ||
| 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/capy | ||
| 8 | // | ||
| 9 | |||
| 10 | #ifndef BOOST_CAPY_FRAME_ALLOCATOR_HPP | ||
| 11 | #define BOOST_CAPY_FRAME_ALLOCATOR_HPP | ||
| 12 | |||
| 13 | #include <boost/capy/detail/config.hpp> | ||
| 14 | |||
| 15 | #include <coroutine> | ||
| 16 | #include <memory_resource> | ||
| 17 | |||
| 18 | /* Design rationale (pdimov): | ||
| 19 | |||
| 20 | This accessor is a thin wrapper over a thread-local pointer. | ||
| 21 | It returns exactly what was stored, including nullptr. No | ||
| 22 | dynamic initializer on the thread-local; a dynamic TLS | ||
| 23 | initializer moves you into a costlier implementation bucket | ||
| 24 | on some platforms - avoid it. | ||
| 25 | |||
| 26 | Null handling is the caller's responsibility (e.g. in | ||
| 27 | promise_type::operator new). The accessor must not substitute | ||
| 28 | a default, because there are multiple valid choices | ||
| 29 | (new_delete_resource, the default pmr resource, etc.). If | ||
| 30 | the allocator is not set, it reports "not set" and the | ||
| 31 | caller interprets that however it wants. | ||
| 32 | */ | ||
| 33 | |||
| 34 | namespace boost { | ||
| 35 | namespace capy { | ||
| 36 | |||
| 37 | namespace detail { | ||
| 38 | |||
| 39 | inline std::pmr::memory_resource*& | ||
| 40 | 31694x | current_frame_allocator_ref() noexcept | |
| 41 | { | ||
| 42 | static thread_local std::pmr::memory_resource* mr = nullptr; | ||
| 43 | 31694x | return mr; | |
| 44 | } | ||
| 45 | |||
| 46 | } // namespace detail | ||
| 47 | |||
| 48 | /** Return the current frame allocator for this thread. | ||
| 49 | |||
| 50 | These accessors exist to implement the allocator | ||
| 51 | propagation portion of the @ref IoAwaitable protocol. | ||
| 52 | Launch functions (`run_async`, `run`) set the | ||
| 53 | thread-local value before invoking a child coroutine; | ||
| 54 | the child's `promise_type::operator new` reads it to | ||
| 55 | allocate the coroutine frame from the correct resource. | ||
| 56 | |||
| 57 | The value is only valid during a narrow execution | ||
| 58 | window. Between a coroutine's resumption | ||
| 59 | and the next suspension point, the protocol guarantees | ||
| 60 | that TLS contains the allocator associated with the | ||
| 61 | currently running chain. Outside that window the value | ||
| 62 | is indeterminate. Only code that implements an | ||
| 63 | @ref IoAwaitable should call these functions. | ||
| 64 | |||
| 65 | A return value of `nullptr` means "not specified" - | ||
| 66 | no allocator has been established for this chain. | ||
| 67 | The awaitable is free to use whatever allocation | ||
| 68 | strategy makes best sense (e.g. | ||
| 69 | `std::pmr::new_delete_resource()`). | ||
| 70 | |||
| 71 | Use of the frame allocator is optional. An awaitable | ||
| 72 | that does not consult this value to allocate its | ||
| 73 | coroutine frame is never wrong. However, a conforming | ||
| 74 | awaitable must still propagate the allocator faithfully | ||
| 75 | so that downstream coroutines can use it. | ||
| 76 | |||
| 77 | @return The thread-local memory_resource pointer, | ||
| 78 | or `nullptr` if none has been set. | ||
| 79 | |||
| 80 | @see set_current_frame_allocator, IoAwaitable | ||
| 81 | */ | ||
| 82 | inline | ||
| 83 | std::pmr::memory_resource* | ||
| 84 | 9703x | get_current_frame_allocator() noexcept | |
| 85 | { | ||
| 86 | 9703x | return detail::current_frame_allocator_ref(); | |
| 87 | } | ||
| 88 | |||
| 89 | /** Set the current frame allocator for this thread. | ||
| 90 | |||
| 91 | Installs @p mr as the frame allocator that will be | ||
| 92 | read by the next coroutine's `promise_type::operator | ||
| 93 | new` on this thread. Only launch functions and | ||
| 94 | @ref IoAwaitable machinery should call this; see | ||
| 95 | @ref get_current_frame_allocator for the full protocol | ||
| 96 | description. | ||
| 97 | |||
| 98 | Passing `nullptr` means "not specified" - no | ||
| 99 | particular allocator is established for the chain. | ||
| 100 | |||
| 101 | @param mr The memory_resource to install, or | ||
| 102 | `nullptr` to clear. | ||
| 103 | |||
| 104 | @see get_current_frame_allocator, IoAwaitable | ||
| 105 | */ | ||
| 106 | inline void | ||
| 107 | 21991x | set_current_frame_allocator( | |
| 108 | std::pmr::memory_resource* mr) noexcept | ||
| 109 | { | ||
| 110 | 21991x | detail::current_frame_allocator_ref() = mr; | |
| 111 | 21991x | } | |
| 112 | |||
| 113 | /** Resume a coroutine handle with frame-allocator TLS protection. | ||
| 114 | |||
| 115 | Saves the current thread-local frame allocator before | ||
| 116 | calling `h.resume()`, then restores it after the call | ||
| 117 | returns. This prevents a resumed coroutine's | ||
| 118 | `await_resume` from permanently overwriting the caller's | ||
| 119 | allocator value. | ||
| 120 | |||
| 121 | Between a coroutine's resumption and its next child | ||
| 122 | invocation, arbitrary user code may run. If that code | ||
| 123 | resumes a coroutine from a different chain on this | ||
| 124 | thread, the other coroutine's `await_resume` overwrites | ||
| 125 | TLS with its own allocator. Without save/restore, the | ||
| 126 | original coroutine's next child would allocate from | ||
| 127 | the wrong resource. | ||
| 128 | |||
| 129 | Event loops, strand dispatch loops, and any code that | ||
| 130 | calls `.resume()` on a coroutine handle should use | ||
| 131 | this function instead of calling `.resume()` directly. | ||
| 132 | See the @ref Executor concept documentation for details. | ||
| 133 | |||
| 134 | @param h The coroutine handle to resume. | ||
| 135 | |||
| 136 | @see get_current_frame_allocator, set_current_frame_allocator | ||
| 137 | */ | ||
| 138 | inline void | ||
| 139 | 1374x | safe_resume(std::coroutine_handle<> h) noexcept | |
| 140 | { | ||
| 141 | 1374x | auto* saved = get_current_frame_allocator(); | |
| 142 | 1374x | h.resume(); | |
| 143 | 1374x | set_current_frame_allocator(saved); | |
| 144 | 1374x | } | |
| 145 | |||
| 146 | } // namespace capy | ||
| 147 | } // namespace boost | ||
| 148 | |||
| 149 | #endif | ||
| 150 |