1 | /* |
2 | * Copyright (c) Facebook, Inc. and its affiliates. |
3 | * |
4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
5 | * you may not use this file except in compliance with the License. |
6 | * You may obtain a copy of the License at |
7 | * |
8 | * http://www.apache.org/licenses/LICENSE-2.0 |
9 | * |
10 | * Unless required by applicable law or agreed to in writing, software |
11 | * distributed under the License is distributed on an "AS IS" BASIS, |
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 | * See the License for the specific language governing permissions and |
14 | * limitations under the License. |
15 | */ |
16 | |
17 | #pragma once |
18 | |
19 | #include <folly/CppAttributes.h> |
20 | #include <folly/Function.h> |
21 | |
22 | #include <atomic> |
23 | #include <memory> |
24 | #include <thread> |
25 | #include <type_traits> |
26 | |
27 | namespace folly { |
28 | |
29 | class CancellationCallback; |
30 | class CancellationSource; |
31 | struct OperationCancelled : public std::exception { |
32 | const char* what() const noexcept override { |
33 | return "coroutine operation cancelled" ; |
34 | } |
35 | }; |
36 | |
37 | namespace detail { |
38 | class CancellationState; |
39 | struct CancellationStateTokenDeleter { |
40 | void operator()(CancellationState*) noexcept; |
41 | }; |
42 | struct CancellationStateSourceDeleter { |
43 | void operator()(CancellationState*) noexcept; |
44 | }; |
45 | using CancellationStateTokenPtr = |
46 | std::unique_ptr<CancellationState, CancellationStateTokenDeleter>; |
47 | using CancellationStateSourcePtr = |
48 | std::unique_ptr<CancellationState, CancellationStateSourceDeleter>; |
49 | } // namespace detail |
50 | |
51 | // A CancellationToken is an object that can be passed into an function or |
52 | // operation that allows the caller to later request that the operation be |
53 | // cancelled. |
54 | // |
55 | // A CancellationToken object can be obtained by calling the .getToken() |
56 | // method on a CancellationSource or by copying another CancellationToken |
57 | // object. All CancellationToken objects obtained from the same original |
58 | // CancellationSource object all reference the same underlying cancellation |
59 | // state and will all be cancelled together. |
60 | // |
61 | // If your function needs to be cancellable but does not need to request |
62 | // cancellation then you should take a CancellationToken as a parameter. |
63 | // If your function needs to be able to request cancellation then you |
64 | // should instead take a CancellationSource as a parameter. |
65 | class CancellationToken { |
66 | public: |
67 | // Constructs to a token that can never be cancelled. |
68 | // |
69 | // Pass a default-constructed CancellationToken into an operation that |
70 | // you never intend to cancel. These objects are very cheap to create. |
71 | CancellationToken() noexcept = default; |
72 | |
73 | // Construct a copy of the token that shares the same underlying state. |
74 | CancellationToken(const CancellationToken& other) noexcept; |
75 | CancellationToken(CancellationToken&& other) noexcept; |
76 | |
77 | CancellationToken& operator=(const CancellationToken& other) noexcept; |
78 | CancellationToken& operator=(CancellationToken&& other) noexcept; |
79 | |
80 | // Query whether someone has called .requestCancellation() on an instance |
81 | // of CancellationSource object associated with this CancellationToken. |
82 | bool isCancellationRequested() const noexcept; |
83 | |
84 | // Query whether this CancellationToken can ever have cancellation requested |
85 | // on it. |
86 | // |
87 | // This will return false if the CancellationToken is not associated with a |
88 | // CancellationSource object. eg. because the CancellationToken was |
89 | // default-constructed, has been moved-from or because the last |
90 | // CancellationSource object associated with the underlying cancellation state |
91 | // has been destroyed and the operation has not yet been cancelled and so |
92 | // never will be. |
93 | // |
94 | // Implementations of operations may be able to take more efficient code-paths |
95 | // if they know they can never be cancelled. |
96 | bool canBeCancelled() const noexcept; |
97 | |
98 | void swap(CancellationToken& other) noexcept; |
99 | |
100 | friend bool operator==( |
101 | const CancellationToken& a, |
102 | const CancellationToken& b) noexcept; |
103 | |
104 | private: |
105 | friend class CancellationCallback; |
106 | friend class CancellationSource; |
107 | |
108 | explicit CancellationToken(detail::CancellationStateTokenPtr state) noexcept; |
109 | |
110 | detail::CancellationStateTokenPtr state_; |
111 | }; |
112 | |
113 | bool operator==( |
114 | const CancellationToken& a, |
115 | const CancellationToken& b) noexcept; |
116 | bool operator!=( |
117 | const CancellationToken& a, |
118 | const CancellationToken& b) noexcept; |
119 | |
120 | // A CancellationSource object provides the ability to request cancellation of |
121 | // operations that an associated CancellationToken was passed to. |
122 | // |
123 | // Example usage: |
124 | // CancellationSource cs; |
125 | // Future<void> f = startSomeOperation(cs.getToken()); |
126 | // |
127 | // // Later... |
128 | // cs.requestCancellation(); |
129 | class CancellationSource { |
130 | public: |
131 | // Construct to a new, independent cancellation source. |
132 | CancellationSource(); |
133 | |
134 | // Construct a new reference to the same underlying cancellation state. |
135 | // |
136 | // Either the original or the new copy can be used to request cancellation |
137 | // of associated work. |
138 | CancellationSource(const CancellationSource& other) noexcept; |
139 | |
140 | // This leaves 'other' in an empty state where 'requestCancellation()' is a |
141 | // no-op and 'canBeCancelled()' returns false. |
142 | CancellationSource(CancellationSource&& other) noexcept; |
143 | |
144 | CancellationSource& operator=(const CancellationSource& other) noexcept; |
145 | CancellationSource& operator=(CancellationSource&& other) noexcept; |
146 | |
147 | // Construct a CancellationSource that cannot be cancelled. |
148 | // |
149 | // This factory function can be used to obtain a CancellationSource that |
150 | // is equivalent to a moved-from CancellationSource object without needing |
151 | // to allocate any shared-state. |
152 | static CancellationSource invalid() noexcept; |
153 | |
154 | // Query if cancellation has already been requested on this CancellationSource |
155 | // or any other CancellationSource object copied from the same original |
156 | // CancellationSource object. |
157 | bool isCancellationRequested() const noexcept; |
158 | |
159 | // Query if cancellation can be requested through this CancellationSource |
160 | // object. This will only return false if the CancellationSource object has |
161 | // been moved-from. |
162 | bool canBeCancelled() const noexcept; |
163 | |
164 | // Obtain a CancellationToken linked to this CancellationSource. |
165 | // |
166 | // This token can be passed into cancellable operations to allow the caller |
167 | // to later request cancellation of that operation. |
168 | CancellationToken getToken() const noexcept; |
169 | |
170 | // Request cancellation of work associated with this CancellationSource. |
171 | // |
172 | // This will ensure subsequent calls to isCancellationRequested() on any |
173 | // CancellationSource or CancellationToken object associated with the same |
174 | // underlying cancellation state to return true. |
175 | // |
176 | // If this is the first call to requestCancellation() on any |
177 | // CancellationSource object with the same underlying state then this call |
178 | // will also execute the callbacks associated with any CancellationCallback |
179 | // objects that were constructed with an associated CancellationToken. |
180 | // |
181 | // Note that it is possible that another thread may be concurrently |
182 | // registering a callback with CancellationCallback. This method guarantees |
183 | // that either this thread will see the callback registration and will |
184 | // ensure that the callback is called, or the CancellationCallback constructor |
185 | // will see the cancellation-requested signal and will execute the callback |
186 | // inline inside the constructor. |
187 | // |
188 | // Returns the previous state of 'isCancellationRequested()'. i.e. |
189 | // - 'true' if cancellation had previously been requested. |
190 | // - 'false' if this was the first call to request cancellation. |
191 | bool requestCancellation() const noexcept; |
192 | |
193 | void swap(CancellationSource& other) noexcept; |
194 | |
195 | friend bool operator==( |
196 | const CancellationSource& a, |
197 | const CancellationSource& b) noexcept; |
198 | |
199 | private: |
200 | explicit CancellationSource( |
201 | detail::CancellationStateSourcePtr&& state) noexcept; |
202 | |
203 | detail::CancellationStateSourcePtr state_; |
204 | }; |
205 | |
206 | bool operator==( |
207 | const CancellationSource& a, |
208 | const CancellationSource& b) noexcept; |
209 | bool operator!=( |
210 | const CancellationSource& a, |
211 | const CancellationSource& b) noexcept; |
212 | |
213 | class CancellationCallback { |
214 | using VoidFunction = folly::Function<void()>; |
215 | |
216 | public: |
217 | // Constructing a CancellationCallback object registers the callback |
218 | // with the specified CancellationToken such that the callback will be |
219 | // executed if the corresponding CancellationSource object has the |
220 | // requestCancellation() method called on it. |
221 | // |
222 | // If the CancellationToken object already had cancellation requested |
223 | // then the callback will be executed inline on the current thread before |
224 | // the constructor returns. Otherwise, the callback will be executed on |
225 | // in the execution context of the first thread to call requestCancellation() |
226 | // on a corresponding CancellationSource. |
227 | // |
228 | // The callback object must not throw any unhandled exceptions. Doing so |
229 | // will result in the program terminating via std::terminate(). |
230 | template < |
231 | typename Callable, |
232 | std::enable_if_t< |
233 | std::is_constructible<VoidFunction, Callable>::value, |
234 | int> = 0> |
235 | CancellationCallback(CancellationToken&& ct, Callable&& callable); |
236 | template < |
237 | typename Callable, |
238 | std::enable_if_t< |
239 | std::is_constructible<VoidFunction, Callable>::value, |
240 | int> = 0> |
241 | CancellationCallback(const CancellationToken& ct, Callable&& callable); |
242 | |
243 | // Deregisters the callback from the CancellationToken. |
244 | // |
245 | // If cancellation has been requested concurrently on another thread and the |
246 | // callback is currently executing then the destructor will block until after |
247 | // the callback has returned (otherwise it might be left with a dangling |
248 | // reference). |
249 | // |
250 | // You should generally try to implement your callback functions to be lock |
251 | // free to avoid deadlocks between the callback executing and the |
252 | // CancellationCallback destructor trying to deregister the callback. |
253 | // |
254 | // If the callback has not started executing yet then the callback will be |
255 | // deregistered from the CancellationToken before the destructor completes. |
256 | // |
257 | // Once the destructor returns you can be guaranteed that the callback will |
258 | // not be called by a subsequent call to 'requestCancellation()' on a |
259 | // CancellationSource associated with the CancellationToken passed to the |
260 | // constructor. |
261 | ~CancellationCallback(); |
262 | |
263 | // Not copyable/movable |
264 | CancellationCallback(const CancellationCallback&) = delete; |
265 | CancellationCallback(CancellationCallback&&) = delete; |
266 | CancellationCallback& operator=(const CancellationCallback&) = delete; |
267 | CancellationCallback& operator=(CancellationCallback&&) = delete; |
268 | |
269 | private: |
270 | friend class detail::CancellationState; |
271 | |
272 | void invokeCallback() noexcept; |
273 | |
274 | CancellationCallback* next_; |
275 | |
276 | // Pointer to the pointer that points to this node in the linked list. |
277 | // This could be the 'next_' of a previous CancellationCallback or could |
278 | // be the 'head_' pointer of the CancellationState. |
279 | // If this node is inserted in the list then this will be non-null. |
280 | CancellationCallback** prevNext_; |
281 | |
282 | detail::CancellationState* state_; |
283 | VoidFunction callback_; |
284 | |
285 | // Pointer to a flag stored on the stack of the caller to invokeCallback() |
286 | // that is used to indicate to the caller of invokeCallback() that the |
287 | // destructor has run and it is no longer valid to access the callback |
288 | // object. |
289 | bool* destructorHasRunInsideCallback_; |
290 | |
291 | // Flag used to signal that the callback has completed executing on another |
292 | // thread and it is now safe to exit the destructor. |
293 | std::atomic<bool> callbackCompleted_; |
294 | }; |
295 | |
296 | } // namespace folly |
297 | |
298 | #include <folly/CancellationToken-inl.h> |
299 | |