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 coroutine handle to an executor.
23  
/** Callable that posts a coroutine handle 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 a raw `std::coroutine_handle<>`. Raw handles resume the
26  
    of a raw `std::coroutine_handle<>`. Raw handles resume the
27  
    coroutine inline on whatever thread calls `request_stop()`,
27  
    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  
    std::coroutine_handle<> h;
39  
    std::coroutine_handle<> h;
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(h);
45  
        ex.post(h);
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. Equivalent to `resume_via_post{executor, h}`.
102  
        instances. Equivalent to `resume_via_post{executor, h}`.
103  

103  

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

108  

109  
        @param h The coroutine handle to post on cancellation.
109  
        @param h The coroutine handle to post on cancellation.
110  

110  

111  
        @return A @ref resume_via_post callable that holds a
111  
        @return A @ref resume_via_post callable that holds a
112  
        non-owning @ref executor_ref and the coroutine handle.
112  
        non-owning @ref executor_ref and the coroutine handle.
113  
        The callable must not outlive the executor it references.
113  
        The callable must not outlive the executor it references.
114  

114  

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

123  

124  
/** Type alias for a stop callback that posts through the executor.
124  
/** Type alias for a stop callback that posts through the executor.
125  

125  

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

130  

131  
    @see resume_via_post, io_env::post_resume
131  
    @see resume_via_post, io_env::post_resume
132  
*/
132  
*/
133  
using stop_resume_callback = std::stop_callback<resume_via_post>;
133  
using stop_resume_callback = std::stop_callback<resume_via_post>;
134  

134  

135  
} // capy
135  
} // capy
136  
} // boost
136  
} // boost
137  

137  

138  
#endif
138  
#endif