Line data Source code
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 2024 : work_guard(Ex ex) noexcept
98 2024 : : ex_(std::move(ex))
99 2024 : , owns_(true)
100 : {
101 2024 : ex_.on_work_started();
102 2024 : }
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 2 : work_guard(work_guard const& other) noexcept
119 2 : : ex_(other.ex_)
120 2 : , owns_(other.owns_)
121 : {
122 2 : if(owns_)
123 1 : ex_.on_work_started();
124 2 : }
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 1 : work_guard(work_guard&& other) noexcept
141 1 : : ex_(std::move(other.ex_))
142 1 : , owns_(other.owns_)
143 : {
144 1 : other.owns_ = false;
145 1 : }
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 2027 : ~work_guard()
156 : {
157 2027 : if(owns_)
158 2022 : ex_.on_work_finished();
159 2027 : }
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 4035 : executor() const noexcept
175 : {
176 4035 : 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 12 : owns_work() const noexcept
189 : {
190 12 : 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 4 : reset() noexcept
207 : {
208 4 : if(owns_)
209 : {
210 3 : ex_.on_work_finished();
211 3 : owns_ = false;
212 : }
213 4 : }
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 1 : make_work_guard(Ex ex)
232 : {
233 1 : return work_guard<Ex>(std::move(ex));
234 : }
235 :
236 : } // capy
237 : } // boost
238 :
239 : #endif
|