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 <memory_resource>
17  
#include <memory_resource>
17  
#include <stop_token>
18  
#include <stop_token>
18  

19  

19  
namespace boost {
20  
namespace boost {
20  
namespace capy {
21  
namespace capy {
21  

22  

 
23 +
/** Callable that posts a coroutine handle to an executor.
 
24 +

 
25 +
    Use this as the callback type for `std::stop_callback` instead
 
26 +
    of a raw `std::coroutine_handle<>`. Raw handles resume the
 
27 +
    coroutine inline on whatever thread calls `request_stop()`,
 
28 +
    which bypasses the executor and corrupts the thread-local
 
29 +
    frame allocator.
 
30 +

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

 
34 +
    @see io_env::post_resume, stop_resume_callback
 
35 +
*/
 
36 +
struct resume_via_post
 
37 +
{
 
38 +
    executor_ref ex;
 
39 +
    std::coroutine_handle<> h;
 
40 +

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

22  
/** Execution environment for IoAwaitables.
49  
/** Execution environment for IoAwaitables.
23  

50  

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

55  

29  
    @par Lifetime
56  
    @par Lifetime
30  

57  

31  
    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
32  
    guarantee it outlives all tasks and awaitables in the launched
59  
    guarantee it outlives all tasks and awaitables in the launched
33  
    chain. Awaitables receive `io_env const*` in `await_suspend`
60  
    chain. Awaitables receive `io_env const*` in `await_suspend`
34  
    and should store it directly, never copy the pointed-to object.
61  
    and should store it directly, never copy the pointed-to object.
35  

62  

 
63 +
    @par Stop Callback Contract
 
64 +

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

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

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

82  

40 -
    @see IoAwaitable, IoRunnable
83 +
    @see IoAwaitable, IoRunnable, resume_via_post
41  
*/
84  
*/
42  
struct io_env
85  
struct io_env
43  
{
86  
{
44  
    /** The executor for coroutine resumption. */
87  
    /** The executor for coroutine resumption. */
45  
    executor_ref executor;
88  
    executor_ref executor;
46  

89  

47  
    /** The stop token for cancellation propagation. */
90  
    /** The stop token for cancellation propagation. */
48  
    std::stop_token stop_token;
91  
    std::stop_token stop_token;
49  

92  

50  
    /** The frame allocator for coroutine frame allocation.
93  
    /** The frame allocator for coroutine frame allocation.
51  

94  

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

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

 
101 +
        Convenience method for registering @ref stop_resume_callback
 
102 +
        instances. Equivalent to `resume_via_post{executor, h}`.
 
103 +

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

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

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

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

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

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

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

134  

57  
} // capy
135  
} // capy
58  
} // boost
136  
} // boost
59  

137  

60  
#endif
138  
#endif