include/boost/capy/ex/frame_allocator.hpp

100.0% Lines (12/12) 100.0% List of functions (4/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 //
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