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 -
        std::exception_ptr ep_;
104 +
    private:
 
105 +
        friend task;
 
106 +
        union { std::exception_ptr ep_; };
 
107 +
        bool has_ep_;
 
108 +

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

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

120  

106  
        std::exception_ptr exception() const noexcept
121  
        std::exception_ptr exception() const noexcept
107  
        {
122  
        {
108 -
            return ep_;
123 +
            if(has_ep_)
 
124 +
                return ep_;
 
125 +
            return {};
109  
        }
126  
        }
110  

127  

111  
        task get_return_object()
128  
        task get_return_object()
112  
        {
129  
        {
113  
            return task{std::coroutine_handle<promise_type>::from_promise(*this)};
130  
            return task{std::coroutine_handle<promise_type>::from_promise(*this)};
114  
        }
131  
        }
115  

132  

116  
        auto initial_suspend() noexcept
133  
        auto initial_suspend() noexcept
117  
        {
134  
        {
118  
            struct awaiter
135  
            struct awaiter
119  
            {
136  
            {
120  
                promise_type* p_;
137  
                promise_type* p_;
121  

138  

122  
                bool await_ready() const noexcept
139  
                bool await_ready() const noexcept
123  
                {
140  
                {
124  
                    return false;
141  
                    return false;
125  
                }
142  
                }
126  

143  

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

149  

133  
                void await_resume() const noexcept
150  
                void await_resume() const noexcept
134  
                {
151  
                {
135  
                    // Restore TLS when body starts executing
152  
                    // Restore TLS when body starts executing
136 -
                    if(p_->frame_allocator())
153 +
                    auto* fa = p_->frame_allocator();
137 -
                        current_frame_allocator() = p_->frame_allocator();
154 +
                    if(fa && fa != current_frame_allocator())
 
155 +
                        current_frame_allocator() = fa;
138  
                }
156  
                }
139  
            };
157  
            };
140  
            return awaiter{this};
158  
            return awaiter{this};
141  
        }
159  
        }
142  

160  

143  
        auto final_suspend() noexcept
161  
        auto final_suspend() noexcept
144  
        {
162  
        {
145  
            struct awaiter
163  
            struct awaiter
146  
            {
164  
            {
147  
                promise_type* p_;
165  
                promise_type* p_;
148  

166  

149  
                bool await_ready() const noexcept
167  
                bool await_ready() const noexcept
150  
                {
168  
                {
151  
                    return false;
169  
                    return false;
152  
                }
170  
                }
153  

171  

154  
                coro await_suspend(coro) const noexcept
172  
                coro await_suspend(coro) const noexcept
155  
                {
173  
                {
156  
                    return p_->complete();
174  
                    return p_->complete();
157  
                }
175  
                }
158  

176  

159  
                void await_resume() const noexcept
177  
                void await_resume() const noexcept
160  
                {
178  
                {
161  
                }
179  
                }
162  
            };
180  
            };
163  
            return awaiter{this};
181  
            return awaiter{this};
164  
        }
182  
        }
165  

183  

166  
        void unhandled_exception()
184  
        void unhandled_exception()
167  
        {
185  
        {
168 -
            ep_ = std::current_exception();
186 +
            new (&ep_) std::exception_ptr(std::current_exception());
 
187 +
            has_ep_ = true;
169  
        }
188  
        }
170  

189  

171  
        template<class Awaitable>
190  
        template<class Awaitable>
172  
        struct transform_awaiter
191  
        struct transform_awaiter
173  
        {
192  
        {
174  
            std::decay_t<Awaitable> a_;
193  
            std::decay_t<Awaitable> a_;
175  
            promise_type* p_;
194  
            promise_type* p_;
176  

195  

177 -
            bool await_ready()
196 +
            bool await_ready() noexcept
178  
            {
197  
            {
179  
                return a_.await_ready();
198  
                return a_.await_ready();
180  
            }
199  
            }
181  

200  

182  
            decltype(auto) await_resume()
201  
            decltype(auto) await_resume()
183  
            {
202  
            {
184  
                // Restore TLS before body resumes
203  
                // Restore TLS before body resumes
185 -
                if(p_->frame_allocator())
204 +
                auto* fa = p_->frame_allocator();
186 -
                    current_frame_allocator() = p_->frame_allocator();
205 +
                if(fa && fa != current_frame_allocator())
 
206 +
                    current_frame_allocator() = fa;
187  
                return a_.await_resume();
207  
                return a_.await_resume();
188  
            }
208  
            }
189  

209  

190  
            template<class Promise>
210  
            template<class Promise>
191 -
            auto await_suspend(std::coroutine_handle<Promise> h)
211 +
            auto await_suspend(std::coroutine_handle<Promise> h) noexcept
192  
            {
212  
            {
193  
                return a_.await_suspend(h, p_->executor(), p_->stop_token());
213  
                return a_.await_suspend(h, p_->executor(), p_->stop_token());
194  
            }
214  
            }
195  
        };
215  
        };
196  

216  

197  
        template<class Awaitable>
217  
        template<class Awaitable>
198  
        auto transform_awaitable(Awaitable&& a)
218  
        auto transform_awaitable(Awaitable&& a)
199  
        {
219  
        {
200  
            using A = std::decay_t<Awaitable>;
220  
            using A = std::decay_t<Awaitable>;
201  
            if constexpr (IoAwaitable<A>)
221  
            if constexpr (IoAwaitable<A>)
202  
            {
222  
            {
203  
                return transform_awaiter<Awaitable>{
223  
                return transform_awaiter<Awaitable>{
204  
                    std::forward<Awaitable>(a), this};
224  
                    std::forward<Awaitable>(a), this};
205  
            }
225  
            }
206  
            else
226  
            else
207  
            {
227  
            {
208  
                static_assert(sizeof(A) == 0, "requires IoAwaitable");
228  
                static_assert(sizeof(A) == 0, "requires IoAwaitable");
209  
            }
229  
            }
210  
        }
230  
        }
211  
    };
231  
    };
212  

232  

213  
    std::coroutine_handle<promise_type> h_;
233  
    std::coroutine_handle<promise_type> h_;
214  

234  

215  
    /// Destroy the task and its coroutine frame if owned.
235  
    /// Destroy the task and its coroutine frame if owned.
216  
    ~task()
236  
    ~task()
217  
    {
237  
    {
218  
        if(h_)
238  
        if(h_)
219  
            h_.destroy();
239  
            h_.destroy();
220  
    }
240  
    }
221  

241  

222  
    /// Return false; tasks are never immediately ready.
242  
    /// Return false; tasks are never immediately ready.
223  
    bool await_ready() const noexcept
243  
    bool await_ready() const noexcept
224  
    {
244  
    {
225  
        return false;
245  
        return false;
226  
    }
246  
    }
227  

247  

228  
    /// Return the result or rethrow any stored exception.
248  
    /// Return the result or rethrow any stored exception.
229  
    auto await_resume()
249  
    auto await_resume()
230  
    {
250  
    {
231 -
        if(h_.promise().ep_)
251 +
        if(h_.promise().has_ep_)
232  
            std::rethrow_exception(h_.promise().ep_);
252  
            std::rethrow_exception(h_.promise().ep_);
233  
        if constexpr (! std::is_void_v<T>)
253  
        if constexpr (! std::is_void_v<T>)
234  
            return std::move(*h_.promise().result_);
254  
            return std::move(*h_.promise().result_);
235  
        else
255  
        else
236  
            return;
256  
            return;
237  
    }
257  
    }
238  

258  

239  
    /// Start execution with the caller's context.
259  
    /// Start execution with the caller's context.
240 -
    coro await_suspend(coro cont, executor_ref caller_ex, std::stop_token token)
260 +
    coro await_suspend(coro cont,
 
261 +
        executor_ref const& caller_ex, std::stop_token const& token)
241  
    {
262  
    {
242  
        h_.promise().set_continuation(cont, caller_ex);
263  
        h_.promise().set_continuation(cont, caller_ex);
243  
        h_.promise().set_executor(caller_ex);
264  
        h_.promise().set_executor(caller_ex);
244  
        h_.promise().set_stop_token(token);
265  
        h_.promise().set_stop_token(token);
245  
        return h_;
266  
        return h_;
246  
    }
267  
    }
247  

268  

248  
    /// Return the coroutine handle.
269  
    /// Return the coroutine handle.
249  
    std::coroutine_handle<promise_type> handle() const noexcept
270  
    std::coroutine_handle<promise_type> handle() const noexcept
250  
    {
271  
    {
251  
        return h_;
272  
        return h_;
252  
    }
273  
    }
253  

274  

254  
    /** Release ownership of the coroutine frame.
275  
    /** Release ownership of the coroutine frame.
255  

276  

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

280  

260  
        @par Postconditions
281  
        @par Postconditions
261  
        `handle()` returns the original handle, but the task no longer
282  
        `handle()` returns the original handle, but the task no longer
262  
        owns it.
283  
        owns it.
263  
    */
284  
    */
264  
    void release() noexcept
285  
    void release() noexcept
265  
    {
286  
    {
266  
        h_ = nullptr;
287  
        h_ = nullptr;
267  
    }
288  
    }
268  

289  

269  
    task(task const&) = delete;
290  
    task(task const&) = delete;
270  
    task& operator=(task const&) = delete;
291  
    task& operator=(task const&) = delete;
271  

292  

272  
    /// Move construct, transferring ownership.
293  
    /// Move construct, transferring ownership.
273  
    task(task&& other) noexcept
294  
    task(task&& other) noexcept
274  
        : h_(std::exchange(other.h_, nullptr))
295  
        : h_(std::exchange(other.h_, nullptr))
275  
    {
296  
    {
276  
    }
297  
    }
277  

298  

278  
    /// Move assign, transferring ownership.
299  
    /// Move assign, transferring ownership.
279  
    task& operator=(task&& other) noexcept
300  
    task& operator=(task&& other) noexcept
280  
    {
301  
    {
281  
        if(this != &other)
302  
        if(this != &other)
282  
        {
303  
        {
283  
            if(h_)
304  
            if(h_)
284  
                h_.destroy();
305  
                h_.destroy();
285  
            h_ = std::exchange(other.h_, nullptr);
306  
            h_ = std::exchange(other.h_, nullptr);
286  
        }
307  
        }
287  
        return *this;
308  
        return *this;
288  
    }
309  
    }
289  

310  

290  
private:
311  
private:
291  
    explicit task(std::coroutine_handle<promise_type> h)
312  
    explicit task(std::coroutine_handle<promise_type> h)
292  
        : h_(h)
313  
        : h_(h)
293  
    {
314  
    {
294  
    }
315  
    }
295  
};
316  
};
296  

317  

297  
} // namespace capy
318  
} // namespace capy
298  
} // namespace boost
319  
} // namespace boost
299  

320  

300  
#endif
321  
#endif