VLink  2.0.0
A high-performance communication middleware
cancellation.h
Go to the documentation of this file.
1 /*
2  * Copyright (C) 2026 by Thun Lu. All rights reserved.
3  * Author: Thun Lu <thun.lu@zohomail.cn>
4  * Repo: https://github.com/thun-res/vlink
5  * _ __ __ _ __
6  * | | / / / / (_) ____ / /__
7  * | | / / / / / / / __ \ / //_/
8  * | |/ / / /___ / / / / / / / ,<
9  * |___/ /_____/ /_/ /_/ /_/ /_/|_|
10  *
11  * Licensed under the Apache License, Version 2.0 (the "License");
12  * you may not use this file except in compliance with the License.
13  * You may obtain a copy of the License at
14  *
15  * http://www.apache.org/licenses/LICENSE-2.0
16  *
17  * Unless required by applicable law or agreed to in writing, software
18  * distributed under the License is distributed on an "AS IS" BASIS,
19  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20  * See the License for the specific language governing permissions and
21  * limitations under the License.
22  */
23 
24 /**
25  * @file cancellation.h
26  * @brief Cooperative one-shot cancellation primitives shared by VLink async building blocks.
27  *
28  * @details
29  * Cancellation is split into a writable @c CancellationSource owned by the work producer, one
30  * or more read-only @c CancellationToken observers held by workers, and per-callback
31  * @c CancellationRegistration handles used to unsubscribe pending callbacks. All three share
32  * a refcounted internal state allocated on the producer side.
33  *
34  * @par Role table
35  *
36  * | Type | Role | Copy / Move |
37  * | ----------------------------- | ------------------------------------- | ------------------- |
38  * | @c CancellationSource | Mutator; signals cancellation | Implicit (refcount) |
39  * | @c CancellationToken | Observer; poll / throw / subscribe | Copyable |
40  * | @c CancellationRegistration | RAII slot for one subscribed callback | Move-only |
41  *
42  * @par State diagram
43  *
44  * @verbatim
45  * +------------+ request_cancel() +---------------+ no-ops on further calls
46  * | active | --------------------> | cancelled | -------------------------+
47  * | (default) | | (terminal) | |
48  * +------------+ +---------------+ |
49  * ^ ^ ^ |
50  * | | is_cancellation_requested()=false |
51  * | | throw_if_cancellation_requested()=no-op |
52  * | | v
53  * | +-- new tokens via token() share the same state ------------------ read-only
54  * |
55  * +------ register_callback() inserts a slot; fires synchronously on transition
56  * @endverbatim
57  *
58  * Cancellation is one-shot. After the first successful @c request_cancel the state is
59  * terminal; subsequent requests return @c false and never re-invoke callbacks. The cancelled
60  * type re-exported as @c vlink::Exception::OperationCancelled is the canonical thrown type at
61  * structured cancellation points.
62  *
63  * @par Lock ordering
64  * Internal mutexes are released before user callbacks fire, so callbacks may freely cancel
65  * sibling sources or register more callbacks without self-deadlock.
66  *
67  * @par Example
68  * @code
69  * vlink::CancellationSource source;
70  * auto token = source.token();
71  *
72  * auto reg = token.register_callback([] {
73  * // Runs once on the cancelling thread, or inline if already cancelled.
74  * });
75  *
76  * while (!token.is_cancellation_requested()) {
77  * token.throw_if_cancellation_requested();
78  * run_work_unit();
79  * }
80  *
81  * source.request_cancel();
82  * @endcode
83  */
84 
85 #pragma once
86 
87 #include <cstddef>
88 #include <memory>
89 
90 #include "./functional.h"
91 #include "./macros.h"
92 
93 namespace vlink {
94 
95 class CancellationToken;
96 
97 /**
98  * @class CancellationRegistration
99  * @brief Move-only RAII handle that represents one subscribed cancellation callback.
100  *
101  * @details
102  * Each registration owns exactly one slot inside the source's state. Destroying or @c reset-ing
103  * the handle detaches the callback if it has not yet fired; once the source has cancelled the
104  * slot is consumed automatically and the handle becomes a benign no-op sink. The type is
105  * deliberately move-only to keep slot identity unique.
106  */
108  public:
109  /**
110  * @brief Constructs an empty registration that owns no callback slot.
111  */
113 
114  /**
115  * @brief Destructor; detaches the owned callback when cancellation has not yet fired.
116  */
118 
119  /**
120  * @brief Move constructor; transfers slot ownership and leaves @p other empty.
121  *
122  * @param other Source registration emptied by the move.
123  */
125 
126  /**
127  * @brief Move assignment; detaches any current slot then adopts @p other 's slot.
128  *
129  * @param other Source registration emptied by the move.
130  * @return Reference to @c *this.
131  */
133 
134  /**
135  * @brief Detaches the owned callback and resets to the empty state.
136  *
137  * @details
138  * Idempotent. Safe to call on an empty or already-reset registration.
139  */
140  void reset() noexcept;
141 
142  /**
143  * @brief Reports whether the registration still owns an attached callback slot.
144  *
145  * @return @c true when the slot is live and the source has not yet cancelled.
146  */
147  [[nodiscard]] bool valid() const noexcept;
148 
149  private:
150  struct State;
151 
152  CancellationRegistration(std::weak_ptr<State> state, size_t id) noexcept;
153 
154  std::weak_ptr<State> state_;
155  size_t id_{0};
156 
157  friend class CancellationToken;
158  friend class CancellationSource;
159 
161 };
162 
163 /**
164  * @class CancellationToken
165  * @brief Lightweight observer of a @c CancellationSource shared across worker threads.
166  *
167  * @details
168  * Tokens are cheap to copy and safe to hand to any thread; many tokens may observe the same
169  * source concurrently. A default-constructed token is invalid -- it never reports cancellation
170  * and never accepts callbacks. Workers typically poll @c is_cancellation_requested at safe
171  * points, call @c throw_if_cancellation_requested at structured points, or install a one-shot
172  * callback via @c register_callback.
173  */
175  public:
176  /**
177  * @brief Constructs an invalid token not bound to any source.
178  */
179  CancellationToken() noexcept;
180 
181  /**
182  * @brief Reports whether the token observes a live cancellation source.
183  *
184  * @return @c true when the token was minted from a @c CancellationSource.
185  */
186  [[nodiscard]] bool valid() const noexcept;
187 
188  /**
189  * @brief Returns @c true once the bound source has been cancelled.
190  *
191  * @return @c true after the source's @c request_cancel has succeeded.
192  */
193  [[nodiscard]] bool is_cancellation_requested() const noexcept;
194 
195  /**
196  * @brief Subscribes a one-shot callback to fire when the source is cancelled.
197  *
198  * @details
199  * When cancellation has not yet been requested the callback is stored in the source and fires
200  * once on the thread that later calls @c CancellationSource::request_cancel. When
201  * cancellation already happened the callback runs synchronously inside this call and the
202  * returned registration is empty. Exceptions escaping the callback are caught and logged
203  * via @c CLOG_E; they never propagate to the registering or cancelling thread.
204  *
205  * @param callback Move-only nullary functor; an empty callback is silently ignored.
206  * @return RAII registration for the subscription, or an empty handle when cancellation had
207  * already fired or the token / callback was invalid.
208  */
209  CancellationRegistration register_callback(MoveFunction<void()>&& callback) const;
210 
211  /**
212  * @brief Throws @c vlink::Exception::OperationCancelled when cancellation has been requested.
213  *
214  * @throws vlink::Exception::OperationCancelled if @c is_cancellation_requested() is @c true.
215  */
216  void throw_if_cancellation_requested() const;
217 
218  private:
219  using State = CancellationRegistration::State;
220 
221  explicit CancellationToken(std::shared_ptr<State> state) noexcept;
222 
223  std::shared_ptr<State> state_;
224 
225  friend class CancellationSource;
226 };
227 
228 /**
229  * @class CancellationSource
230  * @brief Mutator that mints observer tokens and signals one-shot cancellation.
231  *
232  * @details
233  * Holds a refcounted handle to the cancellation state; copies and moves share that state, so
234  * any copy can mint tokens or request cancellation against the same one-shot transition.
235  * @c token returns a cheap observer; @c request_cancel performs the transition exactly once and
236  * fires every subscribed callback.
237  */
239  public:
240  /**
241  * @brief Constructs an uncancelled source with no subscribed callbacks.
242  */
244 
245  /**
246  * @brief Returns a fresh observer token bound to this source.
247  *
248  * @return Valid @c CancellationToken sharing the source's state.
249  */
250  [[nodiscard]] CancellationToken token() const noexcept;
251 
252  /**
253  * @brief Returns @c true once @c request_cancel has succeeded on this source.
254  *
255  * @return @c true after the one-shot transition has completed.
256  */
257  [[nodiscard]] bool is_cancellation_requested() const noexcept;
258 
259  /**
260  * @brief Performs the one-shot active -> cancelled transition and fires registered callbacks.
261  *
262  * @details
263  * Acquires the internal state mutex, swaps the cancellation flag, releases the mutex, then
264  * runs every callback that was attached at the moment of the transition. Exceptions thrown
265  * by callbacks are caught and logged.
266  *
267  * @return @c true when this call performed the transition; @c false when a previous call had
268  * already cancelled the source.
269  */
270  bool request_cancel() const;
271 
272  private:
273  using State = CancellationRegistration::State;
274 
275  std::shared_ptr<State> state_;
276 };
277 
278 } // namespace vlink
Pool-backed type-erased callables: copyable vlink::Function and move-only vlink::MoveFunction.
Cross-platform macros for visibility, branch hints, copy prevention, singletons and string helpers.
#define VLINK_EXPORT
Definition: macros.h:81
#define VLINK_DISALLOW_COPY_AND_ASSIGN(classname)
Deletes the copy constructor and copy-assignment operator of classname.
Definition: macros.h:174