verdnatura-chat/ios/Pods/Flipper-Folly/folly/synchronization/ParkingLot.h

332 lines
9.3 KiB
C++

/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <atomic>
#include <condition_variable>
#include <mutex>
#include <folly/Hash.h>
#include <folly/Indestructible.h>
#include <folly/Portability.h>
#include <folly/Unit.h>
#include <folly/lang/SafeAssert.h>
namespace folly {
namespace parking_lot_detail {
struct WaitNodeBase {
const uint64_t key_;
const uint64_t lotid_;
WaitNodeBase* next_{nullptr};
WaitNodeBase* prev_{nullptr};
// tricky: hold both bucket and node mutex to write, either to read
bool signaled_;
std::mutex mutex_;
std::condition_variable cond_;
WaitNodeBase(uint64_t key, uint64_t lotid)
: key_(key), lotid_(lotid), signaled_(false) {}
template <typename Clock, typename Duration>
std::cv_status wait(std::chrono::time_point<Clock, Duration> deadline) {
std::cv_status status = std::cv_status::no_timeout;
std::unique_lock<std::mutex> nodeLock(mutex_);
while (!signaled_ && status != std::cv_status::timeout) {
if (deadline != std::chrono::time_point<Clock, Duration>::max()) {
status = cond_.wait_until(nodeLock, deadline);
} else {
cond_.wait(nodeLock);
}
}
return status;
}
void wake() {
std::lock_guard<std::mutex> nodeLock(mutex_);
signaled_ = true;
cond_.notify_one();
}
bool signaled() {
return signaled_;
}
};
extern std::atomic<uint64_t> idallocator;
// Our emulated futex uses 4096 lists of wait nodes. There are two levels
// of locking: a per-list mutex that controls access to the list and a
// per-node mutex, condvar, and bool that are used for the actual wakeups.
// The per-node mutex allows us to do precise wakeups without thundering
// herds.
struct Bucket {
std::mutex mutex_;
WaitNodeBase* head_;
WaitNodeBase* tail_;
std::atomic<uint64_t> count_;
static Bucket& bucketFor(uint64_t key);
void push_back(WaitNodeBase* node) {
if (tail_) {
FOLLY_SAFE_DCHECK(head_, "");
node->prev_ = tail_;
tail_->next_ = node;
tail_ = node;
} else {
tail_ = node;
head_ = node;
}
}
void erase(WaitNodeBase* node) {
FOLLY_SAFE_DCHECK(count_.load(std::memory_order_relaxed) >= 1, "");
if (head_ == node && tail_ == node) {
FOLLY_SAFE_DCHECK(node->prev_ == nullptr, "");
FOLLY_SAFE_DCHECK(node->next_ == nullptr, "");
head_ = nullptr;
tail_ = nullptr;
} else if (head_ == node) {
FOLLY_SAFE_DCHECK(node->prev_ == nullptr, "");
FOLLY_SAFE_DCHECK(node->next_, "");
head_ = node->next_;
head_->prev_ = nullptr;
} else if (tail_ == node) {
FOLLY_SAFE_DCHECK(node->next_ == nullptr, "");
FOLLY_SAFE_DCHECK(node->prev_, "");
tail_ = node->prev_;
tail_->next_ = nullptr;
} else {
FOLLY_SAFE_DCHECK(node->next_, "");
FOLLY_SAFE_DCHECK(node->prev_, "");
node->next_->prev_ = node->prev_;
node->prev_->next_ = node->next_;
}
count_.fetch_sub(1, std::memory_order_relaxed);
}
};
} // namespace parking_lot_detail
enum class UnparkControl {
RetainContinue,
RemoveContinue,
RetainBreak,
RemoveBreak,
};
enum class ParkResult {
Skip,
Unpark,
Timeout,
};
/*
* ParkingLot provides an interface that is similar to Linux's futex
* system call, but with additional functionality. It is implemented
* in a portable way on top of std::mutex and std::condition_variable.
*
* Additional reading:
* https://webkit.org/blog/6161/locking-in-webkit/
* https://github.com/WebKit/webkit/blob/master/Source/WTF/wtf/ParkingLot.h
* https://locklessinc.com/articles/futex_cheat_sheet/
*
* The main difference from futex is that park/unpark take lambdas,
* such that nearly anything can be done while holding the bucket
* lock. Unpark() lambda can also be used to wake up any number of
* waiters.
*
* ParkingLot is templated on the data type, however, all ParkingLot
* implementations are backed by a single static array of buckets to
* avoid large memory overhead. Lambdas will only ever be called on
* the specific ParkingLot's nodes.
*/
template <typename Data = Unit>
class ParkingLot {
const uint64_t lotid_;
ParkingLot(const ParkingLot&) = delete;
struct WaitNode : public parking_lot_detail::WaitNodeBase {
const Data data_;
template <typename D>
WaitNode(uint64_t key, uint64_t lotid, D&& data)
: WaitNodeBase(key, lotid), data_(std::forward<D>(data)) {}
};
public:
ParkingLot() : lotid_(parking_lot_detail::idallocator++) {}
/* Park API
*
* Key is almost always the address of a variable.
*
* ToPark runs while holding the bucket lock: usually this
* is a check to see if we can sleep, by checking waiter bits.
*
* PreWait is usually used to implement condition variable like
* things, such that you can unlock the condition variable's lock at
* the appropriate time.
*/
template <typename Key, typename D, typename ToPark, typename PreWait>
ParkResult park(const Key key, D&& data, ToPark&& toPark, PreWait&& preWait) {
return park_until(
key,
std::forward<D>(data),
std::forward<ToPark>(toPark),
std::forward<PreWait>(preWait),
std::chrono::steady_clock::time_point::max());
}
template <
typename Key,
typename D,
typename ToPark,
typename PreWait,
typename Clock,
typename Duration>
ParkResult park_until(
const Key key,
D&& data,
ToPark&& toPark,
PreWait&& preWait,
std::chrono::time_point<Clock, Duration> deadline);
template <
typename Key,
typename D,
typename ToPark,
typename PreWait,
typename Rep,
typename Period>
ParkResult park_for(
const Key key,
D&& data,
ToPark&& toPark,
PreWait&& preWait,
std::chrono::duration<Rep, Period>& timeout) {
return park_until(
key,
std::forward<D>(data),
std::forward<ToPark>(toPark),
std::forward<PreWait>(preWait),
timeout + std::chrono::steady_clock::now());
}
/*
* Unpark API
*
* Key is the same uniqueaddress used in park(), and is used as a
* hash key for lookup of waiters.
*
* Unparker is a function that is given the Data parameter, and
* returns an UnparkControl. The Remove* results will remove and
* wake the waiter, the Ignore/Stop results will not, while stopping
* or continuing iteration of the waiter list.
*/
template <typename Key, typename Unparker>
void unpark(const Key key, Unparker&& func);
};
template <typename Data>
template <
typename Key,
typename D,
typename ToPark,
typename PreWait,
typename Clock,
typename Duration>
ParkResult ParkingLot<Data>::park_until(
const Key bits,
D&& data,
ToPark&& toPark,
PreWait&& preWait,
std::chrono::time_point<Clock, Duration> deadline) {
auto key = hash::twang_mix64(uint64_t(bits));
auto& bucket = parking_lot_detail::Bucket::bucketFor(key);
WaitNode node(key, lotid_, std::forward<D>(data));
{
// A: Must be seq_cst. Matches B.
bucket.count_.fetch_add(1, std::memory_order_seq_cst);
std::unique_lock<std::mutex> bucketLock(bucket.mutex_);
if (!std::forward<ToPark>(toPark)()) {
bucketLock.unlock();
bucket.count_.fetch_sub(1, std::memory_order_relaxed);
return ParkResult::Skip;
}
bucket.push_back(&node);
} // bucketLock scope
std::forward<PreWait>(preWait)();
auto status = node.wait(deadline);
if (status == std::cv_status::timeout) {
// it's not really a timeout until we unlink the unsignaled node
std::lock_guard<std::mutex> bucketLock(bucket.mutex_);
if (!node.signaled()) {
bucket.erase(&node);
return ParkResult::Timeout;
}
}
return ParkResult::Unpark;
}
template <typename Data>
template <typename Key, typename Func>
void ParkingLot<Data>::unpark(const Key bits, Func&& func) {
auto key = hash::twang_mix64(uint64_t(bits));
auto& bucket = parking_lot_detail::Bucket::bucketFor(key);
// B: Must be seq_cst. Matches A. If true, A *must* see in seq_cst
// order any atomic updates in toPark() (and matching updates that
// happen before unpark is called)
if (bucket.count_.load(std::memory_order_seq_cst) == 0) {
return;
}
std::lock_guard<std::mutex> bucketLock(bucket.mutex_);
for (auto iter = bucket.head_; iter != nullptr;) {
auto node = static_cast<WaitNode*>(iter);
iter = iter->next_;
if (node->key_ == key && node->lotid_ == lotid_) {
auto result = std::forward<Func>(func)(node->data_);
if (result == UnparkControl::RemoveBreak ||
result == UnparkControl::RemoveContinue) {
// we unlink, but waiter destroys the node
bucket.erase(node);
node->wake();
}
if (result == UnparkControl::RemoveBreak ||
result == UnparkControl::RetainBreak) {
return;
}
}
}
}
} // namespace folly