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_alloc_mixin.hpp>
15  
#include <boost/capy/ex/frame_allocator.hpp>
15  
#include <boost/capy/ex/frame_allocator.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 <coroutine>
19  
#include <coroutine>
20  
#include <memory_resource>
20  
#include <memory_resource>
21  
#include <stop_token>
21  
#include <stop_token>
22  
#include <type_traits>
22  
#include <type_traits>
23  

23  

24  
namespace boost {
24  
namespace boost {
25  
namespace capy {
25  
namespace capy {
26  

26  

27  
/** 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.
28  

28  

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

30  

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

33  

34  
    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`
35  
       containing the executor, stop token, and allocator for this coroutine.
35  
       containing the executor, stop token, and allocator for this coroutine.
36  

36  

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

41  

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

43  

44  
    @par Basic Usage
44  
    @par Basic Usage
45  

45  

46  
    For coroutines that need to access their execution environment:
46  
    For coroutines that need to access their execution environment:
47  

47  

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

59  

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

62  

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

67  

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

74  

75  
    @par Custom Awaitable Transformation
75  
    @par Custom Awaitable Transformation
76  

76  

77  
    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
78  
    logging), override `transform_awaitable` instead of `await_transform`:
78  
    logging), override `transform_awaitable` instead of `await_transform`:
79  

79  

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

91  

92  
    The mixin's `await_transform` intercepts @ref this_coro::environment_tag
92  
    The mixin's `await_transform` intercepts @ref this_coro::environment_tag
93  
    and the fine-grained tag types (@ref this_coro::executor_tag,
93  
    and the fine-grained tag types (@ref this_coro::executor_tag,
94  
    @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),
95  
    then delegates all other awaitables to your `transform_awaitable`.
95  
    then delegates all other awaitables to your `transform_awaitable`.
96  

96  

97  
    @par Making Your Coroutine an IoAwaitable
97  
    @par Making Your Coroutine an IoAwaitable
98  

98  

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

103  

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

108  

109  
        std::coroutine_handle<promise_type> h_;
109  
        std::coroutine_handle<promise_type> h_;
110  

110  

111  
        // IoAwaitable await_suspend receives and stores the environment
111  
        // IoAwaitable await_suspend receives and stores the environment
112  
        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)
113  
        {
113  
        {
114  
            h_.promise().set_environment(env);
114  
            h_.promise().set_environment(env);
115  
            // ... rest of suspend logic ...
115  
            // ... rest of suspend logic ...
116  
        }
116  
        }
117  
    };
117  
    };
118  
    @endcode
118  
    @endcode
119  

119  

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

124  

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

136  

137  
public:
137  
public:
138  
    ~io_awaitable_promise_base()
138  
    ~io_awaitable_promise_base()
139  
    {
139  
    {
140  
        // Abnormal teardown: destroy orphaned continuation
140  
        // Abnormal teardown: destroy orphaned continuation
141  
        if(cont_ != std::noop_coroutine())
141  
        if(cont_ != std::noop_coroutine())
142  
            cont_.destroy();
142  
            cont_.destroy();
143  
    }
143  
    }
144  

144  

145  
    //----------------------------------------------------------
145  
    //----------------------------------------------------------
146  
    // Continuation support
146  
    // Continuation support
147  
    //----------------------------------------------------------
147  
    //----------------------------------------------------------
148  

148  

149  
    /** Store the continuation to resume on completion.
149  
    /** Store the continuation to resume on completion.
150  

150  

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

154  

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

161  

162  
    /** Return and consume the stored continuation handle.
162  
    /** Return and consume the stored continuation handle.
163  

163  

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

166  

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

173  

174  
    //----------------------------------------------------------
174  
    //----------------------------------------------------------
175  
    // Environment support
175  
    // Environment support
176  
    //----------------------------------------------------------
176  
    //----------------------------------------------------------
177  

177  

178  
    /** Store a pointer to the execution environment.
178  
    /** Store a pointer to the execution environment.
179  

179  

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

184  

185  
        @param env The environment to store.
185  
        @param env The environment to store.
186  
    */
186  
    */
187  
    void set_environment(io_env const* env) noexcept
187  
    void set_environment(io_env const* env) noexcept
188  
    {
188  
    {
189  
        env_ = env;
189  
        env_ = env;
190  
    }
190  
    }
191  

191  

192  
    /** Return the stored execution environment.
192  
    /** Return the stored execution environment.
193  

193  

194  
        @return The environment.
194  
        @return The environment.
195  
    */
195  
    */
196  
    io_env const* environment() const noexcept
196  
    io_env const* environment() const noexcept
197  
    {
197  
    {
198  
        BOOST_CAPY_ASSERT(env_);
198  
        BOOST_CAPY_ASSERT(env_);
199  
        return env_;
199  
        return env_;
200  
    }
200  
    }
201  

201  

202  
    /** Transform an awaitable before co_await.
202  
    /** Transform an awaitable before co_await.
203  

203  

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

207  

208  
        @param a The awaitable expression from `co_await a`.
208  
        @param a The awaitable expression from `co_await a`.
209  

209  

210  
        @return The transformed awaitable.
210  
        @return The transformed awaitable.
211  
    */
211  
    */
212  
    template<typename A>
212  
    template<typename A>
213  
    decltype(auto) transform_awaitable(A&& a)
213  
    decltype(auto) transform_awaitable(A&& a)
214  
    {
214  
    {
215  
        return std::forward<A>(a);
215  
        return std::forward<A>(a);
216  
    }
216  
    }
217  

217  

218  
    /** Intercept co_await expressions.
218  
    /** Intercept co_await expressions.
219  

219  

220  
        This function handles @ref this_coro::environment_tag and
220  
        This function handles @ref this_coro::environment_tag and
221  
        the fine-grained tags (@ref this_coro::executor_tag,
221  
        the fine-grained tags (@ref this_coro::executor_tag,
222  
        @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)
223  
        specially, returning an awaiter that yields the stored value.
223  
        specially, returning an awaiter that yields the stored value.
224  
        All other awaitables are delegated to @ref transform_awaitable.
224  
        All other awaitables are delegated to @ref transform_awaitable.
225  

225  

226  
        @param t The awaited expression.
226  
        @param t The awaited expression.
227  

227  

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

234  

235  
        if constexpr (std::is_same_v<Tag, this_coro::environment_tag>)
235  
        if constexpr (std::is_same_v<Tag, this_coro::environment_tag>)
236  
        {
236  
        {
237  
            BOOST_CAPY_ASSERT(env_);
237  
            BOOST_CAPY_ASSERT(env_);
238  
            struct awaiter
238  
            struct awaiter
239  
            {
239  
            {
240  
                io_env const* env_;
240  
                io_env const* env_;
241  
                bool await_ready() const noexcept { return true; }
241  
                bool await_ready() const noexcept { return true; }
242  
                void await_suspend(std::coroutine_handle<>) const noexcept { }
242  
                void await_suspend(std::coroutine_handle<>) const noexcept { }
243  
                io_env const* await_resume() const noexcept { return env_; }
243  
                io_env const* await_resume() const noexcept { return env_; }
244  
            };
244  
            };
245  
            return awaiter{env_};
245  
            return awaiter{env_};
246  
        }
246  
        }
247  
        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>)
248  
        {
248  
        {
249  
            BOOST_CAPY_ASSERT(env_);
249  
            BOOST_CAPY_ASSERT(env_);
250  
            struct awaiter
250  
            struct awaiter
251  
            {
251  
            {
252  
                executor_ref executor_;
252  
                executor_ref executor_;
253  
                bool await_ready() const noexcept { return true; }
253  
                bool await_ready() const noexcept { return true; }
254  
                void await_suspend(std::coroutine_handle<>) const noexcept { }
254  
                void await_suspend(std::coroutine_handle<>) const noexcept { }
255  
                executor_ref await_resume() const noexcept { return executor_; }
255  
                executor_ref await_resume() const noexcept { return executor_; }
256  
            };
256  
            };
257  
            return awaiter{env_->executor};
257  
            return awaiter{env_->executor};
258  
        }
258  
        }
259  
        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>)
260  
        {
260  
        {
261  
            BOOST_CAPY_ASSERT(env_);
261  
            BOOST_CAPY_ASSERT(env_);
262  
            struct awaiter
262  
            struct awaiter
263  
            {
263  
            {
264  
                std::stop_token token_;
264  
                std::stop_token token_;
265  
                bool await_ready() const noexcept { return true; }
265  
                bool await_ready() const noexcept { return true; }
266  
                void await_suspend(std::coroutine_handle<>) const noexcept { }
266  
                void await_suspend(std::coroutine_handle<>) const noexcept { }
267  
                std::stop_token await_resume() const noexcept { return token_; }
267  
                std::stop_token await_resume() const noexcept { return token_; }
268  
            };
268  
            };
269  
            return awaiter{env_->stop_token};
269  
            return awaiter{env_->stop_token};
270  
        }
270  
        }
271  
        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>)
272  
        {
272  
        {
273  
            BOOST_CAPY_ASSERT(env_);
273  
            BOOST_CAPY_ASSERT(env_);
274  
            struct awaiter
274  
            struct awaiter
275  
            {
275  
            {
276  
                std::pmr::memory_resource* frame_allocator_;
276  
                std::pmr::memory_resource* frame_allocator_;
277  
                bool await_ready() const noexcept { return true; }
277  
                bool await_ready() const noexcept { return true; }
278  
                void await_suspend(std::coroutine_handle<>) const noexcept { }
278  
                void await_suspend(std::coroutine_handle<>) const noexcept { }
279  
                std::pmr::memory_resource* await_resume() const noexcept { return frame_allocator_; }
279  
                std::pmr::memory_resource* await_resume() const noexcept { return frame_allocator_; }
280  
            };
280  
            };
281  
            return awaiter{env_->frame_allocator};
281  
            return awaiter{env_->frame_allocator};
282  
        }
282  
        }
283  
        else
283  
        else
284  
        {
284  
        {
285  
            return static_cast<Derived*>(this)->transform_awaitable(
285  
            return static_cast<Derived*>(this)->transform_awaitable(
286  
                std::forward<T>(t));
286  
                std::forward<T>(t));
287  
        }
287  
        }
288  
    }
288  
    }
289  
};
289  
};
290  

290  

291  
} // namespace capy
291  
} // namespace capy
292  
} // namespace boost
292  
} // namespace boost
293  

293  

294  
#endif
294  
#endif