include/boost/capy/ex/frame_allocator.hpp

100.0% Lines (10 / 10) 100.0% Functions (3 / 3)
frame_allocator.hpp
f(x) 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