GCC Code Coverage Report


Directory: ./
File: libs/capy/include/boost/capy/ex/stop_token.hpp
Date: 2026-01-21 22:59:20
Exec Total Coverage
Lines: 51 61 83.6%
Functions: 14 31 45.2%
Branches: 20 40 50.0%

Line Branch Exec Source
1 //
2 // Copyright (c) 2026 Cinar Gursoy
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_EX_STOP_TOKEN_HPP
11 #define BOOST_CAPY_EX_STOP_TOKEN_HPP
12
13 #include <boost/capy/detail/config.hpp>
14
15 #include <atomic>
16 #include <functional>
17 #include <memory>
18 #include <mutex>
19 #include <vector>
20
21 namespace boost {
22 namespace capy {
23
24 namespace detail {
25
26 struct stop_state
27 {
28 std::atomic<bool> stop_requested_{false};
29 std::mutex mutex_;
30 std::vector<std::function<void()>> callbacks_;
31
32 15 bool request_stop() noexcept
33 {
34 15 bool expected = false;
35
2/2
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 11 times.
15 if (!stop_requested_.compare_exchange_strong(expected, true))
36 4 return false; // Already requested
37
38 // Invoke all callbacks
39 11 std::vector<std::function<void()>> cbs;
40 {
41 11 std::lock_guard<std::mutex> lock(mutex_);
42 11 cbs.swap(callbacks_);
43 11 }
44
2/2
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 11 times.
12 for (auto& cb : cbs)
45 {
46 try
47 {
48
1/1
✓ Branch 1 taken 1 times.
1 cb();
49 }
50 catch (...)
51 {
52 // Callbacks should not throw, but ignore if they do
53 }
54 }
55 11 return true;
56 11 }
57
58 4 void register_callback(std::function<void()> cb)
59 {
60 // If already stopped, invoke immediately
61
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 3 times.
4 if (stop_requested_.load(std::memory_order_acquire))
62 {
63 try
64 {
65
1/1
✓ Branch 1 taken 1 times.
1 cb();
66 }
67 catch (...)
68 {
69 // Ignore exceptions from callbacks
70 }
71 1 return;
72 }
73
74 // Register for later invocation
75
1/1
✓ Branch 1 taken 3 times.
3 std::lock_guard<std::mutex> lock(mutex_);
76
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
3 if (stop_requested_.load(std::memory_order_acquire))
77 {
78 // Stop was requested while we were acquiring lock
79 try
80 {
81 cb();
82 }
83 catch (...)
84 {
85 // Ignore exceptions
86 }
87 }
88 else
89 {
90
1/1
✓ Branch 2 taken 3 times.
3 callbacks_.push_back(std::move(cb));
91 }
92 3 }
93
94 };
95
96 } // namespace detail
97
98 /** A token that can be checked for cancellation requests.
99
100 A `stop_token` can be checked to see if a stop request has been made.
101 Multiple tokens can share the same stop state, allowing coordinated
102 cancellation across multiple operations.
103
104 @see stop_source, stop_callback
105 */
106 class stop_token
107 {
108 std::shared_ptr<detail::stop_state> state_;
109
110 28 explicit stop_token(std::shared_ptr<detail::stop_state> state) noexcept
111 28 : state_(std::move(state))
112 {
113 28 }
114
115 friend class stop_source;
116 template<class Callback>
117 friend class stop_callback;
118
119 public:
120 /** Default constructor.
121
122 Creates a stop token that cannot be stopped.
123 `stop_possible()` returns `false` for default-constructed tokens.
124 */
125 326 stop_token() noexcept = default;
126
127 /** Copy constructor.
128
129 Creates a copy that shares the same stop state.
130 */
131 372 stop_token(stop_token const&) noexcept = default;
132
133 /** Copy assignment.
134
135 Assigns the stop state from another token.
136 */
137 222 stop_token& operator=(stop_token const&) noexcept = default;
138
139 /** Move constructor.
140
141 Moves the stop state from another token.
142 */
143 63 stop_token(stop_token&&) noexcept = default;
144
145 /** Move assignment.
146
147 Moves the stop state from another token.
148 */
149 stop_token& operator=(stop_token&&) noexcept = default;
150
151 /** Check if a stop request has been made.
152
153 @return `true` if a stop request has been made, `false` otherwise.
154 */
155 110 bool stop_requested() const noexcept
156 {
157
2/2
✓ Branch 1 taken 102 times.
✓ Branch 2 taken 8 times.
110 if (!state_)
158 102 return false;
159 8 return state_->stop_requested_.load(std::memory_order_acquire);
160 }
161
162 /** Check if this token can receive stop requests.
163
164 @return `true` if this token is associated with a stop source,
165 `false` for default-constructed tokens.
166 */
167 34 bool stop_possible() const noexcept
168 {
169 34 return state_ != nullptr;
170 }
171
172 /** Swap with another stop token.
173
174 @param other The token to swap with.
175 */
176 void swap(stop_token& other) noexcept
177 {
178 state_.swap(other.state_);
179 }
180 };
181
182 /** Swap two stop tokens.
183
184 @param lhs First token.
185 @param rhs Second token.
186 */
187 inline void swap(stop_token& lhs, stop_token& rhs) noexcept
188 {
189 lhs.swap(rhs);
190 }
191
192 //----------------------------------------------------------
193 //
194 // stop_source
195 //
196 //----------------------------------------------------------
197
198 /** A source for stop tokens that can request cancellation.
199
200 A `stop_source` creates `stop_token` objects and can request
201 cancellation on all tokens it has created. Multiple tokens from
202 the same source share the same stop state.
203
204 @par Thread Safety
205 Distinct objects: Safe.
206 Shared objects: Safe.
207
208 @par Example
209 @code
210 capy::stop_source source;
211 auto token = source.get_token();
212
213 // Start operation with token
214 start_operation(token);
215
216 // Later, cancel the operation
217 source.request_stop();
218 @endcode
219
220 @see stop_token, stop_callback
221 */
222 class stop_source
223 {
224 std::shared_ptr<detail::stop_state> state_;
225
226 public:
227 /** Default constructor.
228
229 Creates a stop source that can create stop tokens.
230 */
231 29 stop_source()
232 29 : state_(std::make_shared<detail::stop_state>())
233 {
234 29 }
235
236 /** Copy constructor.
237
238 Creates a new stop source that shares the same stop state
239 as the source being copied. Both sources can request stop
240 and affect the same tokens.
241 */
242 stop_source(stop_source const&) = default;
243
244 /** Copy assignment.
245
246 Assigns the stop state from another source.
247 */
248 stop_source& operator=(stop_source const&) = default;
249
250 /** Move constructor.
251
252 Moves the stop state from another source.
253 */
254 stop_source(stop_source&&) noexcept = default;
255
256 /** Move assignment.
257
258 Moves the stop state from another source.
259 */
260 stop_source& operator=(stop_source&&) noexcept = default;
261
262 /** Get a stop token associated with this source.
263
264 All tokens returned from the same source share the same
265 stop state. Requesting stop on the source affects all
266 tokens created from it.
267
268 @return A stop token that can be checked for cancellation.
269 */
270 28 stop_token get_token() const noexcept
271 {
272 28 return stop_token(state_);
273 }
274
275 /** Request stop on all tokens created from this source.
276
277 This function is idempotent—calling it multiple times
278 has the same effect as calling it once. After the first
279 call, all subsequent calls return `false`.
280
281 @return `true` if this was the first stop request,
282 `false` if stop was already requested.
283 */
284 15 bool request_stop() noexcept
285 {
286
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 15 times.
15 if (!state_)
287 return false;
288 15 return state_->request_stop();
289 }
290
291 /** Check if stop has been requested.
292
293 @return `true` if `request_stop()` has been called,
294 `false` otherwise.
295 */
296 bool stop_requested() const noexcept
297 {
298 if (!state_)
299 return false;
300 return state_->stop_requested_.load(std::memory_order_acquire);
301 }
302 };
303
304 //----------------------------------------------------------
305 //
306 // stop_callback
307 //
308 //----------------------------------------------------------
309
310 /** Register a callback to be invoked when stop is requested.
311
312 The callback is invoked when the associated stop token receives
313 a stop request. If stop has already been requested when the
314 callback is constructed, it is invoked immediately.
315
316 The callback is automatically unregistered when the `stop_callback`
317 object is destroyed.
318
319 @tparam Callback The type of the callback function object.
320 Must be callable with no arguments.
321
322 @par Thread Safety
323 Distinct objects: Safe.
324 Shared objects: Unsafe.
325
326 @par Example
327 @code
328 capy::stop_source source;
329 auto token = source.get_token();
330
331 // Register callback
332 capy::stop_callback cb(token, [] {
333 std::cout << "Stop requested!\n";
334 });
335
336 // Requesting stop will invoke the callback
337 source.request_stop();
338 @endcode
339
340 @see stop_token, stop_source
341 */
342 template<class Callback>
343 class stop_callback
344 {
345 std::shared_ptr<detail::stop_state> state_;
346 std::shared_ptr<Callback> callback_;
347
348 public:
349 /** Construct a stop callback.
350
351 Registers the callback to be invoked when the stop token
352 receives a stop request. If stop has already been requested,
353 the callback is invoked immediately during construction.
354
355 @param st The stop token to monitor.
356 @param cb The callback to invoke when stop is requested.
357 */
358 8 explicit stop_callback(stop_token const& st, Callback&& cb)
359 8 : state_(st.state_)
360
1/1
✓ Branch 2 taken 4 times.
8 , callback_(std::make_shared<Callback>(std::forward<Callback>(cb)))
361 {
362
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
8 if (!state_)
363 return; // No-op token
364
365 // Register callback with shared_ptr to keep it alive
366 8 auto weak_cb = std::weak_ptr<Callback>(callback_);
367
2/2
✓ Branch 3 taken 4 times.
✓ Branch 6 taken 4 times.
12 state_->register_callback([weak_cb] {
368
2/18
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
✗ Branch 22 not taken.
✗ Branch 23 not taken.
✗ Branch 27 not taken.
✗ Branch 28 not taken.
✗ Branch 32 not taken.
✗ Branch 33 not taken.
✗ Branch 37 not taken.
✗ Branch 38 not taken.
✓ Branch 42 taken 1 times.
✓ Branch 43 taken 1 times.
3 if (auto cb = weak_cb.lock())
369 {
370 1 (*cb)();
371 }
372 });
373 8 }
374
375 /** Destructor.
376
377 The callback may still be invoked after destruction if
378 stop is requested, as long as the callback object itself
379 remains valid (via shared_ptr).
380 */
381 8 ~stop_callback() = default;
382
383 // Non-copyable, non-movable
384 stop_callback(stop_callback const&) = delete;
385 stop_callback& operator=(stop_callback const&) = delete;
386 stop_callback(stop_callback&&) = delete;
387 stop_callback& operator=(stop_callback&&) = delete;
388 };
389
390 } // namespace capy
391 } // namespace boost
392
393 #endif
394