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