Line data Source code
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 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 12 : for (auto& cb : cbs)
45 : {
46 : try
47 : {
48 1 : cb();
49 : }
50 0 : catch (...)
51 : {
52 : // Callbacks should not throw, but ignore if they do
53 0 : }
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 4 : if (stop_requested_.load(std::memory_order_acquire))
62 : {
63 : try
64 : {
65 1 : cb();
66 : }
67 0 : catch (...)
68 : {
69 : // Ignore exceptions from callbacks
70 0 : }
71 1 : return;
72 : }
73 :
74 : // Register for later invocation
75 3 : std::lock_guard<std::mutex> lock(mutex_);
76 3 : if (stop_requested_.load(std::memory_order_acquire))
77 : {
78 : // Stop was requested while we were acquiring lock
79 : try
80 : {
81 0 : cb();
82 : }
83 0 : catch (...)
84 : {
85 : // Ignore exceptions
86 0 : }
87 : }
88 : else
89 : {
90 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 0 : 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 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 15 : if (!state_)
287 0 : 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 4 : explicit stop_callback(stop_token const& st, Callback&& cb)
359 4 : : state_(st.state_)
360 4 : , callback_(std::make_shared<Callback>(std::forward<Callback>(cb)))
361 : {
362 4 : if (!state_)
363 0 : return; // No-op token
364 :
365 : // Register callback with shared_ptr to keep it alive
366 4 : auto weak_cb = std::weak_ptr<Callback>(callback_);
367 8 : state_->register_callback([weak_cb] {
368 3 : if (auto cb = weak_cb.lock())
369 : {
370 1 : (*cb)();
371 : }
372 : });
373 4 : }
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 4 : ~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
|