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/corosio
7  
// Official repository: https://github.com/cppalliance/corosio
8  
//
8  
//
9  

9  

10  
#ifndef BOOST_CAPY_TASK_HPP
10  
#ifndef BOOST_CAPY_TASK_HPP
11  
#define BOOST_CAPY_TASK_HPP
11  
#define BOOST_CAPY_TASK_HPP
12  

12  

13  
#include <boost/capy/detail/config.hpp>
13  
#include <boost/capy/detail/config.hpp>
14  
#include <boost/capy/concept/executor.hpp>
14  
#include <boost/capy/concept/executor.hpp>
15  
#include <boost/capy/concept/io_awaitable.hpp>
15  
#include <boost/capy/concept/io_awaitable.hpp>
16  
#include <boost/capy/ex/io_awaitable_support.hpp>
16  
#include <boost/capy/ex/io_awaitable_support.hpp>
17  
#include <boost/capy/ex/executor_ref.hpp>
17  
#include <boost/capy/ex/executor_ref.hpp>
18  
#include <boost/capy/ex/frame_allocator.hpp>
18  
#include <boost/capy/ex/frame_allocator.hpp>
19  

19  

20  
#include <exception>
20  
#include <exception>
21  
#include <optional>
21  
#include <optional>
22  
#include <type_traits>
22  
#include <type_traits>
23  
#include <utility>
23  
#include <utility>
24  
#include <variant>
24  
#include <variant>
25  

25  

26  
namespace boost {
26  
namespace boost {
27  
namespace capy {
27  
namespace capy {
28  

28  

29  
namespace detail {
29  
namespace detail {
30  

30  

31  
// Helper base for result storage and return_void/return_value
31  
// Helper base for result storage and return_void/return_value
32  
template<typename T>
32  
template<typename T>
33  
struct task_return_base
33  
struct task_return_base
34  
{
34  
{
35  
    std::optional<T> result_;
35  
    std::optional<T> result_;
36  

36  

37  
    void return_value(T value)
37  
    void return_value(T value)
38  
    {
38  
    {
39  
        result_ = std::move(value);
39  
        result_ = std::move(value);
40  
    }
40  
    }
41  

41  

42  
    T&& result() noexcept
42  
    T&& result() noexcept
43  
    {
43  
    {
44  
        return std::move(*result_);
44  
        return std::move(*result_);
45  
    }
45  
    }
46  
};
46  
};
47  

47  

48  
template<>
48  
template<>
49  
struct task_return_base<void>
49  
struct task_return_base<void>
50  
{
50  
{
51  
    void return_void()
51  
    void return_void()
52  
    {
52  
    {
53  
    }
53  
    }
54  
};
54  
};
55  

55  

56  
} // namespace detail
56  
} // namespace detail
57  

57  

58  
/** Lazy coroutine task satisfying @ref IoLaunchableTask.
58  
/** Lazy coroutine task satisfying @ref IoLaunchableTask.
59  

59  

60  
    Use `task<T>` as the return type for coroutines that perform I/O
60  
    Use `task<T>` as the return type for coroutines that perform I/O
61  
    and return a value of type `T`. The coroutine body does not start
61  
    and return a value of type `T`. The coroutine body does not start
62  
    executing until the task is awaited, enabling efficient composition
62  
    executing until the task is awaited, enabling efficient composition
63  
    without unnecessary eager execution.
63  
    without unnecessary eager execution.
64  

64  

65  
    The task participates in the I/O awaitable protocol: when awaited,
65  
    The task participates in the I/O awaitable protocol: when awaited,
66  
    it receives the caller's executor and stop token, propagating them
66  
    it receives the caller's executor and stop token, propagating them
67  
    to nested `co_await` expressions. This enables cancellation and
67  
    to nested `co_await` expressions. This enables cancellation and
68  
    proper completion dispatch across executor boundaries.
68  
    proper completion dispatch across executor boundaries.
69  

69  

70  
    @tparam T The result type. Use `task<>` for `task<void>`.
70  
    @tparam T The result type. Use `task<>` for `task<void>`.
71  

71  

72  
    @par Thread Safety
72  
    @par Thread Safety
73  
    Distinct objects: Safe.
73  
    Distinct objects: Safe.
74  
    Shared objects: Unsafe.
74  
    Shared objects: Unsafe.
75  

75  

76  
    @par Example
76  
    @par Example
77  

77  

78  
    @code
78  
    @code
79  
    task<int> compute_value()
79  
    task<int> compute_value()
80  
    {
80  
    {
81  
        auto [ec, n] = co_await stream.read_some( buf );
81  
        auto [ec, n] = co_await stream.read_some( buf );
82  
        if( ec )
82  
        if( ec )
83  
            co_return 0;
83  
            co_return 0;
84  
        co_return process( buf, n );
84  
        co_return process( buf, n );
85  
    }
85  
    }
86  

86  

87  
    task<> run_session( tcp_socket sock )
87  
    task<> run_session( tcp_socket sock )
88  
    {
88  
    {
89  
        int result = co_await compute_value();
89  
        int result = co_await compute_value();
90  
        // ...
90  
        // ...
91  
    }
91  
    }
92  
    @endcode
92  
    @endcode
93  

93  

94  
    @see IoLaunchableTask, IoAwaitableTask, run, run_async
94  
    @see IoLaunchableTask, IoAwaitableTask, run, run_async
95  
*/
95  
*/
96  
template<typename T = void>
96  
template<typename T = void>
97  
struct [[nodiscard]] BOOST_CAPY_CORO_AWAIT_ELIDABLE
97  
struct [[nodiscard]] BOOST_CAPY_CORO_AWAIT_ELIDABLE
98  
    task
98  
    task
99  
{
99  
{
100  
    struct promise_type
100  
    struct promise_type
101  
        : io_awaitable_support<promise_type>
101  
        : io_awaitable_support<promise_type>
102  
        , detail::task_return_base<T>
102  
        , detail::task_return_base<T>
103  
    {
103  
    {
104  
    private:
104  
    private:
105  
        friend task;
105  
        friend task;
106  
        union { std::exception_ptr ep_; };
106  
        union { std::exception_ptr ep_; };
107  
        bool has_ep_;
107  
        bool has_ep_;
108  

108  

109  
    public:
109  
    public:
110  
        promise_type() noexcept
110  
        promise_type() noexcept
111  
            : has_ep_(false)
111  
            : has_ep_(false)
112  
        {
112  
        {
113  
        }
113  
        }
114  

114  

115  
        ~promise_type()
115  
        ~promise_type()
116  
        {
116  
        {
117  
            if(has_ep_)
117  
            if(has_ep_)
118  
                ep_.~exception_ptr();
118  
                ep_.~exception_ptr();
119  
        }
119  
        }
120  

120  

121  
        std::exception_ptr exception() const noexcept
121  
        std::exception_ptr exception() const noexcept
122  
        {
122  
        {
123  
            if(has_ep_)
123  
            if(has_ep_)
124  
                return ep_;
124  
                return ep_;
125  
            return {};
125  
            return {};
126  
        }
126  
        }
127  

127  

128  
        task get_return_object()
128  
        task get_return_object()
129  
        {
129  
        {
130  
            return task{std::coroutine_handle<promise_type>::from_promise(*this)};
130  
            return task{std::coroutine_handle<promise_type>::from_promise(*this)};
131  
        }
131  
        }
132  

132  

133  
        auto initial_suspend() noexcept
133  
        auto initial_suspend() noexcept
134  
        {
134  
        {
135  
            struct awaiter
135  
            struct awaiter
136  
            {
136  
            {
137  
                promise_type* p_;
137  
                promise_type* p_;
138  

138  

139  
                bool await_ready() const noexcept
139  
                bool await_ready() const noexcept
140  
                {
140  
                {
141  
                    return false;
141  
                    return false;
142  
                }
142  
                }
143  

143  

144  
                void await_suspend(coro) const noexcept
144  
                void await_suspend(coro) const noexcept
145  
                {
145  
                {
146  
                    // Capture TLS allocator while it's still valid
146  
                    // Capture TLS allocator while it's still valid
147  
                    p_->set_frame_allocator(current_frame_allocator());
147  
                    p_->set_frame_allocator(current_frame_allocator());
148  
                }
148  
                }
149  

149  

150  
                void await_resume() const noexcept
150  
                void await_resume() const noexcept
151  
                {
151  
                {
152  
                    // Restore TLS when body starts executing
152  
                    // Restore TLS when body starts executing
153  
                    auto* fa = p_->frame_allocator();
153  
                    auto* fa = p_->frame_allocator();
154  
                    if(fa && fa != current_frame_allocator())
154  
                    if(fa && fa != current_frame_allocator())
155  
                        current_frame_allocator() = fa;
155  
                        current_frame_allocator() = fa;
156  
                }
156  
                }
157  
            };
157  
            };
158  
            return awaiter{this};
158  
            return awaiter{this};
159  
        }
159  
        }
160  

160  

161  
        auto final_suspend() noexcept
161  
        auto final_suspend() noexcept
162  
        {
162  
        {
163  
            struct awaiter
163  
            struct awaiter
164  
            {
164  
            {
165  
                promise_type* p_;
165  
                promise_type* p_;
166  

166  

167  
                bool await_ready() const noexcept
167  
                bool await_ready() const noexcept
168  
                {
168  
                {
169  
                    return false;
169  
                    return false;
170  
                }
170  
                }
171  

171  

172  
                coro await_suspend(coro) const noexcept
172  
                coro await_suspend(coro) const noexcept
173  
                {
173  
                {
174  
                    return p_->complete();
174  
                    return p_->complete();
175  
                }
175  
                }
176  

176  

177  
                void await_resume() const noexcept
177  
                void await_resume() const noexcept
178  
                {
178  
                {
179  
                }
179  
                }
180  
            };
180  
            };
181  
            return awaiter{this};
181  
            return awaiter{this};
182  
        }
182  
        }
183  

183  

184  
        void unhandled_exception()
184  
        void unhandled_exception()
185  
        {
185  
        {
186  
            new (&ep_) std::exception_ptr(std::current_exception());
186  
            new (&ep_) std::exception_ptr(std::current_exception());
187  
            has_ep_ = true;
187  
            has_ep_ = true;
188  
        }
188  
        }
189  

189  

190  
        template<class Awaitable>
190  
        template<class Awaitable>
191  
        struct transform_awaiter
191  
        struct transform_awaiter
192  
        {
192  
        {
193  
            std::decay_t<Awaitable> a_;
193  
            std::decay_t<Awaitable> a_;
194  
            promise_type* p_;
194  
            promise_type* p_;
195  

195  

196  
            bool await_ready() noexcept
196  
            bool await_ready() noexcept
197  
            {
197  
            {
198  
                return a_.await_ready();
198  
                return a_.await_ready();
199  
            }
199  
            }
200  

200  

201  
            decltype(auto) await_resume()
201  
            decltype(auto) await_resume()
202  
            {
202  
            {
203  
                // Restore TLS before body resumes
203  
                // Restore TLS before body resumes
204  
                auto* fa = p_->frame_allocator();
204  
                auto* fa = p_->frame_allocator();
205  
                if(fa && fa != current_frame_allocator())
205  
                if(fa && fa != current_frame_allocator())
206  
                    current_frame_allocator() = fa;
206  
                    current_frame_allocator() = fa;
207  
                return a_.await_resume();
207  
                return a_.await_resume();
208  
            }
208  
            }
209  

209  

210  
            template<class Promise>
210  
            template<class Promise>
211  
            auto await_suspend(std::coroutine_handle<Promise> h) noexcept
211  
            auto await_suspend(std::coroutine_handle<Promise> h) noexcept
212  
            {
212  
            {
213  
                return a_.await_suspend(h, p_->executor(), p_->stop_token());
213  
                return a_.await_suspend(h, p_->executor(), p_->stop_token());
214  
            }
214  
            }
215  
        };
215  
        };
216  

216  

217  
        template<class Awaitable>
217  
        template<class Awaitable>
218  
        auto transform_awaitable(Awaitable&& a)
218  
        auto transform_awaitable(Awaitable&& a)
219  
        {
219  
        {
220  
            using A = std::decay_t<Awaitable>;
220  
            using A = std::decay_t<Awaitable>;
221  
            if constexpr (IoAwaitable<A>)
221  
            if constexpr (IoAwaitable<A>)
222  
            {
222  
            {
223  
                return transform_awaiter<Awaitable>{
223  
                return transform_awaiter<Awaitable>{
224  
                    std::forward<Awaitable>(a), this};
224  
                    std::forward<Awaitable>(a), this};
225  
            }
225  
            }
226  
            else
226  
            else
227  
            {
227  
            {
228  
                static_assert(sizeof(A) == 0, "requires IoAwaitable");
228  
                static_assert(sizeof(A) == 0, "requires IoAwaitable");
229  
            }
229  
            }
230  
        }
230  
        }
231  
    };
231  
    };
232  

232  

233  
    std::coroutine_handle<promise_type> h_;
233  
    std::coroutine_handle<promise_type> h_;
234  

234  

235  
    /// Destroy the task and its coroutine frame if owned.
235  
    /// Destroy the task and its coroutine frame if owned.
236  
    ~task()
236  
    ~task()
237  
    {
237  
    {
238  
        if(h_)
238  
        if(h_)
239  
            h_.destroy();
239  
            h_.destroy();
240  
    }
240  
    }
241  

241  

242  
    /// Return false; tasks are never immediately ready.
242  
    /// Return false; tasks are never immediately ready.
243  
    bool await_ready() const noexcept
243  
    bool await_ready() const noexcept
244  
    {
244  
    {
245  
        return false;
245  
        return false;
246  
    }
246  
    }
247  

247  

248  
    /// Return the result or rethrow any stored exception.
248  
    /// Return the result or rethrow any stored exception.
249  
    auto await_resume()
249  
    auto await_resume()
250  
    {
250  
    {
251  
        if(h_.promise().has_ep_)
251  
        if(h_.promise().has_ep_)
252  
            std::rethrow_exception(h_.promise().ep_);
252  
            std::rethrow_exception(h_.promise().ep_);
253  
        if constexpr (! std::is_void_v<T>)
253  
        if constexpr (! std::is_void_v<T>)
254  
            return std::move(*h_.promise().result_);
254  
            return std::move(*h_.promise().result_);
255  
        else
255  
        else
256  
            return;
256  
            return;
257  
    }
257  
    }
258  

258  

259  
    /// Start execution with the caller's context.
259  
    /// Start execution with the caller's context.
260  
    coro await_suspend(coro cont,
260  
    coro await_suspend(coro cont,
261  
        executor_ref const& caller_ex, std::stop_token const& token)
261  
        executor_ref const& caller_ex, std::stop_token const& token)
262  
    {
262  
    {
263  
        h_.promise().set_continuation(cont, caller_ex);
263  
        h_.promise().set_continuation(cont, caller_ex);
264  
        h_.promise().set_executor(caller_ex);
264  
        h_.promise().set_executor(caller_ex);
265  
        h_.promise().set_stop_token(token);
265  
        h_.promise().set_stop_token(token);
266  
        return h_;
266  
        return h_;
267  
    }
267  
    }
268  

268  

269  
    /// Return the coroutine handle.
269  
    /// Return the coroutine handle.
270  
    std::coroutine_handle<promise_type> handle() const noexcept
270  
    std::coroutine_handle<promise_type> handle() const noexcept
271  
    {
271  
    {
272  
        return h_;
272  
        return h_;
273  
    }
273  
    }
274  

274  

275  
    /** Release ownership of the coroutine frame.
275  
    /** Release ownership of the coroutine frame.
276  

276  

277  
        After calling this, destroying the task does not destroy the
277  
        After calling this, destroying the task does not destroy the
278  
        coroutine frame. The caller becomes responsible for the frame's
278  
        coroutine frame. The caller becomes responsible for the frame's
279  
        lifetime.
279  
        lifetime.
280  

280  

281  
        @par Postconditions
281  
        @par Postconditions
282  
        `handle()` returns the original handle, but the task no longer
282  
        `handle()` returns the original handle, but the task no longer
283  
        owns it.
283  
        owns it.
284  
    */
284  
    */
285  
    void release() noexcept
285  
    void release() noexcept
286  
    {
286  
    {
287  
        h_ = nullptr;
287  
        h_ = nullptr;
288  
    }
288  
    }
289  

289  

290  
    task(task const&) = delete;
290  
    task(task const&) = delete;
291  
    task& operator=(task const&) = delete;
291  
    task& operator=(task const&) = delete;
292  

292  

293  
    /// Move construct, transferring ownership.
293  
    /// Move construct, transferring ownership.
294  
    task(task&& other) noexcept
294  
    task(task&& other) noexcept
295  
        : h_(std::exchange(other.h_, nullptr))
295  
        : h_(std::exchange(other.h_, nullptr))
296  
    {
296  
    {
297  
    }
297  
    }
298  

298  

299  
    /// Move assign, transferring ownership.
299  
    /// Move assign, transferring ownership.
300  
    task& operator=(task&& other) noexcept
300  
    task& operator=(task&& other) noexcept
301  
    {
301  
    {
302  
        if(this != &other)
302  
        if(this != &other)
303  
        {
303  
        {
304  
            if(h_)
304  
            if(h_)
305  
                h_.destroy();
305  
                h_.destroy();
306  
            h_ = std::exchange(other.h_, nullptr);
306  
            h_ = std::exchange(other.h_, nullptr);
307  
        }
307  
        }
308  
        return *this;
308  
        return *this;
309  
    }
309  
    }
310  

310  

311  
private:
311  
private:
312  
    explicit task(std::coroutine_handle<promise_type> h)
312  
    explicit task(std::coroutine_handle<promise_type> h)
313  
        : h_(h)
313  
        : h_(h)
314  
    {
314  
    {
315  
    }
315  
    }
316  
};
316  
};
317  

317  

318  
} // namespace capy
318  
} // namespace capy
319  
} // namespace boost
319  
} // namespace boost
320  

320  

321  
#endif
321  
#endif