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
27namespace folly {
28
29class CancellationCallback;
30class CancellationSource;
31struct OperationCancelled : public std::exception {
32 const char* what() const noexcept override {
33 return "coroutine operation cancelled";
34 }
35};
36
37namespace detail {
38class CancellationState;
39struct CancellationStateTokenDeleter {
40 void operator()(CancellationState*) noexcept;
41};
42struct CancellationStateSourceDeleter {
43 void operator()(CancellationState*) noexcept;
44};
45using CancellationStateTokenPtr =
46 std::unique_ptr<CancellationState, CancellationStateTokenDeleter>;
47using 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.
65class 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
113bool operator==(
114 const CancellationToken& a,
115 const CancellationToken& b) noexcept;
116bool 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();
129class 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
206bool operator==(
207 const CancellationSource& a,
208 const CancellationSource& b) noexcept;
209bool operator!=(
210 const CancellationSource& a,
211 const CancellationSource& b) noexcept;
212
213class 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