1 +
//
 
2 +
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
 
3 +
//
 
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)
 
6 +
//
 
7 +
// Official repository: https://github.com/cppalliance/capy
 
8 +
//
 
9 +

 
10 +
#ifndef BOOST_CAPY_WORK_GUARD_HPP
 
11 +
#define BOOST_CAPY_WORK_GUARD_HPP
 
12 +

 
13 +
#include <boost/capy/detail/config.hpp>
 
14 +
#include <boost/capy/ex/execution_context.hpp>
 
15 +
#include <boost/capy/concept/executor.hpp>
 
16 +

 
17 +
#include <utility>
 
18 +

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

 
22 +
/** RAII guard that keeps an executor's context from completing.
 
23 +

 
24 +
    This class holds "work" on an executor, preventing the associated
 
25 +
    execution context's `run()` function from returning due to lack of
 
26 +
    work. It calls `on_work_started()` on construction and
 
27 +
    `on_work_finished()` on destruction, ensuring proper work tracking.
 
28 +

 
29 +
    The guard is useful when you need to keep an execution context
 
30 +
    running while waiting for external events or when work will be
 
31 +
    posted later.
 
32 +

 
33 +
    @par RAII Semantics
 
34 +

 
35 +
    @li Construction calls `ex.on_work_started()`.
 
36 +
    @li Destruction calls `ex.on_work_finished()` if `owns_work()`.
 
37 +
    @li Copy construction creates a new work reference (calls
 
38 +
        `on_work_started()` again).
 
39 +
    @li Move construction transfers ownership without additional calls.
 
40 +

 
41 +
    @par Thread Safety
 
42 +

 
43 +
    Distinct objects may be accessed concurrently. Access to a single
 
44 +
    object requires external synchronization.
 
45 +

 
46 +
    @par Example
 
47 +
    @code
 
48 +
    io_context ctx;
 
49 +

 
50 +
    // Keep context running while we set things up
 
51 +
    auto guard = make_work_guard(ctx);
 
52 +

 
53 +
    std::thread t([&ctx]{ ctx.run(); });
 
54 +

 
55 +
    // ... post work to ctx ...
 
56 +

 
57 +
    // Allow context to complete when work is done
 
58 +
    guard.reset();
 
59 +

 
60 +
    t.join();
 
61 +
    @endcode
 
62 +

 
63 +
    @note The executor is returned by reference, allowing callers to
 
64 +
    manage the executor's lifetime directly. This is essential in
 
65 +
    coroutine-first designs where the executor often outlives individual
 
66 +
    coroutine frames.
 
67 +

 
68 +
    @tparam Ex A type satisfying the Executor concept.
 
69 +

 
70 +
    @see make_work_guard, Executor
 
71 +
*/
 
72 +
template<Executor Ex>
 
73 +
class work_guard
 
74 +
{
 
75 +
    Ex ex_;
 
76 +
    bool owns_;
 
77 +

 
78 +
public:
 
79 +
    /** The underlying executor type. */
 
80 +
    using executor_type = Ex;
 
81 +

 
82 +
    /** Construct a work guard.
 
83 +

 
84 +
        Calls `ex.on_work_started()` to inform the executor that
 
85 +
        work is outstanding.
 
86 +

 
87 +
        @par Exception Safety
 
88 +
        No-throw guarantee.
 
89 +

 
90 +
        @par Postconditions
 
91 +
        @li `owns_work() == true`
 
92 +
        @li `executor() == ex`
 
93 +

 
94 +
        @param ex The executor to hold work on. Moved into the guard.
 
95 +
    */
 
96 +
    explicit
 
97 +
    work_guard(Ex ex) noexcept
 
98 +
        : ex_(std::move(ex))
 
99 +
        , owns_(true)
 
100 +
    {
 
101 +
        ex_.on_work_started();
 
102 +
    }
 
103 +

 
104 +
    /** Copy constructor.
 
105 +

 
106 +
        Creates a new work guard holding work on the same executor.
 
107 +
        Calls `on_work_started()` on the executor.
 
108 +

 
109 +
        @par Exception Safety
 
110 +
        No-throw guarantee.
 
111 +

 
112 +
        @par Postconditions
 
113 +
        @li `owns_work() == other.owns_work()`
 
114 +
        @li `executor() == other.executor()`
 
115 +

 
116 +
        @param other The work guard to copy from.
 
117 +
    */
 
118 +
    work_guard(work_guard const& other) noexcept
 
119 +
        : ex_(other.ex_)
 
120 +
        , owns_(other.owns_)
 
121 +
    {
 
122 +
        if(owns_)
 
123 +
            ex_.on_work_started();
 
124 +
    }
 
125 +

 
126 +
    /** Move constructor.
 
127 +

 
128 +
        Transfers work ownership from `other` to `*this`. Does not
 
129 +
        call `on_work_started()` or `on_work_finished()`.
 
130 +

 
131 +
        @par Exception Safety
 
132 +
        No-throw guarantee.
 
133 +

 
134 +
        @par Postconditions
 
135 +
        @li `owns_work()` equals the prior value of `other.owns_work()`
 
136 +
        @li `other.owns_work() == false`
 
137 +

 
138 +
        @param other The work guard to move from.
 
139 +
    */
 
140 +
    work_guard(work_guard&& other) noexcept
 
141 +
        : ex_(std::move(other.ex_))
 
142 +
        , owns_(other.owns_)
 
143 +
    {
 
144 +
        other.owns_ = false;
 
145 +
    }
 
146 +

 
147 +
    /** Destructor.
 
148 +

 
149 +
        If `owns_work()` is `true`, calls `on_work_finished()` on
 
150 +
        the executor.
 
151 +

 
152 +
        @par Exception Safety
 
153 +
        No-throw guarantee.
 
154 +
    */
 
155 +
    ~work_guard()
 
156 +
    {
 
157 +
        if(owns_)
 
158 +
            ex_.on_work_finished();
 
159 +
    }
 
160 +

 
161 +
    work_guard& operator=(work_guard const&) = delete;
 
162 +

 
163 +
    /** Return the underlying executor by reference.
 
164 +

 
165 +
        The reference remains valid for the lifetime of this guard,
 
166 +
        enabling callers to manage executor lifetime explicitly.
 
167 +

 
168 +
        @par Exception Safety
 
169 +
        No-throw guarantee.
 
170 +

 
171 +
        @return A reference to the stored executor.
 
172 +
    */
 
173 +
    executor_type const&
 
174 +
    executor() const noexcept
 
175 +
    {
 
176 +
        return ex_;
 
177 +
    }
 
178 +

 
179 +
    /** Return whether the guard owns work.
 
180 +

 
181 +
        @par Exception Safety
 
182 +
        No-throw guarantee.
 
183 +

 
184 +
        @return `true` if this guard will call `on_work_finished()`
 
185 +
            on destruction, `false` otherwise.
 
186 +
    */
 
187 +
    bool
 
188 +
    owns_work() const noexcept
 
189 +
    {
 
190 +
        return owns_;
 
191 +
    }
 
192 +

 
193 +
    /** Release ownership of the work.
 
194 +

 
195 +
        If `owns_work()` is `true`, calls `on_work_finished()` on
 
196 +
        the executor and sets ownership to `false`. Otherwise, has
 
197 +
        no effect.
 
198 +

 
199 +
        @par Exception Safety
 
200 +
        No-throw guarantee.
 
201 +

 
202 +
        @par Postconditions
 
203 +
        @li `owns_work() == false`
 
204 +
    */
 
205 +
    void
 
206 +
    reset() noexcept
 
207 +
    {
 
208 +
        if(owns_)
 
209 +
        {
 
210 +
            ex_.on_work_finished();
 
211 +
            owns_ = false;
 
212 +
        }
 
213 +
    }
 
214 +
};
 
215 +

 
216 +
//------------------------------------------------
 
217 +

 
218 +
/** Create a work guard from an executor.
 
219 +

 
220 +
    @par Exception Safety
 
221 +
    No-throw guarantee.
 
222 +

 
223 +
    @param ex The executor to create the guard for.
 
224 +

 
225 +
    @return A `work_guard` holding work on `ex`.
 
226 +

 
227 +
    @see work_guard
 
228 +
*/
 
229 +
template<Executor Ex>
 
230 +
work_guard<Ex>
 
231 +
make_work_guard(Ex ex)
 
232 +
{
 
233 +
    return work_guard<Ex>(std::move(ex));
 
234 +
}
 
235 +

 
236 +
} // capy
 
237 +
} // boost
 
238 +

 
239 +
#endif