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   // 3   //
4   // Distributed under the Boost Software License, Version 1.0. (See accompanying 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) 5   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6   // 6   //
7   // Official repository: https://github.com/cppalliance/capy 7   // Official repository: https://github.com/cppalliance/capy
8   // 8   //
9   9  
10   #ifndef BOOST_CAPY_FRAME_ALLOCATOR_HPP 10   #ifndef BOOST_CAPY_FRAME_ALLOCATOR_HPP
11   #define BOOST_CAPY_FRAME_ALLOCATOR_HPP 11   #define BOOST_CAPY_FRAME_ALLOCATOR_HPP
12   12  
13   #include <boost/capy/detail/config.hpp> 13   #include <boost/capy/detail/config.hpp>
14   14  
  15 + #include <coroutine>
15   #include <memory_resource> 16   #include <memory_resource>
16   17  
17   /* Design rationale (pdimov): 18   /* Design rationale (pdimov):
18   19  
19   This accessor is a thin wrapper over a thread-local pointer. 20   This accessor is a thin wrapper over a thread-local pointer.
20   It returns exactly what was stored, including nullptr. No 21   It returns exactly what was stored, including nullptr. No
21   dynamic initializer on the thread-local; a dynamic TLS 22   dynamic initializer on the thread-local; a dynamic TLS
22   initializer moves you into a costlier implementation bucket 23   initializer moves you into a costlier implementation bucket
23   on some platforms - avoid it. 24   on some platforms - avoid it.
24   25  
25   Null handling is the caller's responsibility (e.g. in 26   Null handling is the caller's responsibility (e.g. in
26   promise_type::operator new). The accessor must not substitute 27   promise_type::operator new). The accessor must not substitute
27   a default, because there are multiple valid choices 28   a default, because there are multiple valid choices
28   (new_delete_resource, the default pmr resource, etc.). If 29   (new_delete_resource, the default pmr resource, etc.). If
29   the allocator is not set, it reports "not set" and the 30   the allocator is not set, it reports "not set" and the
30   caller interprets that however it wants. 31   caller interprets that however it wants.
31   */ 32   */
32   33  
33   namespace boost { 34   namespace boost {
34   namespace capy { 35   namespace capy {
35   36  
36   namespace detail { 37   namespace detail {
37   38  
38   inline std::pmr::memory_resource*& 39   inline std::pmr::memory_resource*&
HITCBC 39   26734 current_frame_allocator_ref() noexcept 40   130621 current_frame_allocator_ref() noexcept
40   { 41   {
41   static thread_local std::pmr::memory_resource* mr = nullptr; 42   static thread_local std::pmr::memory_resource* mr = nullptr;
HITCBC 42   26734 return mr; 43   130621 return mr;
43   } 44   }
44   45  
45   } // namespace detail 46   } // namespace detail
46   47  
47   /** Return the current frame allocator for this thread. 48   /** Return the current frame allocator for this thread.
48   49  
49   These accessors exist to implement the allocator 50   These accessors exist to implement the allocator
50   propagation portion of the @ref IoAwaitable protocol. 51   propagation portion of the @ref IoAwaitable protocol.
51   Launch functions (`run_async`, `run`) set the 52   Launch functions (`run_async`, `run`) set the
52   thread-local value before invoking a child coroutine; 53   thread-local value before invoking a child coroutine;
53   the child's `promise_type::operator new` reads it to 54   the child's `promise_type::operator new` reads it to
54   allocate the coroutine frame from the correct resource. 55   allocate the coroutine frame from the correct resource.
55   56  
56   The value is only valid during a narrow execution 57   The value is only valid during a narrow execution
57   window. Between a coroutine's resumption 58   window. Between a coroutine's resumption
58   and the next suspension point, the protocol guarantees 59   and the next suspension point, the protocol guarantees
59   that TLS contains the allocator associated with the 60   that TLS contains the allocator associated with the
60   currently running chain. Outside that window the value 61   currently running chain. Outside that window the value
61   is indeterminate. Only code that implements an 62   is indeterminate. Only code that implements an
62   @ref IoAwaitable should call these functions. 63   @ref IoAwaitable should call these functions.
63   64  
64   A return value of `nullptr` means "not specified" - 65   A return value of `nullptr` means "not specified" -
65   no allocator has been established for this chain. 66   no allocator has been established for this chain.
66   The awaitable is free to use whatever allocation 67   The awaitable is free to use whatever allocation
67   strategy makes best sense (e.g. 68   strategy makes best sense (e.g.
68   `std::pmr::new_delete_resource()`). 69   `std::pmr::new_delete_resource()`).
69   70  
70   Use of the frame allocator is optional. An awaitable 71   Use of the frame allocator is optional. An awaitable
71   that does not consult this value to allocate its 72   that does not consult this value to allocate its
72   coroutine frame is never wrong. However, a conforming 73   coroutine frame is never wrong. However, a conforming
73   awaitable must still propagate the allocator faithfully 74   awaitable must still propagate the allocator faithfully
74   so that downstream coroutines can use it. 75   so that downstream coroutines can use it.
75   76  
76   @return The thread-local memory_resource pointer, 77   @return The thread-local memory_resource pointer,
77   or `nullptr` if none has been set. 78   or `nullptr` if none has been set.
78   79  
79   @see set_current_frame_allocator, IoAwaitable 80   @see set_current_frame_allocator, IoAwaitable
80   */ 81   */
81   inline 82   inline
82   std::pmr::memory_resource* 83   std::pmr::memory_resource*
HITCBC 83   7623 get_current_frame_allocator() noexcept 84   59263 get_current_frame_allocator() noexcept
84   { 85   {
HITCBC 85   7623 return detail::current_frame_allocator_ref(); 86   59263 return detail::current_frame_allocator_ref();
86   } 87   }
87   88  
88   /** Set the current frame allocator for this thread. 89   /** Set the current frame allocator for this thread.
89   90  
90   Installs @p mr as the frame allocator that will be 91   Installs @p mr as the frame allocator that will be
91   read by the next coroutine's `promise_type::operator 92   read by the next coroutine's `promise_type::operator
92   new` on this thread. Only launch functions and 93   new` on this thread. Only launch functions and
93   @ref IoAwaitable machinery should call this; see 94   @ref IoAwaitable machinery should call this; see
94   @ref get_current_frame_allocator for the full protocol 95   @ref get_current_frame_allocator for the full protocol
95   description. 96   description.
96   97  
97   Passing `nullptr` means "not specified" - no 98   Passing `nullptr` means "not specified" - no
98   particular allocator is established for the chain. 99   particular allocator is established for the chain.
99   100  
100   @param mr The memory_resource to install, or 101   @param mr The memory_resource to install, or
101   `nullptr` to clear. 102   `nullptr` to clear.
102   103  
103   @see get_current_frame_allocator, IoAwaitable 104   @see get_current_frame_allocator, IoAwaitable
104   */ 105   */
105   inline void 106   inline void
HITCBC 106   19111 set_current_frame_allocator( 107   71358 set_current_frame_allocator(
107   std::pmr::memory_resource* mr) noexcept 108   std::pmr::memory_resource* mr) noexcept
108   { 109   {
HITCBC 109   19111 detail::current_frame_allocator_ref() = mr; 110   71358 detail::current_frame_allocator_ref() = mr;
HITGNC   111 + 71358 }
  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
HITGNC   139 + 50634 safe_resume(std::coroutine_handle<> h) noexcept
  140 + {
HITGNC   141 + 50634 auto* saved = get_current_frame_allocator();
HITGNC   142 + 50634 h.resume();
HITGNC   143 + 50634 set_current_frame_allocator(saved);
HITCBC 110   19111 } 144   50634 }
111   145  
112   } // namespace capy 146   } // namespace capy
113   } // namespace boost 147   } // namespace boost
114   148  
115   #endif 149   #endif