1  
//
1  
//
2  
// Copyright (c) 2026 Michael Vandeberg
2  
// Copyright (c) 2026 Michael Vandeberg
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_QUITTER_HPP
10  
#ifndef BOOST_CAPY_QUITTER_HPP
11  
#define BOOST_CAPY_QUITTER_HPP
11  
#define BOOST_CAPY_QUITTER_HPP
12  

12  

13  
#include <boost/capy/detail/config.hpp>
13  
#include <boost/capy/detail/config.hpp>
14  
#include <boost/capy/detail/stop_requested_exception.hpp>
14  
#include <boost/capy/detail/stop_requested_exception.hpp>
15  
#include <boost/capy/concept/executor.hpp>
15  
#include <boost/capy/concept/executor.hpp>
16  
#include <boost/capy/concept/io_awaitable.hpp>
16  
#include <boost/capy/concept/io_awaitable.hpp>
17  
#include <boost/capy/ex/io_awaitable_promise_base.hpp>
17  
#include <boost/capy/ex/io_awaitable_promise_base.hpp>
18  
#include <boost/capy/ex/io_env.hpp>
18  
#include <boost/capy/ex/io_env.hpp>
19  
#include <boost/capy/ex/frame_allocator.hpp>
19  
#include <boost/capy/ex/frame_allocator.hpp>
20  
#include <boost/capy/detail/await_suspend_helper.hpp>
20  
#include <boost/capy/detail/await_suspend_helper.hpp>
21  

21  

22  
#include <exception>
22  
#include <exception>
23  
#include <optional>
23  
#include <optional>
24  
#include <type_traits>
24  
#include <type_traits>
25  
#include <utility>
25  
#include <utility>
26  

26  

27  
/* Stop-aware coroutine task.
27  
/* Stop-aware coroutine task.
28  

28  

29  
   quitter<T> is identical to task<T> except that when the stop token
29  
   quitter<T> is identical to task<T> except that when the stop token
30  
   is triggered, the coroutine body never sees the cancellation.  The
30  
   is triggered, the coroutine body never sees the cancellation.  The
31  
   promise intercepts it on resume (in transform_awaiter::await_resume)
31  
   promise intercepts it on resume (in transform_awaiter::await_resume)
32  
   and throws a sentinel exception that unwinds through RAII destructors
32  
   and throws a sentinel exception that unwinds through RAII destructors
33  
   to final_suspend.  The parent sees a "stopped" completion.
33  
   to final_suspend.  The parent sees a "stopped" completion.
34  

34  

35  
   See doc/quitter.md for the full design rationale. */
35  
   See doc/quitter.md for the full design rationale. */
36  

36  

37  
namespace boost {
37  
namespace boost {
38  
namespace capy {
38  
namespace capy {
39  

39  

40  
namespace detail {
40  
namespace detail {
41  

41  

42  
// Reuse the same return-value storage as task<T>.
42  
// Reuse the same return-value storage as task<T>.
43  
// task_return_base is defined in task.hpp, but quitter needs its own
43  
// task_return_base is defined in task.hpp, but quitter needs its own
44  
// copy to avoid a header dependency on task.hpp.
44  
// copy to avoid a header dependency on task.hpp.
45  
template<typename T>
45  
template<typename T>
46  
struct quitter_return_base
46  
struct quitter_return_base
47  
{
47  
{
48  
    std::optional<T> result_;
48  
    std::optional<T> result_;
49  

49  

50  
    void return_value(T value)
50  
    void return_value(T value)
51  
    {
51  
    {
52  
        result_ = std::move(value);
52  
        result_ = std::move(value);
53  
    }
53  
    }
54  

54  

55  
    T&& result() noexcept
55  
    T&& result() noexcept
56  
    {
56  
    {
57  
        return std::move(*result_);
57  
        return std::move(*result_);
58  
    }
58  
    }
59  
};
59  
};
60  

60  

61  
template<>
61  
template<>
62  
struct quitter_return_base<void>
62  
struct quitter_return_base<void>
63  
{
63  
{
64  
    void return_void()
64  
    void return_void()
65  
    {
65  
    {
66  
    }
66  
    }
67  
};
67  
};
68  

68  

69  
} // namespace detail
69  
} // namespace detail
70  

70  

71  
/** Stop-aware lazy coroutine task satisfying @ref IoRunnable.
71  
/** Stop-aware lazy coroutine task satisfying @ref IoRunnable.
72  

72  

73  
    When the stop token is triggered, the next `co_await` inside the
73  
    When the stop token is triggered, the next `co_await` inside the
74  
    coroutine short-circuits: the body never sees the result and RAII
74  
    coroutine short-circuits: the body never sees the result and RAII
75  
    destructors run normally.  The parent observes a "stopped"
75  
    destructors run normally.  The parent observes a "stopped"
76  
    completion via @ref promise_type::stopped.
76  
    completion via @ref promise_type::stopped.
77  

77  

78  
    Everything else — frame allocation, environment propagation,
78  
    Everything else — frame allocation, environment propagation,
79  
    symmetric transfer, move semantics — is identical to @ref task.
79  
    symmetric transfer, move semantics — is identical to @ref task.
80  

80  

81  
    @tparam T The result type.  Use `quitter<>` for `quitter<void>`.
81  
    @tparam T The result type.  Use `quitter<>` for `quitter<void>`.
82  

82  

83  
    @see task, IoRunnable, IoAwaitable
83  
    @see task, IoRunnable, IoAwaitable
84  
*/
84  
*/
85  
template<typename T = void>
85  
template<typename T = void>
86  
struct [[nodiscard]] BOOST_CAPY_CORO_AWAIT_ELIDABLE
86  
struct [[nodiscard]] BOOST_CAPY_CORO_AWAIT_ELIDABLE
87  
    quitter
87  
    quitter
88  
{
88  
{
89  
    struct promise_type
89  
    struct promise_type
90  
        : io_awaitable_promise_base<promise_type>
90  
        : io_awaitable_promise_base<promise_type>
91  
        , detail::quitter_return_base<T>
91  
        , detail::quitter_return_base<T>
92  
    {
92  
    {
93  
    private:
93  
    private:
94  
        friend quitter;
94  
        friend quitter;
95  

95  

96  
        enum class completion { running, value, exception, stopped };
96  
        enum class completion { running, value, exception, stopped };
97  

97  

98  
        union { std::exception_ptr ep_; };
98  
        union { std::exception_ptr ep_; };
99  
        completion state_;
99  
        completion state_;
100  

100  

101  
    public:
101  
    public:
102  
        promise_type() noexcept
102  
        promise_type() noexcept
103  
            : state_(completion::running)
103  
            : state_(completion::running)
104  
        {
104  
        {
105  
        }
105  
        }
106  

106  

107  
        ~promise_type()
107  
        ~promise_type()
108  
        {
108  
        {
109  
            if(state_ == completion::exception ||
109  
            if(state_ == completion::exception ||
110  
               state_ == completion::stopped)
110  
               state_ == completion::stopped)
111  
                ep_.~exception_ptr();
111  
                ep_.~exception_ptr();
112  
        }
112  
        }
113  

113  

114  
        /// Return a non-null exception_ptr when the coroutine threw
114  
        /// Return a non-null exception_ptr when the coroutine threw
115  
        /// or was stopped.  Stopped quitters report the sentinel
115  
        /// or was stopped.  Stopped quitters report the sentinel
116  
        /// stop_requested_exception so that run_async routes to
116  
        /// stop_requested_exception so that run_async routes to
117  
        /// the error handler instead of accessing a non-existent
117  
        /// the error handler instead of accessing a non-existent
118  
        /// result.
118  
        /// result.
119  
        std::exception_ptr exception() const noexcept
119  
        std::exception_ptr exception() const noexcept
120  
        {
120  
        {
121  
            if(state_ == completion::exception ||
121  
            if(state_ == completion::exception ||
122  
               state_ == completion::stopped)
122  
               state_ == completion::stopped)
123  
                return ep_;
123  
                return ep_;
124  
            return {};
124  
            return {};
125  
        }
125  
        }
126  

126  

127  
        /// True when the coroutine was stopped via the stop token.
127  
        /// True when the coroutine was stopped via the stop token.
128  
        bool stopped() const noexcept
128  
        bool stopped() const noexcept
129  
        {
129  
        {
130  
            return state_ == completion::stopped;
130  
            return state_ == completion::stopped;
131  
        }
131  
        }
132  

132  

133  
        quitter get_return_object()
133  
        quitter get_return_object()
134  
        {
134  
        {
135  
            return quitter{
135  
            return quitter{
136  
                std::coroutine_handle<promise_type>::from_promise(*this)};
136  
                std::coroutine_handle<promise_type>::from_promise(*this)};
137  
        }
137  
        }
138  

138  

139  
        auto initial_suspend() noexcept
139  
        auto initial_suspend() noexcept
140  
        {
140  
        {
141  
            struct awaiter
141  
            struct awaiter
142  
            {
142  
            {
143  
                promise_type* p_;
143  
                promise_type* p_;
144  

144  

145  
                bool await_ready() const noexcept
145  
                bool await_ready() const noexcept
146  
                {
146  
                {
147  
                    return false;
147  
                    return false;
148  
                }
148  
                }
149  

149  

150  
                void await_suspend(std::coroutine_handle<>) const noexcept
150  
                void await_suspend(std::coroutine_handle<>) const noexcept
151  
                {
151  
                {
152  
                }
152  
                }
153  

153  

154  
                // Potentially-throwing: checks the stop token before
154  
                // Potentially-throwing: checks the stop token before
155  
                // the coroutine body executes its first statement.
155  
                // the coroutine body executes its first statement.
156  
                void await_resume() const
156  
                void await_resume() const
157  
                {
157  
                {
158  
                    set_current_frame_allocator(
158  
                    set_current_frame_allocator(
159  
                        p_->environment()->frame_allocator);
159  
                        p_->environment()->frame_allocator);
160  
                    if(p_->environment()->stop_token.stop_requested())
160  
                    if(p_->environment()->stop_token.stop_requested())
161  
                        throw detail::stop_requested_exception{};
161  
                        throw detail::stop_requested_exception{};
162  
                }
162  
                }
163  
            };
163  
            };
164  
            return awaiter{this};
164  
            return awaiter{this};
165  
        }
165  
        }
166  

166  

167  
        auto final_suspend() noexcept
167  
        auto final_suspend() noexcept
168  
        {
168  
        {
169  
            struct awaiter
169  
            struct awaiter
170  
            {
170  
            {
171  
                promise_type* p_;
171  
                promise_type* p_;
172  

172  

173  
                bool await_ready() const noexcept
173  
                bool await_ready() const noexcept
174  
                {
174  
                {
175  
                    return false;
175  
                    return false;
176  
                }
176  
                }
177  

177  

178  
                std::coroutine_handle<> await_suspend(
178  
                std::coroutine_handle<> await_suspend(
179  
                    std::coroutine_handle<>) const noexcept
179  
                    std::coroutine_handle<>) const noexcept
180  
                {
180  
                {
181  
                    return p_->continuation();
181  
                    return p_->continuation();
182  
                }
182  
                }
183  

183  

184  
                void await_resume() const noexcept
184  
                void await_resume() const noexcept
185  
                {
185  
                {
186  
                }
186  
                }
187  
            };
187  
            };
188  
            return awaiter{this};
188  
            return awaiter{this};
189  
        }
189  
        }
190  

190  

191  
        void unhandled_exception()
191  
        void unhandled_exception()
192  
        {
192  
        {
193  
            try
193  
            try
194  
            {
194  
            {
195  
                throw;
195  
                throw;
196  
            }
196  
            }
197  
            catch(detail::stop_requested_exception const&)
197  
            catch(detail::stop_requested_exception const&)
198  
            {
198  
            {
199  
                // Store the exception_ptr so that run_async's
199  
                // Store the exception_ptr so that run_async's
200  
                // invoke_impl routes to the error handler
200  
                // invoke_impl routes to the error handler
201  
                // instead of accessing a non-existent result.
201  
                // instead of accessing a non-existent result.
202  
                new (&ep_) std::exception_ptr(
202  
                new (&ep_) std::exception_ptr(
203  
                    std::current_exception());
203  
                    std::current_exception());
204  
                state_ = completion::stopped;
204  
                state_ = completion::stopped;
205  
            }
205  
            }
206  
            catch(...)
206  
            catch(...)
207  
            {
207  
            {
208  
                new (&ep_) std::exception_ptr(
208  
                new (&ep_) std::exception_ptr(
209  
                    std::current_exception());
209  
                    std::current_exception());
210  
                state_ = completion::exception;
210  
                state_ = completion::exception;
211  
            }
211  
            }
212  
        }
212  
        }
213  

213  

214  
        //------------------------------------------------------
214  
        //------------------------------------------------------
215  
        // transform_awaitable — the key difference from task<T>
215  
        // transform_awaitable — the key difference from task<T>
216  
        //------------------------------------------------------
216  
        //------------------------------------------------------
217  

217  

218  
        template<class Awaitable>
218  
        template<class Awaitable>
219  
        struct transform_awaiter
219  
        struct transform_awaiter
220  
        {
220  
        {
221  
            std::decay_t<Awaitable> a_;
221  
            std::decay_t<Awaitable> a_;
222  
            promise_type* p_;
222  
            promise_type* p_;
223  

223  

224  
            bool await_ready() noexcept
224  
            bool await_ready() noexcept
225  
            {
225  
            {
226  
                return a_.await_ready();
226  
                return a_.await_ready();
227  
            }
227  
            }
228  

228  

229  
            // Check the stop token BEFORE the coroutine body
229  
            // Check the stop token BEFORE the coroutine body
230  
            // sees the result of the I/O operation.
230  
            // sees the result of the I/O operation.
231  
            decltype(auto) await_resume()
231  
            decltype(auto) await_resume()
232  
            {
232  
            {
233  
                set_current_frame_allocator(
233  
                set_current_frame_allocator(
234  
                    p_->environment()->frame_allocator);
234  
                    p_->environment()->frame_allocator);
235  
                if(p_->environment()->stop_token.stop_requested())
235  
                if(p_->environment()->stop_token.stop_requested())
236  
                    throw detail::stop_requested_exception{};
236  
                    throw detail::stop_requested_exception{};
237  
                return a_.await_resume();
237  
                return a_.await_resume();
238  
            }
238  
            }
239  

239  

240  
            template<class Promise>
240  
            template<class Promise>
241  
            auto await_suspend(
241  
            auto await_suspend(
242  
                std::coroutine_handle<Promise> h) noexcept
242  
                std::coroutine_handle<Promise> h) noexcept
243  
            {
243  
            {
244  
                using R = decltype(
244  
                using R = decltype(
245  
                    a_.await_suspend(h, p_->environment()));
245  
                    a_.await_suspend(h, p_->environment()));
246  
                if constexpr (std::is_same_v<
246  
                if constexpr (std::is_same_v<
247  
                    R, std::coroutine_handle<>>)
247  
                    R, std::coroutine_handle<>>)
248  
                    return detail::symmetric_transfer(
248  
                    return detail::symmetric_transfer(
249  
                        a_.await_suspend(h, p_->environment()));
249  
                        a_.await_suspend(h, p_->environment()));
250  
                else
250  
                else
251  
                    return a_.await_suspend(
251  
                    return a_.await_suspend(
252  
                        h, p_->environment());
252  
                        h, p_->environment());
253  
            }
253  
            }
254  
        };
254  
        };
255  

255  

256  
        template<class Awaitable>
256  
        template<class Awaitable>
257  
        auto transform_awaitable(Awaitable&& a)
257  
        auto transform_awaitable(Awaitable&& a)
258  
        {
258  
        {
259  
            using A = std::decay_t<Awaitable>;
259  
            using A = std::decay_t<Awaitable>;
260  
            if constexpr (IoAwaitable<A>)
260  
            if constexpr (IoAwaitable<A>)
261  
            {
261  
            {
262  
                return transform_awaiter<Awaitable>{
262  
                return transform_awaiter<Awaitable>{
263  
                    std::forward<Awaitable>(a), this};
263  
                    std::forward<Awaitable>(a), this};
264  
            }
264  
            }
265  
            else
265  
            else
266  
            {
266  
            {
267  
                static_assert(sizeof(A) == 0,
267  
                static_assert(sizeof(A) == 0,
268  
                    "requires IoAwaitable");
268  
                    "requires IoAwaitable");
269  
            }
269  
            }
270  
        }
270  
        }
271  
    };
271  
    };
272  

272  

273  
    std::coroutine_handle<promise_type> h_;
273  
    std::coroutine_handle<promise_type> h_;
274  

274  

275  
    /// Destroy the quitter and its coroutine frame if owned.
275  
    /// Destroy the quitter and its coroutine frame if owned.
276  
    ~quitter()
276  
    ~quitter()
277  
    {
277  
    {
278  
        if(h_)
278  
        if(h_)
279  
            h_.destroy();
279  
            h_.destroy();
280  
    }
280  
    }
281  

281  

282  
    /// Return false; quitters are never immediately ready.
282  
    /// Return false; quitters are never immediately ready.
283  
    bool await_ready() const noexcept
283  
    bool await_ready() const noexcept
284  
    {
284  
    {
285  
        return false;
285  
        return false;
286  
    }
286  
    }
287  

287  

288  
    /** Return the result, rethrow exception, or propagate stop.
288  
    /** Return the result, rethrow exception, or propagate stop.
289  

289  

290  
        When stopped, throws stop_requested_exception so that a
290  
        When stopped, throws stop_requested_exception so that a
291  
        parent quitter also stops.  A parent task<T> will see this
291  
        parent quitter also stops.  A parent task<T> will see this
292  
        as an unhandled exception — by design.
292  
        as an unhandled exception — by design.
293  
    */
293  
    */
294  
    auto await_resume()
294  
    auto await_resume()
295  
    {
295  
    {
296  
        if(h_.promise().stopped())
296  
        if(h_.promise().stopped())
297  
            throw detail::stop_requested_exception{};
297  
            throw detail::stop_requested_exception{};
298  
        if(h_.promise().state_ == promise_type::completion::exception)
298  
        if(h_.promise().state_ == promise_type::completion::exception)
299  
            std::rethrow_exception(h_.promise().ep_);
299  
            std::rethrow_exception(h_.promise().ep_);
300  
        if constexpr (! std::is_void_v<T>)
300  
        if constexpr (! std::is_void_v<T>)
301  
            return std::move(*h_.promise().result_);
301  
            return std::move(*h_.promise().result_);
302  
        else
302  
        else
303  
            return;
303  
            return;
304  
    }
304  
    }
305  

305  

306  
    /// Start execution with the caller's context.
306  
    /// Start execution with the caller's context.
307  
    std::coroutine_handle<> await_suspend(
307  
    std::coroutine_handle<> await_suspend(
308  
        std::coroutine_handle<> cont,
308  
        std::coroutine_handle<> cont,
309  
        io_env const* env)
309  
        io_env const* env)
310  
    {
310  
    {
311  
        h_.promise().set_continuation(cont);
311  
        h_.promise().set_continuation(cont);
312  
        h_.promise().set_environment(env);
312  
        h_.promise().set_environment(env);
313  
        return h_;
313  
        return h_;
314  
    }
314  
    }
315  

315  

316  
    /// Return the coroutine handle.
316  
    /// Return the coroutine handle.
317  
    std::coroutine_handle<promise_type> handle() const noexcept
317  
    std::coroutine_handle<promise_type> handle() const noexcept
318  
    {
318  
    {
319  
        return h_;
319  
        return h_;
320  
    }
320  
    }
321  

321  

322  
    /// Release ownership of the coroutine frame.
322  
    /// Release ownership of the coroutine frame.
323  
    void release() noexcept
323  
    void release() noexcept
324  
    {
324  
    {
325  
        h_ = nullptr;
325  
        h_ = nullptr;
326  
    }
326  
    }
327  

327  

328  
    quitter(quitter const&) = delete;
328  
    quitter(quitter const&) = delete;
329  
    quitter& operator=(quitter const&) = delete;
329  
    quitter& operator=(quitter const&) = delete;
330  

330  

331  
    /// Construct by moving, transferring ownership.
331  
    /// Construct by moving, transferring ownership.
332  
    quitter(quitter&& other) noexcept
332  
    quitter(quitter&& other) noexcept
333  
        : h_(std::exchange(other.h_, nullptr))
333  
        : h_(std::exchange(other.h_, nullptr))
334  
    {
334  
    {
335  
    }
335  
    }
336  

336  

337  
    /// Assign by moving, transferring ownership.
337  
    /// Assign by moving, transferring ownership.
338  
    quitter& operator=(quitter&& other) noexcept
338  
    quitter& operator=(quitter&& other) noexcept
339  
    {
339  
    {
340  
        if(this != &other)
340  
        if(this != &other)
341  
        {
341  
        {
342  
            if(h_)
342  
            if(h_)
343  
                h_.destroy();
343  
                h_.destroy();
344  
            h_ = std::exchange(other.h_, nullptr);
344  
            h_ = std::exchange(other.h_, nullptr);
345  
        }
345  
        }
346  
        return *this;
346  
        return *this;
347  
    }
347  
    }
348  

348  

349  
private:
349  
private:
350  
    explicit quitter(std::coroutine_handle<promise_type> h)
350  
    explicit quitter(std::coroutine_handle<promise_type> h)
351  
        : h_(h)
351  
        : h_(h)
352  
    {
352  
    {
353  
    }
353  
    }
354  
};
354  
};
355  

355  

356  
} // namespace capy
356  
} // namespace capy
357  
} // namespace boost
357  
} // namespace boost
358  

358  

359  
#endif
359  
#endif