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