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_IO_ENV_HPP
10  
#ifndef BOOST_CAPY_IO_ENV_HPP
11  
#define BOOST_CAPY_IO_ENV_HPP
11  
#define BOOST_CAPY_IO_ENV_HPP
12  

12  

13  
#include <boost/capy/detail/config.hpp>
13  
#include <boost/capy/detail/config.hpp>
14  
#include <boost/capy/ex/executor_ref.hpp>
14  
#include <boost/capy/ex/executor_ref.hpp>
15  

15  

16  
#include <coroutine>
16  
#include <coroutine>
17  
#include <memory_resource>
17  
#include <memory_resource>
18  
#include <stop_token>
18  
#include <stop_token>
19  

19  

20  
namespace boost {
20  
namespace boost {
21  
namespace capy {
21  
namespace capy {
22  

22  

23  
/** Callable that posts a continuation to an executor.
23  
/** Callable that posts a continuation to an executor.
24  

24  

25  
    Use this as the callback type for `std::stop_callback` instead
25  
    Use this as the callback type for `std::stop_callback` instead
26  
    of resuming a coroutine handle directly. Direct resumption runs
26  
    of resuming a coroutine handle directly. Direct resumption runs
27  
    the coroutine inline on whatever thread calls `request_stop()`,
27  
    the coroutine inline on whatever thread calls `request_stop()`,
28  
    which bypasses the executor and corrupts the thread-local
28  
    which bypasses the executor and corrupts the thread-local
29  
    frame allocator.
29  
    frame allocator.
30  

30  

31  
    Prefer @ref io_env::post_resume and the @ref stop_resume_callback
31  
    Prefer @ref io_env::post_resume and the @ref stop_resume_callback
32  
    alias to construct these—see examples there.
32  
    alias to construct these—see examples there.
33  

33  

34  
    @see io_env::post_resume, stop_resume_callback
34  
    @see io_env::post_resume, stop_resume_callback
35  
*/
35  
*/
36  
struct resume_via_post
36  
struct resume_via_post
37  
{
37  
{
38  
    executor_ref ex;
38  
    executor_ref ex;
39  
    mutable continuation cont;
39  
    mutable continuation cont;
40  

40  

41  
    // post() must not throw; stop_callback requires a
41  
    // post() must not throw; stop_callback requires a
42  
    // non-throwing invocable.
42  
    // non-throwing invocable.
43  
    void operator()() const noexcept
43  
    void operator()() const noexcept
44  
    {
44  
    {
45  
        ex.post(cont);
45  
        ex.post(cont);
46  
    }
46  
    }
47  
};
47  
};
48  

48  

49  
/** Execution environment for IoAwaitables.
49  
/** Execution environment for IoAwaitables.
50  

50  

51  
    This struct bundles the execution context passed through
51  
    This struct bundles the execution context passed through
52  
    coroutine chains via the IoAwaitable protocol. It contains
52  
    coroutine chains via the IoAwaitable protocol. It contains
53  
    the executor for resumption, a stop token for cancellation,
53  
    the executor for resumption, a stop token for cancellation,
54  
    and an optional frame allocator for coroutine frame allocation.
54  
    and an optional frame allocator for coroutine frame allocation.
55  

55  

56  
    @par Lifetime
56  
    @par Lifetime
57  

57  

58  
    Launch functions (@ref run_async, @ref run) own the `io_env` and
58  
    Launch functions (@ref run_async, @ref run) own the `io_env` and
59  
    guarantee it outlives all tasks and awaitables in the launched
59  
    guarantee it outlives all tasks and awaitables in the launched
60  
    chain. Awaitables receive `io_env const*` in `await_suspend`
60  
    chain. Awaitables receive `io_env const*` in `await_suspend`
61  
    and should store it directly, never copy the pointed-to object.
61  
    and should store it directly, never copy the pointed-to object.
62  

62  

63  
    @par Stop Callback Contract
63  
    @par Stop Callback Contract
64  

64  

65  
    Awaitables that register a `std::stop_callback` **must not**
65  
    Awaitables that register a `std::stop_callback` **must not**
66  
    resume the coroutine handle directly. The callback fires
66  
    resume the coroutine handle directly. The callback fires
67  
    synchronously on the thread that calls `request_stop()`, which
67  
    synchronously on the thread that calls `request_stop()`, which
68  
    may not be an executor-managed thread. Resuming inline poisons
68  
    may not be an executor-managed thread. Resuming inline poisons
69  
    that thread's TLS frame allocator with the pool's allocator,
69  
    that thread's TLS frame allocator with the pool's allocator,
70  
    causing use-after-free on the next coroutine allocation.
70  
    causing use-after-free on the next coroutine allocation.
71  

71  

72  
    Use @ref io_env::post_resume and @ref stop_resume_callback:
72  
    Use @ref io_env::post_resume and @ref stop_resume_callback:
73  
    @code
73  
    @code
74  
    std::optional<stop_resume_callback> stop_cb_;
74  
    std::optional<stop_resume_callback> stop_cb_;
75  
    // In await_suspend:
75  
    // In await_suspend:
76  
    stop_cb_.emplace(env->stop_token, env->post_resume(h));
76  
    stop_cb_.emplace(env->stop_token, env->post_resume(h));
77  
    @endcode
77  
    @endcode
78  

78  

79  
    @par Thread Safety
79  
    @par Thread Safety
80  
    The referenced executor and allocator must remain valid
80  
    The referenced executor and allocator must remain valid
81  
    for the lifetime of any coroutine using this environment.
81  
    for the lifetime of any coroutine using this environment.
82  

82  

83  
    @see IoAwaitable, IoRunnable, resume_via_post
83  
    @see IoAwaitable, IoRunnable, resume_via_post
84  
*/
84  
*/
85  
struct io_env
85  
struct io_env
86  
{
86  
{
87  
    /** The executor for coroutine resumption. */
87  
    /** The executor for coroutine resumption. */
88  
    executor_ref executor;
88  
    executor_ref executor;
89  

89  

90  
    /** The stop token for cancellation propagation. */
90  
    /** The stop token for cancellation propagation. */
91  
    std::stop_token stop_token;
91  
    std::stop_token stop_token;
92  

92  

93  
    /** The frame allocator for coroutine frame allocation.
93  
    /** The frame allocator for coroutine frame allocation.
94  

94  

95  
        When null, the default allocator is used.
95  
        When null, the default allocator is used.
96  
    */
96  
    */
97  
    std::pmr::memory_resource* frame_allocator = nullptr;
97  
    std::pmr::memory_resource* frame_allocator = nullptr;
98  

98  

99  
    /** Create a resume_via_post callable for this environment.
99  
    /** Create a resume_via_post callable for this environment.
100  

100  

101  
        Convenience method for registering @ref stop_resume_callback
101  
        Convenience method for registering @ref stop_resume_callback
102  
        instances. Wraps the coroutine handle in a @ref continuation
102  
        instances. Wraps the coroutine handle in a @ref continuation
103  
        and pairs it with this environment's executor. Equivalent to
103  
        and pairs it with this environment's executor. Equivalent to
104  
        `resume_via_post{executor, continuation{h}}`.
104  
        `resume_via_post{executor, continuation{h}}`.
105  

105  

106  
        @par Example
106  
        @par Example
107  
        @code
107  
        @code
108  
        stop_cb_.emplace(env->stop_token, env->post_resume(h));
108  
        stop_cb_.emplace(env->stop_token, env->post_resume(h));
109  
        @endcode
109  
        @endcode
110  

110  

111  
        @param h The coroutine handle to wrap in a continuation
111  
        @param h The coroutine handle to wrap in a continuation
112  
            and post on cancellation.
112  
            and post on cancellation.
113  

113  

114  
        @return A @ref resume_via_post callable that holds a
114  
        @return A @ref resume_via_post callable that holds a
115  
        non-owning @ref executor_ref and a @ref continuation.
115  
        non-owning @ref executor_ref and a @ref continuation.
116  
        The callable must not outlive the executor it references.
116  
        The callable must not outlive the executor it references.
117  

117  

118  
        @see resume_via_post, stop_resume_callback
118  
        @see resume_via_post, stop_resume_callback
119  
    */
119  
    */
120  
    resume_via_post
120  
    resume_via_post
121  
    post_resume(std::coroutine_handle<> h) const noexcept
121  
    post_resume(std::coroutine_handle<> h) const noexcept
122  
    {
122  
    {
123  
        return resume_via_post{executor, continuation{h}};
123  
        return resume_via_post{executor, continuation{h}};
124  
    }
124  
    }
125  
};
125  
};
126  

126  

127  
/** Type alias for a stop callback that posts through the executor.
127  
/** Type alias for a stop callback that posts through the executor.
128  

128  

129  
    Use this to declare the stop callback member in your awaitable:
129  
    Use this to declare the stop callback member in your awaitable:
130  
    @code
130  
    @code
131  
    std::optional<stop_resume_callback> stop_cb_;
131  
    std::optional<stop_resume_callback> stop_cb_;
132  
    @endcode
132  
    @endcode
133  

133  

134  
    @see resume_via_post, io_env::post_resume
134  
    @see resume_via_post, io_env::post_resume
135  
*/
135  
*/
136  
using stop_resume_callback = std::stop_callback<resume_via_post>;
136  
using stop_resume_callback = std::stop_callback<resume_via_post>;
137  

137  

138  
} // capy
138  
} // capy
139  
} // boost
139  
} // boost
140  

140  

141  
#endif
141  
#endif