include/boost/capy/ex/frame_allocator.hpp

100.0% Lines (12/0/12) 100.0% List of functions (4/0/4)
frame_allocator.hpp
f(x) Functions (4)
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