TLA Line data 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 HIT 31694 : current_frame_allocator_ref() noexcept
41 : {
42 : static thread_local std::pmr::memory_resource* mr = nullptr;
43 31694 : 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 9703 : get_current_frame_allocator() noexcept
85 : {
86 9703 : 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 21991 : set_current_frame_allocator(
108 : std::pmr::memory_resource* mr) noexcept
109 : {
110 21991 : detail::current_frame_allocator_ref() = mr;
111 21991 : }
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 1374 : safe_resume(std::coroutine_handle<> h) noexcept
140 : {
141 1374 : auto* saved = get_current_frame_allocator();
142 1374 : h.resume();
143 1374 : set_current_frame_allocator(saved);
144 1374 : }
145 :
146 : } // namespace capy
147 : } // namespace boost
148 :
149 : #endif
|