newsmemory-ios-sdk/Frameworks/RCT-Folly.xcframework/ios-arm64/Headers/folly/CancellationToken-inl.h

505 lines
16 KiB
C++

/*
* Copyright (c) Meta Platforms, Inc. and 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.
*/
#include <algorithm>
#include <array>
#include <cstdint>
#include <limits>
#include <tuple>
#include <utility>
#include <glog/logging.h>
namespace folly {
namespace detail {
struct MergingCancellationStateTag {};
// Internal cancellation state object.
class CancellationState {
public:
FOLLY_NODISCARD static CancellationStateSourcePtr create();
protected:
// Constructed initially with a CancellationSource reference count of 1.
CancellationState() noexcept;
// Constructed initially with a CancellationToken reference count of 1.
explicit CancellationState(MergingCancellationStateTag) noexcept;
virtual ~CancellationState();
friend struct CancellationStateTokenDeleter;
friend struct CancellationStateSourceDeleter;
void removeTokenReference() noexcept;
void removeSourceReference() noexcept;
public:
FOLLY_NODISCARD CancellationStateTokenPtr addTokenReference() noexcept;
FOLLY_NODISCARD CancellationStateSourcePtr addSourceReference() noexcept;
bool tryAddCallback(
CancellationCallback* callback,
bool incrementRefCountIfSuccessful) noexcept;
void removeCallback(CancellationCallback* callback) noexcept;
bool isCancellationRequested() const noexcept;
bool canBeCancelled() const noexcept;
// Request cancellation.
// Return 'true' if cancellation had already been requested.
// Return 'false' if this was the first thread to request
// cancellation.
bool requestCancellation() noexcept;
private:
void lock() noexcept;
void unlock() noexcept;
void unlockAndIncrementTokenCount() noexcept;
void unlockAndDecrementTokenCount() noexcept;
bool tryLockAndCancelUnlessCancelled() noexcept;
template <typename Predicate>
bool tryLock(Predicate predicate) noexcept;
static bool canBeCancelled(std::uint64_t state) noexcept;
static bool isCancellationRequested(std::uint64_t state) noexcept;
static bool isLocked(std::uint64_t state) noexcept;
static constexpr std::uint64_t kCancellationRequestedFlag = 1;
static constexpr std::uint64_t kLockedFlag = 2;
static constexpr std::uint64_t kMergingFlag = 4;
static constexpr std::uint64_t kTokenReferenceCountIncrement = 8;
static constexpr std::uint64_t kSourceReferenceCountIncrement =
std::uint64_t(1) << 34u;
static constexpr std::uint64_t kTokenReferenceCountMask =
(kSourceReferenceCountIncrement - 1u) -
(kTokenReferenceCountIncrement - 1u);
static constexpr std::uint64_t kSourceReferenceCountMask =
std::numeric_limits<std::uint64_t>::max() -
(kSourceReferenceCountIncrement - 1u);
// Bit 0 - Cancellation Requested
// Bit 1 - Locked Flag
// Bit 2 - MergingCancellationState Flag
// Bits 3-33 - Token reference count (max ~2 billion)
// Bits 34-63 - Source reference count (max ~1 billion)
std::atomic<std::uint64_t> state_;
CancellationCallback* head_{nullptr};
std::thread::id signallingThreadId_;
};
template <typename... Data>
class CancellationStateWithData : public CancellationState {
template <typename... Args>
CancellationStateWithData(Args&&... data);
public:
template <typename... Args>
FOLLY_NODISCARD static std::
pair<CancellationStateSourcePtr, std::tuple<Data...>*>
create(Args&&... data);
private:
std::tuple<Data...> data_;
};
inline void CancellationStateTokenDeleter::operator()(
CancellationState* state) noexcept {
state->removeTokenReference();
}
inline void CancellationStateSourceDeleter::operator()(
CancellationState* state) noexcept {
state->removeSourceReference();
}
} // namespace detail
inline CancellationToken::CancellationToken(
const CancellationToken& other) noexcept
: state_() {
if (other.state_) {
state_ = other.state_->addTokenReference();
}
}
inline CancellationToken::CancellationToken(CancellationToken&& other) noexcept
: state_(std::move(other.state_)) {}
inline CancellationToken& CancellationToken::operator=(
const CancellationToken& other) noexcept {
if (state_ != other.state_) {
CancellationToken temp{other};
swap(temp);
}
return *this;
}
inline CancellationToken& CancellationToken::operator=(
CancellationToken&& other) noexcept {
state_ = std::move(other.state_);
return *this;
}
inline bool CancellationToken::isCancellationRequested() const noexcept {
return state_ != nullptr && state_->isCancellationRequested();
}
inline bool CancellationToken::canBeCancelled() const noexcept {
return state_ != nullptr && state_->canBeCancelled();
}
inline void CancellationToken::swap(CancellationToken& other) noexcept {
std::swap(state_, other.state_);
}
inline CancellationToken::CancellationToken(
detail::CancellationStateTokenPtr state) noexcept
: state_(std::move(state)) {}
inline bool operator==(
const CancellationToken& a, const CancellationToken& b) noexcept {
return a.state_ == b.state_;
}
inline bool operator!=(
const CancellationToken& a, const CancellationToken& b) noexcept {
return !(a == b);
}
inline CancellationSource::CancellationSource()
: state_(detail::CancellationState::create()) {}
inline CancellationSource::CancellationSource(
const CancellationSource& other) noexcept
: state_() {
if (other.state_) {
state_ = other.state_->addSourceReference();
}
}
inline CancellationSource::CancellationSource(
CancellationSource&& other) noexcept
: state_(std::move(other.state_)) {}
inline CancellationSource& CancellationSource::operator=(
const CancellationSource& other) noexcept {
if (state_ != other.state_) {
CancellationSource temp{other};
swap(temp);
}
return *this;
}
inline CancellationSource& CancellationSource::operator=(
CancellationSource&& other) noexcept {
state_ = std::move(other.state_);
return *this;
}
inline CancellationSource CancellationSource::invalid() noexcept {
return CancellationSource{detail::CancellationStateSourcePtr{}};
}
inline bool CancellationSource::isCancellationRequested() const noexcept {
return state_ != nullptr && state_->isCancellationRequested();
}
inline bool CancellationSource::canBeCancelled() const noexcept {
return state_ != nullptr;
}
inline CancellationToken CancellationSource::getToken() const noexcept {
if (state_ != nullptr) {
return CancellationToken{state_->addTokenReference()};
}
return CancellationToken{};
}
inline bool CancellationSource::requestCancellation() const noexcept {
if (state_ != nullptr) {
return state_->requestCancellation();
}
return false;
}
inline void CancellationSource::swap(CancellationSource& other) noexcept {
std::swap(state_, other.state_);
}
inline CancellationSource::CancellationSource(
detail::CancellationStateSourcePtr&& state) noexcept
: state_(std::move(state)) {}
template <
typename Callable,
std::enable_if_t<
std::is_constructible<CancellationCallback::VoidFunction, Callable>::
value,
int>>
inline CancellationCallback::CancellationCallback(
CancellationToken&& ct, Callable&& callable)
: next_(nullptr),
prevNext_(nullptr),
state_(nullptr),
callback_(static_cast<Callable&&>(callable)),
destructorHasRunInsideCallback_(nullptr),
callbackCompleted_(false) {
if (ct.state_ != nullptr && ct.state_->tryAddCallback(this, false)) {
state_ = ct.state_.release();
}
}
template <
typename Callable,
std::enable_if_t<
std::is_constructible<CancellationCallback::VoidFunction, Callable>::
value,
int>>
inline CancellationCallback::CancellationCallback(
const CancellationToken& ct, Callable&& callable)
: next_(nullptr),
prevNext_(nullptr),
state_(nullptr),
callback_(static_cast<Callable&&>(callable)),
destructorHasRunInsideCallback_(nullptr),
callbackCompleted_(false) {
if (ct.state_ != nullptr && ct.state_->tryAddCallback(this, true)) {
state_ = ct.state_.get();
}
}
inline CancellationCallback::~CancellationCallback() {
if (state_ != nullptr) {
state_->removeCallback(this);
}
}
inline void CancellationCallback::invokeCallback() noexcept {
// Invoke within a noexcept context so that we std::terminate() if it throws.
callback_();
}
namespace detail {
inline CancellationStateSourcePtr CancellationState::create() {
return CancellationStateSourcePtr{new CancellationState()};
}
inline CancellationState::CancellationState() noexcept
: state_(kSourceReferenceCountIncrement) {}
inline CancellationState::CancellationState(
MergingCancellationStateTag) noexcept
: state_(kTokenReferenceCountIncrement | kMergingFlag) {}
inline CancellationStateTokenPtr
CancellationState::addTokenReference() noexcept {
state_.fetch_add(kTokenReferenceCountIncrement, std::memory_order_relaxed);
return CancellationStateTokenPtr{this};
}
// This `alignas()` is here because "create" will going to allocate this
// back-to-back with our `CancellationCallback` array.
class alignas(CancellationCallback) MergingCancellationState
: public CancellationState {
// There's a noticeable perf gain from specializing the constructors
struct CopyTag {};
struct MoveTag {};
struct CopyMoveTag {};
explicit MergingCancellationState();
CancellationCallback* callbackEnd_; // byte after the last callback
public:
// Ctors are a private implementation detail of the create*() factory funcs
explicit MergingCancellationState(
CopyTag, size_t nCopy, const CancellationToken** copyToks);
explicit MergingCancellationState(
MoveTag, size_t nMove, CancellationToken** moveToks);
explicit MergingCancellationState(
CopyMoveTag,
size_t nCopy,
const CancellationToken** copyToks,
size_t nMove,
CancellationToken** moveToks);
~MergingCancellationState() override;
static CancellationStateTokenPtr createCopy(
size_t nCopy, const CancellationToken** copyToks);
static CancellationStateTokenPtr createMove(
size_t nMove, CancellationToken** moveToks);
static CancellationStateTokenPtr createCopyMove(
size_t nCopy,
const CancellationToken** copyToks,
size_t nMove,
CancellationToken** moveToks);
void destroy() noexcept;
};
inline void CancellationState::removeTokenReference() noexcept {
const auto oldState = state_.fetch_sub(
kTokenReferenceCountIncrement, std::memory_order_acq_rel);
DCHECK(
(oldState & kTokenReferenceCountMask) >= kTokenReferenceCountIncrement);
if (oldState < (2 * kTokenReferenceCountIncrement)) {
if (oldState & kMergingFlag) {
static_cast<MergingCancellationState*>(this)->destroy();
} else {
delete this;
}
}
}
inline CancellationStateSourcePtr
CancellationState::addSourceReference() noexcept {
state_.fetch_add(kSourceReferenceCountIncrement, std::memory_order_relaxed);
return CancellationStateSourcePtr{this};
}
inline void CancellationState::removeSourceReference() noexcept {
const auto oldState = state_.fetch_sub(
kSourceReferenceCountIncrement, std::memory_order_acq_rel);
DCHECK(
(oldState & kSourceReferenceCountMask) >= kSourceReferenceCountIncrement);
if (oldState <
(kSourceReferenceCountIncrement + kTokenReferenceCountIncrement)) {
// No "free()" branch because "merging" state has no source pointers.
DCHECK(!(oldState & kMergingFlag));
delete this;
}
}
inline bool CancellationState::isCancellationRequested() const noexcept {
return isCancellationRequested(state_.load(std::memory_order_acquire));
}
inline bool CancellationState::canBeCancelled() const noexcept {
return canBeCancelled(state_.load(std::memory_order_acquire));
}
inline bool CancellationState::canBeCancelled(std::uint64_t state) noexcept {
// Can be cancelled if there is at least one CancellationSource ref-count
// or if cancellation has been requested.
return (state >= kSourceReferenceCountIncrement) ||
(state & kMergingFlag) != 0 || isCancellationRequested(state);
}
inline bool CancellationState::isCancellationRequested(
std::uint64_t state) noexcept {
return (state & kCancellationRequestedFlag) != 0;
}
inline bool CancellationState::isLocked(std::uint64_t state) noexcept {
return (state & kLockedFlag) != 0;
}
template <typename... Data>
struct WithDataTag {};
template <typename... Data>
template <typename... Args>
CancellationStateWithData<Data...>::CancellationStateWithData(Args&&... data)
: data_(std::forward<Args>(data)...) {}
template <typename... Data>
template <typename... Args>
std::pair<CancellationStateSourcePtr, std::tuple<Data...>*>
CancellationStateWithData<Data...>::create(Args&&... data) {
auto* state =
new CancellationStateWithData<Data...>(std::forward<Args>(data)...);
return {CancellationStateSourcePtr{state}, &state->data_};
}
} // namespace detail
template <typename... Data, typename... Args>
std::pair<CancellationSource, std::tuple<Data...>*> CancellationSource::create(
detail::WithDataTag<Data...>, Args&&... data) {
auto cancellationStateWithData =
detail::CancellationStateWithData<Data...>::create(
std::forward<Args>(data)...);
return {
CancellationSource{std::move(cancellationStateWithData.first)},
cancellationStateWithData.second};
}
template <typename... Ts>
inline CancellationToken CancellationToken::merge(Ts&&... tokens) {
if constexpr (sizeof...(Ts) == 0) {
return CancellationToken();
} else if constexpr (sizeof...(Ts) == 1) {
return (tokens, ...);
} else {
constexpr size_t N = sizeof...(Ts);
constexpr size_t NCopy =
((std::is_reference_v<Ts> || std::is_const_v<Ts>)+...);
std::array<const CancellationToken*, NCopy> copyToks;
std::array<CancellationToken*, N - NCopy> moveToks;
const detail::CancellationState* prevState = nullptr;
size_t copyIdx = 0, moveIdx = 0;
(
[&] {
constexpr bool mustCopy =
std::is_reference_v<Ts> || std::is_const_v<Ts>;
auto* state = tokens.state_.get();
if (!state) {
return; // Omit empties
}
if (state == prevState) {
if constexpr (!mustCopy) {
// Move out the input token, although we didn't use it. The
// goal is to make deduplication non-observable by the user.
std::exchange(tokens, {});
}
return; // Omit adjacent duplicates
}
prevState = state;
if constexpr (mustCopy) {
copyToks[copyIdx++] = &tokens;
} else {
moveToks[moveIdx++] = &tokens;
}
}(),
...);
size_t n = copyIdx + moveIdx;
if (n == 0) {
return CancellationToken();
} else if (n == 1) {
if (moveIdx) { // A ternary would have type `const CT*` and NOT move!
return std::move(*moveToks[0]);
}
return *copyToks[0];
} else if constexpr (NCopy == N) {
return CancellationToken(detail::MergingCancellationState::createCopy(
copyIdx, copyToks.data()));
} else if constexpr (NCopy == 0) {
return CancellationToken(detail::MergingCancellationState::createMove(
moveIdx, moveToks.data()));
} else {
return CancellationToken(detail::MergingCancellationState::createCopyMove(
copyIdx, copyToks.data(), moveIdx, moveToks.data()));
}
}
}
} // namespace folly