]>
git.proxmox.com Git - ceph.git/blob - ceph/src/rocksdb/third-party/folly/folly/synchronization/ParkingLot.h
1 // Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
2 // This source code is licensed under both the GPLv2 (found in the
3 // COPYING file in the root directory) and Apache 2.0 License
4 // (found in the LICENSE.Apache file in the root directory).
9 #include <condition_variable>
12 #include <folly/hash/Hash.h>
13 #include <folly/Indestructible.h>
14 #include <folly/Unit.h>
18 namespace parking_lot_detail
{
22 const uint64_t lotid_
;
23 WaitNodeBase
* next_
{nullptr};
24 WaitNodeBase
* prev_
{nullptr};
26 // tricky: hold both bucket and node mutex to write, either to read
29 std::condition_variable cond_
;
31 WaitNodeBase(uint64_t key
, uint64_t lotid
)
32 : key_(key
), lotid_(lotid
), signaled_(false) {}
34 template <typename Clock
, typename Duration
>
35 std::cv_status
wait(std::chrono::time_point
<Clock
, Duration
> deadline
) {
36 std::cv_status status
= std::cv_status::no_timeout
;
37 std::unique_lock
<std::mutex
> nodeLock(mutex_
);
38 while (!signaled_
&& status
!= std::cv_status::timeout
) {
39 if (deadline
!= std::chrono::time_point
<Clock
, Duration
>::max()) {
40 status
= cond_
.wait_until(nodeLock
, deadline
);
49 std::lock_guard
<std::mutex
> nodeLock(mutex_
);
59 extern std::atomic
<uint64_t> idallocator
;
61 // Our emulated futex uses 4096 lists of wait nodes. There are two levels
62 // of locking: a per-list mutex that controls access to the list and a
63 // per-node mutex, condvar, and bool that are used for the actual wakeups.
64 // The per-node mutex allows us to do precise wakeups without thundering
70 std::atomic
<uint64_t> count_
;
72 static Bucket
& bucketFor(uint64_t key
);
74 void push_back(WaitNodeBase
* node
) {
86 void erase(WaitNodeBase
* node
) {
87 assert(count_
.load(std::memory_order_relaxed
) >= 1);
88 if (head_
== node
&& tail_
== node
) {
89 assert(node
->prev_
== nullptr);
90 assert(node
->next_
== nullptr);
93 } else if (head_
== node
) {
94 assert(node
->prev_
== nullptr);
97 head_
->prev_
= nullptr;
98 } else if (tail_
== node
) {
99 assert(node
->next_
== nullptr);
102 tail_
->next_
= nullptr;
106 node
->next_
->prev_
= node
->prev_
;
107 node
->prev_
->next_
= node
->next_
;
109 count_
.fetch_sub(1, std::memory_order_relaxed
);
113 } // namespace parking_lot_detail
115 enum class UnparkControl
{
122 enum class ParkResult
{
129 * ParkingLot provides an interface that is similar to Linux's futex
130 * system call, but with additional functionality. It is implemented
131 * in a portable way on top of std::mutex and std::condition_variable.
133 * Additional reading:
134 * https://webkit.org/blog/6161/locking-in-webkit/
135 * https://github.com/WebKit/webkit/blob/master/Source/WTF/wtf/ParkingLot.h
136 * https://locklessinc.com/articles/futex_cheat_sheet/
138 * The main difference from futex is that park/unpark take lambdas,
139 * such that nearly anything can be done while holding the bucket
140 * lock. Unpark() lambda can also be used to wake up any number of
143 * ParkingLot is templated on the data type, however, all ParkingLot
144 * implementations are backed by a single static array of buckets to
145 * avoid large memory overhead. Lambdas will only ever be called on
146 * the specific ParkingLot's nodes.
148 template <typename Data
= Unit
>
150 const uint64_t lotid_
;
151 ParkingLot(const ParkingLot
&) = delete;
153 struct WaitNode
: public parking_lot_detail::WaitNodeBase
{
156 template <typename D
>
157 WaitNode(uint64_t key
, uint64_t lotid
, D
&& data
)
158 : WaitNodeBase(key
, lotid
), data_(std::forward
<D
>(data
)) {}
162 ParkingLot() : lotid_(parking_lot_detail::idallocator
++) {}
166 * Key is almost always the address of a variable.
168 * ToPark runs while holding the bucket lock: usually this
169 * is a check to see if we can sleep, by checking waiter bits.
171 * PreWait is usually used to implement condition variable like
172 * things, such that you can unlock the condition variable's lock at
173 * the appropriate time.
175 template <typename Key
, typename D
, typename ToPark
, typename PreWait
>
176 ParkResult
park(const Key key
, D
&& data
, ToPark
&& toPark
, PreWait
&& preWait
) {
179 std::forward
<D
>(data
),
180 std::forward
<ToPark
>(toPark
),
181 std::forward
<PreWait
>(preWait
),
182 std::chrono::steady_clock::time_point::max());
192 ParkResult
park_until(
197 std::chrono::time_point
<Clock
, Duration
> deadline
);
211 std::chrono::duration
<Rep
, Period
>& timeout
) {
214 std::forward
<D
>(data
),
215 std::forward
<ToPark
>(toPark
),
216 std::forward
<PreWait
>(preWait
),
217 timeout
+ std::chrono::steady_clock::now());
223 * Key is the same uniqueaddress used in park(), and is used as a
224 * hash key for lookup of waiters.
226 * Unparker is a function that is given the Data parameter, and
227 * returns an UnparkControl. The Remove* results will remove and
228 * wake the waiter, the Ignore/Stop results will not, while stopping
229 * or continuing iteration of the waiter list.
231 template <typename Key
, typename Unparker
>
232 void unpark(const Key key
, Unparker
&& func
);
235 template <typename Data
>
243 ParkResult ParkingLot
<Data
>::park_until(
248 std::chrono::time_point
<Clock
, Duration
> deadline
) {
249 auto key
= hash::twang_mix64(uint64_t(bits
));
250 auto& bucket
= parking_lot_detail::Bucket::bucketFor(key
);
251 WaitNode
node(key
, lotid_
, std::forward
<D
>(data
));
254 // A: Must be seq_cst. Matches B.
255 bucket
.count_
.fetch_add(1, std::memory_order_seq_cst
);
257 std::unique_lock
<std::mutex
> bucketLock(bucket
.mutex_
);
259 if (!std::forward
<ToPark
>(toPark
)()) {
261 bucket
.count_
.fetch_sub(1, std::memory_order_relaxed
);
262 return ParkResult::Skip
;
265 bucket
.push_back(&node
);
266 } // bucketLock scope
268 std::forward
<PreWait
>(preWait
)();
270 auto status
= node
.wait(deadline
);
272 if (status
== std::cv_status::timeout
) {
273 // it's not really a timeout until we unlink the unsignaled node
274 std::lock_guard
<std::mutex
> bucketLock(bucket
.mutex_
);
275 if (!node
.signaled()) {
277 return ParkResult::Timeout
;
281 return ParkResult::Unpark
;
284 template <typename Data
>
285 template <typename Key
, typename Func
>
286 void ParkingLot
<Data
>::unpark(const Key bits
, Func
&& func
) {
287 auto key
= hash::twang_mix64(uint64_t(bits
));
288 auto& bucket
= parking_lot_detail::Bucket::bucketFor(key
);
289 // B: Must be seq_cst. Matches A. If true, A *must* see in seq_cst
290 // order any atomic updates in toPark() (and matching updates that
291 // happen before unpark is called)
292 if (bucket
.count_
.load(std::memory_order_seq_cst
) == 0) {
296 std::lock_guard
<std::mutex
> bucketLock(bucket
.mutex_
);
298 for (auto iter
= bucket
.head_
; iter
!= nullptr;) {
299 auto node
= static_cast<WaitNode
*>(iter
);
301 if (node
->key_
== key
&& node
->lotid_
== lotid_
) {
302 auto result
= std::forward
<Func
>(func
)(node
->data_
);
303 if (result
== UnparkControl::RemoveBreak
||
304 result
== UnparkControl::RemoveContinue
) {
305 // we unlink, but waiter destroys the node
310 if (result
== UnparkControl::RemoveBreak
||
311 result
== UnparkControl::RetainBreak
) {