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_EX_IO_AWAITABLE_PROMISE_BASE_HPP
10  
#ifndef BOOST_CAPY_EX_IO_AWAITABLE_PROMISE_BASE_HPP
11  
#define BOOST_CAPY_EX_IO_AWAITABLE_PROMISE_BASE_HPP
11  
#define BOOST_CAPY_EX_IO_AWAITABLE_PROMISE_BASE_HPP
12  

12  

13  
#include <boost/capy/detail/config.hpp>
13  
#include <boost/capy/detail/config.hpp>
 
14 +
#include <boost/capy/ex/frame_alloc_mixin.hpp>
14  
#include <boost/capy/ex/frame_allocator.hpp>
15  
#include <boost/capy/ex/frame_allocator.hpp>
15 -
#include <boost/capy/ex/recycling_memory_resource.hpp>
 
16  
#include <boost/capy/ex/io_env.hpp>
16  
#include <boost/capy/ex/io_env.hpp>
17  
#include <boost/capy/ex/this_coro.hpp>
17  
#include <boost/capy/ex/this_coro.hpp>
18  

18  

19 -
#include <cstddef>
 
20 -
#include <cstring>
 
21  
#include <coroutine>
19  
#include <coroutine>
22  
#include <memory_resource>
20  
#include <memory_resource>
23  
#include <stop_token>
21  
#include <stop_token>
24  
#include <type_traits>
22  
#include <type_traits>
25  

23  

26  
namespace boost {
24  
namespace boost {
27  
namespace capy {
25  
namespace capy {
28  

26  

29  
/** CRTP mixin that adds I/O awaitable support to a promise type.
27  
/** CRTP mixin that adds I/O awaitable support to a promise type.
30  

28  

31  
    Inherit from this class to enable these capabilities in your coroutine:
29  
    Inherit from this class to enable these capabilities in your coroutine:
32  

30  

33  
    1. **Frame allocation** — The mixin provides `operator new/delete` that
31  
    1. **Frame allocation** — The mixin provides `operator new/delete` that
34  
       use the thread-local frame allocator set by `run_async`.
32  
       use the thread-local frame allocator set by `run_async`.
35  

33  

36  
    2. **Environment storage** — The mixin stores a pointer to the `io_env`
34  
    2. **Environment storage** — The mixin stores a pointer to the `io_env`
37  
       containing the executor, stop token, and allocator for this coroutine.
35  
       containing the executor, stop token, and allocator for this coroutine.
38  

36  

39  
    3. **Environment access** — Coroutine code can retrieve the environment
37  
    3. **Environment access** — Coroutine code can retrieve the environment
40  
       via `co_await this_coro::environment`, or individual fields via
38  
       via `co_await this_coro::environment`, or individual fields via
41  
       `co_await this_coro::executor`, `co_await this_coro::stop_token`,
39  
       `co_await this_coro::executor`, `co_await this_coro::stop_token`,
42  
       and `co_await this_coro::frame_allocator`.
40  
       and `co_await this_coro::frame_allocator`.
43  

41  

44  
    @tparam Derived The derived promise type (CRTP pattern).
42  
    @tparam Derived The derived promise type (CRTP pattern).
45  

43  

46  
    @par Basic Usage
44  
    @par Basic Usage
47  

45  

48  
    For coroutines that need to access their execution environment:
46  
    For coroutines that need to access their execution environment:
49  

47  

50  
    @code
48  
    @code
51  
    struct my_task
49  
    struct my_task
52  
    {
50  
    {
53  
        struct promise_type : io_awaitable_promise_base<promise_type>
51  
        struct promise_type : io_awaitable_promise_base<promise_type>
54  
        {
52  
        {
55  
            my_task get_return_object();
53  
            my_task get_return_object();
56  
            std::suspend_always initial_suspend() noexcept;
54  
            std::suspend_always initial_suspend() noexcept;
57  
            std::suspend_always final_suspend() noexcept;
55  
            std::suspend_always final_suspend() noexcept;
58  
            void return_void();
56  
            void return_void();
59  
            void unhandled_exception();
57  
            void unhandled_exception();
60  
        };
58  
        };
61  

59  

62  
        // ... awaitable interface ...
60  
        // ... awaitable interface ...
63  
    };
61  
    };
64  

62  

65  
    my_task example()
63  
    my_task example()
66  
    {
64  
    {
67  
        auto env = co_await this_coro::environment;
65  
        auto env = co_await this_coro::environment;
68  
        // Access env->executor, env->stop_token, env->frame_allocator
66  
        // Access env->executor, env->stop_token, env->frame_allocator
69  

67  

70  
        // Or use fine-grained accessors:
68  
        // Or use fine-grained accessors:
71  
        auto ex = co_await this_coro::executor;
69  
        auto ex = co_await this_coro::executor;
72  
        auto token = co_await this_coro::stop_token;
70  
        auto token = co_await this_coro::stop_token;
73  
        auto* alloc = co_await this_coro::frame_allocator;
71  
        auto* alloc = co_await this_coro::frame_allocator;
74  
    }
72  
    }
75  
    @endcode
73  
    @endcode
76  

74  

77  
    @par Custom Awaitable Transformation
75  
    @par Custom Awaitable Transformation
78  

76  

79  
    If your promise needs to transform awaitables (e.g., for affinity or
77  
    If your promise needs to transform awaitables (e.g., for affinity or
80  
    logging), override `transform_awaitable` instead of `await_transform`:
78  
    logging), override `transform_awaitable` instead of `await_transform`:
81  

79  

82  
    @code
80  
    @code
83  
    struct promise_type : io_awaitable_promise_base<promise_type>
81  
    struct promise_type : io_awaitable_promise_base<promise_type>
84  
    {
82  
    {
85  
        template<typename A>
83  
        template<typename A>
86  
        auto transform_awaitable(A&& a)
84  
        auto transform_awaitable(A&& a)
87  
        {
85  
        {
88  
            // Your custom transformation logic
86  
            // Your custom transformation logic
89  
            return std::forward<A>(a);
87  
            return std::forward<A>(a);
90  
        }
88  
        }
91  
    };
89  
    };
92  
    @endcode
90  
    @endcode
93  

91  

94  
    The mixin's `await_transform` intercepts @ref this_coro::environment_tag
92  
    The mixin's `await_transform` intercepts @ref this_coro::environment_tag
95  
    and the fine-grained tag types (@ref this_coro::executor_tag,
93  
    and the fine-grained tag types (@ref this_coro::executor_tag,
96  
    @ref this_coro::stop_token_tag, @ref this_coro::frame_allocator_tag),
94  
    @ref this_coro::stop_token_tag, @ref this_coro::frame_allocator_tag),
97  
    then delegates all other awaitables to your `transform_awaitable`.
95  
    then delegates all other awaitables to your `transform_awaitable`.
98  

96  

99  
    @par Making Your Coroutine an IoAwaitable
97  
    @par Making Your Coroutine an IoAwaitable
100  

98  

101  
    The mixin handles the "inside the coroutine" part—accessing the
99  
    The mixin handles the "inside the coroutine" part—accessing the
102  
    environment. To receive the environment when your coroutine is awaited
100  
    environment. To receive the environment when your coroutine is awaited
103  
    (satisfying @ref IoAwaitable), implement the `await_suspend` overload
101  
    (satisfying @ref IoAwaitable), implement the `await_suspend` overload
104  
    on your coroutine return type:
102  
    on your coroutine return type:
105  

103  

106  
    @code
104  
    @code
107  
    struct my_task
105  
    struct my_task
108  
    {
106  
    {
109  
        struct promise_type : io_awaitable_promise_base<promise_type> { ... };
107  
        struct promise_type : io_awaitable_promise_base<promise_type> { ... };
110  

108  

111  
        std::coroutine_handle<promise_type> h_;
109  
        std::coroutine_handle<promise_type> h_;
112  

110  

113  
        // IoAwaitable await_suspend receives and stores the environment
111  
        // IoAwaitable await_suspend receives and stores the environment
114  
        std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* env)
112  
        std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* env)
115  
        {
113  
        {
116  
            h_.promise().set_environment(env);
114  
            h_.promise().set_environment(env);
117  
            // ... rest of suspend logic ...
115  
            // ... rest of suspend logic ...
118  
        }
116  
        }
119  
    };
117  
    };
120  
    @endcode
118  
    @endcode
121  

119  

122  
    @par Thread Safety
120  
    @par Thread Safety
123  
    The environment is stored during `await_suspend` and read during
121  
    The environment is stored during `await_suspend` and read during
124  
    `co_await this_coro::environment`. These occur on the same logical
122  
    `co_await this_coro::environment`. These occur on the same logical
125  
    thread of execution, so no synchronization is required.
123  
    thread of execution, so no synchronization is required.
126  

124  

127  
    @see this_coro::environment, this_coro::executor,
125  
    @see this_coro::environment, this_coro::executor,
128  
         this_coro::stop_token, this_coro::frame_allocator
126  
         this_coro::stop_token, this_coro::frame_allocator
129  
    @see io_env
127  
    @see io_env
130  
    @see IoAwaitable
128  
    @see IoAwaitable
131  
*/
129  
*/
132  
template<typename Derived>
130  
template<typename Derived>
133  
class io_awaitable_promise_base
131  
class io_awaitable_promise_base
 
132 +
    : public frame_alloc_mixin
134  
{
133  
{
135  
    io_env const* env_ = nullptr;
134  
    io_env const* env_ = nullptr;
136  
    mutable std::coroutine_handle<> cont_{std::noop_coroutine()};
135  
    mutable std::coroutine_handle<> cont_{std::noop_coroutine()};
137  

136  

138 -
    /** Allocate a coroutine frame.
 
139 -

 
140 -
        Uses the thread-local frame allocator set by run_async.
 
141 -
        Falls back to default memory resource if not set.
 
142 -
        Stores the allocator pointer at the end of each frame for
 
143 -
        correct deallocation even when TLS changes. Uses memcpy
 
144 -
        to avoid alignment requirements on the trailing pointer.
 
145 -
        Bypasses virtual dispatch for the recycling allocator.
 
146 -
    */
 
147 -
    static void* operator new(std::size_t size)
 
148 -
    {
 
149 -
        static auto* const rmr = get_recycling_memory_resource();
 
150 -

 
151 -
        auto* mr = get_current_frame_allocator();
 
152 -
        if(!mr)
 
153 -
            mr = std::pmr::get_default_resource();
 
154 -

 
155 -
        auto total = size + sizeof(std::pmr::memory_resource*);
 
156 -
        void* raw;
 
157 -
        if(mr == rmr)
 
158 -
            raw = static_cast<recycling_memory_resource*>(mr)
 
159 -
                ->allocate_fast(total, alignof(std::max_align_t));
 
160 -
        else
 
161 -
            raw = mr->allocate(total, alignof(std::max_align_t));
 
162 -
        std::memcpy(static_cast<char*>(raw) + size, &mr, sizeof(mr));
 
163 -
        return raw;
 
164 -
    }
 
165 -

 
166 -
    /** Deallocate a coroutine frame.
 
167 -

 
168 -
        Reads the allocator pointer stored at the end of the frame
 
169 -
        to ensure correct deallocation regardless of current TLS.
 
170 -
        Bypasses virtual dispatch for the recycling allocator.
 
171 -
    */
 
172 -
    static void operator delete(void* ptr, std::size_t size) noexcept
 
173 -
    {
 
174 -
        static auto* const rmr = get_recycling_memory_resource();
 
175 -

 
176 -
        std::pmr::memory_resource* mr;
 
177 -
        std::memcpy(&mr, static_cast<char*>(ptr) + size, sizeof(mr));
 
178 -
        auto total = size + sizeof(std::pmr::memory_resource*);
 
179 -
        if(mr == rmr)
 
180 -
            static_cast<recycling_memory_resource*>(mr)
 
181 -
                ->deallocate_fast(ptr, total, alignof(std::max_align_t));
 
182 -
        else
 
183 -
            mr->deallocate(ptr, total, alignof(std::max_align_t));
 
184 -
    }
 
185 -

 
186  
public:
137  
public:
187  
    ~io_awaitable_promise_base()
138  
    ~io_awaitable_promise_base()
188  
    {
139  
    {
189  
        // Abnormal teardown: destroy orphaned continuation
140  
        // Abnormal teardown: destroy orphaned continuation
190  
        if(cont_ != std::noop_coroutine())
141  
        if(cont_ != std::noop_coroutine())
191  
            cont_.destroy();
142  
            cont_.destroy();
192  
    }
143  
    }
193  

144  

194  
    //----------------------------------------------------------
145  
    //----------------------------------------------------------
195  
    // Continuation support
146  
    // Continuation support
196  
    //----------------------------------------------------------
147  
    //----------------------------------------------------------
197  

148  

198  
    /** Store the continuation to resume on completion.
149  
    /** Store the continuation to resume on completion.
199  

150  

200  
        Call this from your coroutine type's `await_suspend` overload
151  
        Call this from your coroutine type's `await_suspend` overload
201  
        to set up the completion path. The `final_suspend` awaiter
152  
        to set up the completion path. The `final_suspend` awaiter
202  
        returns this handle via unconditional symmetric transfer.
153  
        returns this handle via unconditional symmetric transfer.
203  

154  

204  
        @param cont The continuation to resume on completion.
155  
        @param cont The continuation to resume on completion.
205  
    */
156  
    */
206  
    void set_continuation(std::coroutine_handle<> cont) noexcept
157  
    void set_continuation(std::coroutine_handle<> cont) noexcept
207  
    {
158  
    {
208  
        cont_ = cont;
159  
        cont_ = cont;
209  
    }
160  
    }
210  

161  

211  
    /** Return and consume the stored continuation handle.
162  
    /** Return and consume the stored continuation handle.
212  

163  

213  
        Resets the stored handle to `noop_coroutine()` so the
164  
        Resets the stored handle to `noop_coroutine()` so the
214  
        destructor will not double-destroy it.
165  
        destructor will not double-destroy it.
215  

166  

216  
        @return The continuation for symmetric transfer.
167  
        @return The continuation for symmetric transfer.
217  
    */
168  
    */
218  
    std::coroutine_handle<> continuation() const noexcept
169  
    std::coroutine_handle<> continuation() const noexcept
219  
    {
170  
    {
220  
        return std::exchange(cont_, std::noop_coroutine());
171  
        return std::exchange(cont_, std::noop_coroutine());
221  
    }
172  
    }
222  

173  

223  
    //----------------------------------------------------------
174  
    //----------------------------------------------------------
224  
    // Environment support
175  
    // Environment support
225  
    //----------------------------------------------------------
176  
    //----------------------------------------------------------
226  

177  

227  
    /** Store a pointer to the execution environment.
178  
    /** Store a pointer to the execution environment.
228  

179  

229  
        Call this from your coroutine type's `await_suspend`
180  
        Call this from your coroutine type's `await_suspend`
230  
        overload to make the environment available via
181  
        overload to make the environment available via
231  
        `co_await this_coro::environment`. The pointed-to
182  
        `co_await this_coro::environment`. The pointed-to
232  
        `io_env` must outlive this coroutine.
183  
        `io_env` must outlive this coroutine.
233  

184  

234  
        @param env The environment to store.
185  
        @param env The environment to store.
235  
    */
186  
    */
236  
    void set_environment(io_env const* env) noexcept
187  
    void set_environment(io_env const* env) noexcept
237  
    {
188  
    {
238  
        env_ = env;
189  
        env_ = env;
239  
    }
190  
    }
240  

191  

241  
    /** Return the stored execution environment.
192  
    /** Return the stored execution environment.
242  

193  

243  
        @return The environment.
194  
        @return The environment.
244  
    */
195  
    */
245  
    io_env const* environment() const noexcept
196  
    io_env const* environment() const noexcept
246  
    {
197  
    {
247  
        BOOST_CAPY_ASSERT(env_);
198  
        BOOST_CAPY_ASSERT(env_);
248  
        return env_;
199  
        return env_;
249  
    }
200  
    }
250  

201  

251  
    /** Transform an awaitable before co_await.
202  
    /** Transform an awaitable before co_await.
252  

203  

253  
        Override this in your derived promise type to customize how
204  
        Override this in your derived promise type to customize how
254  
        awaitables are transformed. The default implementation passes
205  
        awaitables are transformed. The default implementation passes
255  
        the awaitable through unchanged.
206  
        the awaitable through unchanged.
256  

207  

257  
        @param a The awaitable expression from `co_await a`.
208  
        @param a The awaitable expression from `co_await a`.
258  

209  

259  
        @return The transformed awaitable.
210  
        @return The transformed awaitable.
260  
    */
211  
    */
261  
    template<typename A>
212  
    template<typename A>
262  
    decltype(auto) transform_awaitable(A&& a)
213  
    decltype(auto) transform_awaitable(A&& a)
263  
    {
214  
    {
264  
        return std::forward<A>(a);
215  
        return std::forward<A>(a);
265  
    }
216  
    }
266  

217  

267  
    /** Intercept co_await expressions.
218  
    /** Intercept co_await expressions.
268  

219  

269  
        This function handles @ref this_coro::environment_tag and
220  
        This function handles @ref this_coro::environment_tag and
270  
        the fine-grained tags (@ref this_coro::executor_tag,
221  
        the fine-grained tags (@ref this_coro::executor_tag,
271  
        @ref this_coro::stop_token_tag, @ref this_coro::frame_allocator_tag)
222  
        @ref this_coro::stop_token_tag, @ref this_coro::frame_allocator_tag)
272  
        specially, returning an awaiter that yields the stored value.
223  
        specially, returning an awaiter that yields the stored value.
273  
        All other awaitables are delegated to @ref transform_awaitable.
224  
        All other awaitables are delegated to @ref transform_awaitable.
274  

225  

275  
        @param t The awaited expression.
226  
        @param t The awaited expression.
276  

227  

277  
        @return An awaiter for the expression.
228  
        @return An awaiter for the expression.
278  
    */
229  
    */
279  
    template<typename T>
230  
    template<typename T>
280  
    auto await_transform(T&& t)
231  
    auto await_transform(T&& t)
281  
    {
232  
    {
282  
        using Tag = std::decay_t<T>;
233  
        using Tag = std::decay_t<T>;
283  

234  

284  
        if constexpr (std::is_same_v<Tag, this_coro::environment_tag>)
235  
        if constexpr (std::is_same_v<Tag, this_coro::environment_tag>)
285  
        {
236  
        {
286  
            BOOST_CAPY_ASSERT(env_);
237  
            BOOST_CAPY_ASSERT(env_);
287  
            struct awaiter
238  
            struct awaiter
288  
            {
239  
            {
289  
                io_env const* env_;
240  
                io_env const* env_;
290  
                bool await_ready() const noexcept { return true; }
241  
                bool await_ready() const noexcept { return true; }
291  
                void await_suspend(std::coroutine_handle<>) const noexcept { }
242  
                void await_suspend(std::coroutine_handle<>) const noexcept { }
292  
                io_env const* await_resume() const noexcept { return env_; }
243  
                io_env const* await_resume() const noexcept { return env_; }
293  
            };
244  
            };
294  
            return awaiter{env_};
245  
            return awaiter{env_};
295  
        }
246  
        }
296  
        else if constexpr (std::is_same_v<Tag, this_coro::executor_tag>)
247  
        else if constexpr (std::is_same_v<Tag, this_coro::executor_tag>)
297  
        {
248  
        {
298  
            BOOST_CAPY_ASSERT(env_);
249  
            BOOST_CAPY_ASSERT(env_);
299  
            struct awaiter
250  
            struct awaiter
300  
            {
251  
            {
301  
                executor_ref executor_;
252  
                executor_ref executor_;
302  
                bool await_ready() const noexcept { return true; }
253  
                bool await_ready() const noexcept { return true; }
303  
                void await_suspend(std::coroutine_handle<>) const noexcept { }
254  
                void await_suspend(std::coroutine_handle<>) const noexcept { }
304  
                executor_ref await_resume() const noexcept { return executor_; }
255  
                executor_ref await_resume() const noexcept { return executor_; }
305  
            };
256  
            };
306  
            return awaiter{env_->executor};
257  
            return awaiter{env_->executor};
307  
        }
258  
        }
308  
        else if constexpr (std::is_same_v<Tag, this_coro::stop_token_tag>)
259  
        else if constexpr (std::is_same_v<Tag, this_coro::stop_token_tag>)
309  
        {
260  
        {
310  
            BOOST_CAPY_ASSERT(env_);
261  
            BOOST_CAPY_ASSERT(env_);
311  
            struct awaiter
262  
            struct awaiter
312  
            {
263  
            {
313  
                std::stop_token token_;
264  
                std::stop_token token_;
314  
                bool await_ready() const noexcept { return true; }
265  
                bool await_ready() const noexcept { return true; }
315  
                void await_suspend(std::coroutine_handle<>) const noexcept { }
266  
                void await_suspend(std::coroutine_handle<>) const noexcept { }
316  
                std::stop_token await_resume() const noexcept { return token_; }
267  
                std::stop_token await_resume() const noexcept { return token_; }
317  
            };
268  
            };
318  
            return awaiter{env_->stop_token};
269  
            return awaiter{env_->stop_token};
319  
        }
270  
        }
320  
        else if constexpr (std::is_same_v<Tag, this_coro::frame_allocator_tag>)
271  
        else if constexpr (std::is_same_v<Tag, this_coro::frame_allocator_tag>)
321  
        {
272  
        {
322  
            BOOST_CAPY_ASSERT(env_);
273  
            BOOST_CAPY_ASSERT(env_);
323  
            struct awaiter
274  
            struct awaiter
324  
            {
275  
            {
325  
                std::pmr::memory_resource* frame_allocator_;
276  
                std::pmr::memory_resource* frame_allocator_;
326  
                bool await_ready() const noexcept { return true; }
277  
                bool await_ready() const noexcept { return true; }
327  
                void await_suspend(std::coroutine_handle<>) const noexcept { }
278  
                void await_suspend(std::coroutine_handle<>) const noexcept { }
328  
                std::pmr::memory_resource* await_resume() const noexcept { return frame_allocator_; }
279  
                std::pmr::memory_resource* await_resume() const noexcept { return frame_allocator_; }
329  
            };
280  
            };
330  
            return awaiter{env_->frame_allocator};
281  
            return awaiter{env_->frame_allocator};
331  
        }
282  
        }
332  
        else
283  
        else
333  
        {
284  
        {
334  
            return static_cast<Derived*>(this)->transform_awaitable(
285  
            return static_cast<Derived*>(this)->transform_awaitable(
335  
                std::forward<T>(t));
286  
                std::forward<T>(t));
336  
        }
287  
        }
337  
    }
288  
    }
338  
};
289  
};
339  

290  

340  
} // namespace capy
291  
} // namespace capy
341  
} // namespace boost
292  
} // namespace boost
342  

293  

343  
#endif
294  
#endif