unioil-loyalty-rn-app/ios/Pods/Flipper-Folly/folly/io/async/Request.cpp

657 lines
19 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.
*/
#include <folly/io/async/Request.h>
#include <folly/GLog.h>
#include <folly/MapUtil.h>
#include <folly/SingletonThreadLocal.h>
#include <folly/experimental/SingleWriterFixedHashMap.h>
#include <folly/synchronization/Hazptr.h>
#include <folly/tracing/StaticTracepoint.h>
namespace folly {
namespace {
using SingletonT =
SingletonThreadLocal<RequestContext::StaticContext, RequestContext>;
}
RequestToken::RequestToken(const std::string& str) {
auto& cache = getCache();
{
auto c = cache.rlock();
auto res = c->find(str);
if (res != c->end()) {
token_ = res->second;
return;
}
}
auto c = cache.wlock();
auto res = c->find(str);
if (res != c->end()) {
token_ = res->second;
return;
}
static uint32_t nextToken{1};
token_ = nextToken++;
(*c)[str] = token_;
}
std::string RequestToken::getDebugString() const {
auto& cache = getCache();
auto c = cache.rlock();
for (auto& v : *c) {
if (v.second == token_) {
return v.first;
}
}
throw std::logic_error("Could not find debug string in RequestToken");
}
Synchronized<F14FastMap<std::string, uint32_t>>& RequestToken::getCache() {
static Indestructible<Synchronized<F14FastMap<std::string, uint32_t>>> cache;
return *cache;
}
FOLLY_ALWAYS_INLINE
void RequestData::acquireRef() {
auto rc = keepAliveCounter_.fetch_add(
kClearCount + kDeleteCount, std::memory_order_relaxed);
DCHECK_GE(rc, 0);
}
void RequestData::releaseRefClearOnly() {
auto rc =
keepAliveCounter_.fetch_sub(kClearCount, std::memory_order_acq_rel) -
kClearCount;
DCHECK_GT(rc, 0);
if (rc < kClearCount) {
this->onClear();
}
}
void RequestData::releaseRefDeleteOnly() {
auto rc =
keepAliveCounter_.fetch_sub(kDeleteCount, std::memory_order_acq_rel) -
kDeleteCount;
DCHECK_GE(rc, 0);
if (rc == 0) {
delete this;
}
}
FOLLY_ALWAYS_INLINE
void RequestData::releaseRefClearDelete() {
auto rc = keepAliveCounter_.load(std::memory_order_acquire);
if (FOLLY_LIKELY(rc == (kClearCount + kDeleteCount))) {
this->onClear();
delete this;
} else {
releaseRefClearDeleteSlow();
}
}
FOLLY_NOINLINE
void RequestData::releaseRefClearDeleteSlow() {
releaseRefClearOnly();
releaseRefDeleteOnly();
}
// The Combined struct keeps the two structures for context data
// and callbacks together, so that readers can protect consistent
// versions of the two structures together using hazard pointers.
struct RequestContext::State::Combined : hazptr_obj_base<Combined> {
static constexpr size_t kInitialCapacity = 4;
static constexpr size_t kSlackReciprocal = 4; // unused >= 1/4 capacity
// This must be optimized for lookup, its hot path is getContextData
// Efficiency of copying the container also matters in setShallowCopyContext
SingleWriterFixedHashMap<RequestToken, RequestData*> requestData_;
// This must be optimized for iteration, its hot path is setContext
SingleWriterFixedHashMap<RequestData*, bool> callbackData_;
// Vector of cleared data. Accessed only sequentially by writers.
std::vector<std::pair<RequestToken, RequestData*>> cleared_;
Combined()
: requestData_(kInitialCapacity), callbackData_(kInitialCapacity) {}
Combined(const Combined& o)
: Combined(o.requestData_.capacity(), o.callbackData_.capacity(), o) {}
Combined(size_t dataCapacity, size_t callbackCapacity, const Combined& o)
: requestData_(dataCapacity, o.requestData_),
callbackData_(callbackCapacity, o.callbackData_) {}
Combined(Combined&&) = delete;
Combined& operator=(const Combined&) = delete;
Combined& operator=(Combined&&) = delete;
~Combined() { releaseDataRefs(); }
/* acquireDataRefs - Called at most once per Combined instance. */
void acquireDataRefs() {
for (auto it = requestData_.begin(); it != requestData_.end(); ++it) {
auto p = it.value();
if (p) {
p->acquireRef();
}
}
}
/* releaseDataRefs - Called only once from ~Combined */
void releaseDataRefs() {
if (!cleared_.empty()) {
for (auto& pair : cleared_) {
pair.second->releaseRefDeleteOnly();
requestData_.erase(pair.first);
}
}
for (auto it = requestData_.begin(); it != requestData_.end(); ++it) {
RequestData* data = it.value();
if (data) {
data->releaseRefClearDelete();
}
}
}
/* needExpand */
bool needExpand() {
return needExpandRequestData() || needExpandCallbackData();
}
/* needExpandRequestData */
bool needExpandRequestData() {
return kSlackReciprocal * (requestData_.available() - 1) <
requestData_.capacity();
}
/* needExpandCallbackData */
bool needExpandCallbackData() {
return kSlackReciprocal * (callbackData_.available() - 1) <
callbackData_.capacity();
}
}; // Combined
RequestContext::State::State() = default;
FOLLY_ALWAYS_INLINE
RequestContext::State::State(const State& o) {
Combined* oc = o.combined();
if (oc) {
auto p = new Combined(*oc);
p->acquireDataRefs();
setCombined(p);
}
}
RequestContext::State::~State() {
cohort_.shutdown_and_reclaim();
auto p = combined();
if (p) {
delete p;
}
}
FOLLY_ALWAYS_INLINE
RequestContext::State::Combined* RequestContext::State::combined() const {
return combined_.load(std::memory_order_acquire);
}
FOLLY_ALWAYS_INLINE
RequestContext::State::Combined* RequestContext::State::ensureCombined() {
auto c = combined();
if (!c) {
c = new Combined;
setCombined(c);
}
return c;
}
FOLLY_ALWAYS_INLINE
void RequestContext::State::setCombined(Combined* p) {
p->set_cohort_tag(&cohort_);
combined_.store(p, std::memory_order_release);
}
FOLLY_ALWAYS_INLINE
bool RequestContext::State::doSetContextData(
const RequestToken& token,
std::unique_ptr<RequestData>& data,
DoSetBehaviour behaviour,
bool safe) {
SetContextDataResult result;
if (safe) {
result = doSetContextDataHelper(token, data, behaviour, safe);
} else {
std::lock_guard<std::mutex> g(mutex_);
result = doSetContextDataHelper(token, data, behaviour, safe);
}
if (result.unexpected) {
FB_LOG_EVERY_MS(WARNING, 60000)
<< "Calling RequestContext::setContextData for "
<< token.getDebugString() << " but it is already set";
}
if (result.replaced) {
result.replaced->retire(); // Retire to hazptr library
}
return result.changed;
}
FOLLY_ALWAYS_INLINE
RequestContext::State::SetContextDataResult
RequestContext::State::doSetContextDataHelper(
const RequestToken& token,
std::unique_ptr<RequestData>& data,
DoSetBehaviour behaviour,
bool safe) {
bool unexpected = false;
Combined* cur = ensureCombined();
Combined* replaced = nullptr;
auto it = cur->requestData_.find(token);
bool found = it != cur->requestData_.end();
if (found) {
if (behaviour == DoSetBehaviour::SET_IF_ABSENT) {
return {
false /* no changes made */,
false /* nothing unexpected */,
nullptr /* combined not replaced */};
}
RequestData* oldData = it.value();
// Always erase old data (and run onUnset callback, if any).
// Old data will always be overwritten either by the new data
// (if behavior is OVERWRITE) or by nullptr (if behavior is SET).
Combined* newCombined = eraseOldData(cur, token, oldData, safe);
DCHECK(oldData != nullptr || newCombined == nullptr);
if (newCombined) {
replaced = cur;
cur = newCombined;
}
if (behaviour == DoSetBehaviour::SET) {
// The expected behavior for SET when found is to reset the
// pointer and warn, without updating to the new data.
bool inserted = cur->requestData_.insert(token, nullptr);
DCHECK(inserted);
unexpected = true;
} else {
DCHECK(behaviour == DoSetBehaviour::OVERWRITE);
}
}
if (!unexpected) {
// Replace combined if needed, call onSet if any, insert new data.
Combined* newCombined = insertNewData(cur, token, data, found);
if (newCombined) {
replaced = cur;
cur = newCombined;
}
}
if (replaced) {
// Now the new Combined is consistent. Safe to publish.
setCombined(cur);
}
return {
true, /* changes were made */
unexpected,
replaced};
}
FOLLY_ALWAYS_INLINE
RequestContext::State::Combined* FOLLY_NULLABLE
RequestContext::State::eraseOldData(
RequestContext::State::Combined* cur,
const RequestToken& token,
RequestData* olddata,
bool safe) {
Combined* newCombined = nullptr;
// Call onUnset, if any.
if (olddata && olddata->hasCallback()) {
olddata->onUnset();
bool erased = cur->callbackData_.erase(olddata);
DCHECK(erased);
}
if (safe || olddata == nullptr) {
// If the caller guarantees thread-safety or the old data is null,
// then erase the entry in the current version.
bool erased = cur->requestData_.erase(token);
DCHECK(erased);
if (olddata) {
olddata->releaseRefClearDelete();
}
} else {
// If there may be concurrent readers, then copy-on-erase.
// Update the data reference counts to account for the
// existence of the new copy.
newCombined = new Combined(*cur);
bool erased = newCombined->requestData_.erase(token);
DCHECK(erased);
newCombined->acquireDataRefs();
}
return newCombined;
}
FOLLY_ALWAYS_INLINE
RequestContext::State::Combined* FOLLY_NULLABLE
RequestContext::State::insertNewData(
RequestContext::State::Combined* cur,
const RequestToken& token,
std::unique_ptr<RequestData>& data,
bool found) {
Combined* newCombined = nullptr;
// Update value to point to the new data.
if (!found && cur->needExpand()) {
// Replace the current Combined with an expanded one
newCombined = expand(cur);
cur = newCombined;
cur->acquireDataRefs();
}
if (data && data->hasCallback()) {
// If data has callback, insert in callback structure, call onSet
bool inserted = cur->callbackData_.insert(data.get(), true);
DCHECK(inserted);
data->onSet();
}
if (data) {
data->acquireRef();
}
bool inserted = cur->requestData_.insert(token, data.release());
DCHECK(inserted);
return newCombined;
}
FOLLY_ALWAYS_INLINE
bool RequestContext::State::hasContextData(const RequestToken& token) const {
hazptr_local<1> h;
Combined* combined = h[0].get_protected(combined_);
return combined ? combined->requestData_.contains(token) : false;
}
FOLLY_ALWAYS_INLINE
RequestData* FOLLY_NULLABLE
RequestContext::State::getContextData(const RequestToken& token) {
hazptr_local<1> h;
Combined* combined = h[0].get_protected(combined_);
if (!combined) {
return nullptr;
}
auto& reqData = combined->requestData_;
auto it = reqData.find(token);
return it == reqData.end() ? nullptr : it.value();
}
FOLLY_ALWAYS_INLINE
const RequestData* FOLLY_NULLABLE
RequestContext::State::getContextData(const RequestToken& token) const {
hazptr_local<1> h;
Combined* combined = h[0].get_protected(combined_);
if (!combined) {
return nullptr;
}
auto& reqData = combined->requestData_;
auto it = reqData.find(token);
return it == reqData.end() ? nullptr : it.value();
}
FOLLY_ALWAYS_INLINE
void RequestContext::State::onSet() {
// Don't use hazptr_local because callback may use hazptr
hazptr_holder<> h;
Combined* combined = h.get_protected(combined_);
if (!combined) {
return;
}
auto& cb = combined->callbackData_;
for (auto it = cb.begin(); it != cb.end(); ++it) {
it.key()->onSet();
}
}
FOLLY_ALWAYS_INLINE
void RequestContext::State::onUnset() {
// Don't use hazptr_local because callback may use hazptr
hazptr_holder<> h;
Combined* combined = h.get_protected(combined_);
if (!combined) {
return;
}
auto& cb = combined->callbackData_;
for (auto it = cb.begin(); it != cb.end(); ++it) {
it.key()->onUnset();
}
}
void RequestContext::State::clearContextData(const RequestToken& token) {
RequestData* data;
Combined* replaced = nullptr;
{ // Lock mutex_
std::lock_guard<std::mutex> g(mutex_);
Combined* cur = combined();
if (!cur) {
return;
}
auto it = cur->requestData_.find(token);
if (it == cur->requestData_.end()) {
return;
}
data = it.value();
if (!data) {
bool erased = cur->requestData_.erase(token);
DCHECK(erased);
return;
}
if (data->hasCallback()) {
data->onUnset();
bool erased = cur->callbackData_.erase(data);
DCHECK(erased);
}
replaced = cur;
cur = new Combined(*replaced);
bool erased = cur->requestData_.erase(token);
DCHECK(erased);
cur->acquireDataRefs();
setCombined(cur);
} // Unlock mutex_
DCHECK(data);
data->releaseRefClearOnly();
DCHECK(replaced);
replaced->cleared_.emplace_back(std::make_pair(token, data));
replaced->retire();
}
RequestContext::State::Combined* RequestContext::State::expand(
RequestContext::State::Combined* c) {
size_t dataCapacity = c->requestData_.capacity();
if (c->needExpandRequestData()) {
dataCapacity *= 2;
}
size_t callbackCapacity = c->callbackData_.capacity();
if (c->needExpandCallbackData()) {
callbackCapacity *= 2;
}
return new Combined(dataCapacity, callbackCapacity, *c);
}
RequestContext::RequestContext() : rootId_(reinterpret_cast<intptr_t>(this)) {}
RequestContext::RequestContext(intptr_t rootid) : rootId_(rootid) {}
RequestContext::RequestContext(const RequestContext& ctx, intptr_t rootid, Tag)
: RequestContext(ctx) {
rootId_ = rootid;
}
RequestContext::RequestContext(const RequestContext& ctx, Tag)
: RequestContext(ctx) {}
/* static */ std::shared_ptr<RequestContext> RequestContext::copyAsRoot(
const RequestContext& ctx, intptr_t rootid) {
return std::make_shared<RequestContext>(ctx, rootid, Tag{});
}
/* static */ std::shared_ptr<RequestContext> RequestContext::copyAsChild(
const RequestContext& ctx) {
return std::make_shared<RequestContext>(ctx, Tag{});
}
void RequestContext::setContextData(
const RequestToken& token, std::unique_ptr<RequestData> data) {
state_.doSetContextData(token, data, DoSetBehaviour::SET, false);
}
bool RequestContext::setContextDataIfAbsent(
const RequestToken& token, std::unique_ptr<RequestData> data) {
return state_.doSetContextData(
token, data, DoSetBehaviour::SET_IF_ABSENT, false);
}
void RequestContext::overwriteContextData(
const RequestToken& token, std::unique_ptr<RequestData> data, bool safe) {
state_.doSetContextData(token, data, DoSetBehaviour::OVERWRITE, safe);
}
bool RequestContext::hasContextData(const RequestToken& val) const {
return state_.hasContextData(val);
}
RequestData* FOLLY_NULLABLE
RequestContext::getContextData(const RequestToken& val) {
return state_.getContextData(val);
}
const RequestData* FOLLY_NULLABLE
RequestContext::getContextData(const RequestToken& val) const {
return state_.getContextData(val);
}
void RequestContext::onSet() {
state_.onSet();
}
void RequestContext::onUnset() {
state_.onUnset();
}
void RequestContext::clearContextData(const RequestToken& val) {
state_.clearContextData(val);
}
/* static */ std::shared_ptr<RequestContext> RequestContext::setContext(
std::shared_ptr<RequestContext> const& newCtx) {
return setContext(copy(newCtx));
}
/* static */ std::shared_ptr<RequestContext> RequestContext::setContext(
std::shared_ptr<RequestContext>&& newCtx_) {
auto newCtx = std::move(newCtx_); // enforce that it is really moved-from
auto& staticCtx = getStaticContext();
if (newCtx == staticCtx.first) {
return newCtx;
}
FOLLY_SDT(
folly,
request_context_switch_before,
staticCtx.first.get(),
newCtx.get(),
staticCtx.first ? staticCtx.first->getRootId() : 0,
newCtx ? newCtx->getRootId() : 0);
std::shared_ptr<RequestContext> prevCtx;
RequestContext* curCtx = staticCtx.first.get();
bool checkCur = curCtx && curCtx->state_.combined();
bool checkNew = newCtx && newCtx->state_.combined();
if (checkCur && checkNew) {
hazptr_array<2> h;
auto curc = h[0].get_protected(curCtx->state_.combined_);
auto newc = h[1].get_protected(newCtx->state_.combined_);
auto& curcb = curc->callbackData_;
auto& newcb = newc->callbackData_;
for (auto it = curcb.begin(); it != curcb.end(); ++it) {
DCHECK(it.key());
auto data = it.key();
if (!newcb.contains(data)) {
data->onUnset();
}
}
prevCtx = std::move(staticCtx.first);
staticCtx.first = std::move(newCtx);
staticCtx.second.store(staticCtx.first->rootId_, std::memory_order_relaxed);
for (auto it = newcb.begin(); it != newcb.end(); ++it) {
DCHECK(it.key());
auto data = it.key();
if (!curcb.contains(data)) {
data->onSet();
}
}
} else {
if (curCtx) {
curCtx->state_.onUnset();
}
prevCtx = std::move(staticCtx.first);
staticCtx.first = std::move(newCtx);
if (staticCtx.first) {
staticCtx.second.store(
staticCtx.first->rootId_, std::memory_order_relaxed);
staticCtx.first->state_.onSet();
} else {
staticCtx.second.store(0, std::memory_order_relaxed);
}
}
return prevCtx;
}
RequestContext::StaticContext& RequestContext::getStaticContext() {
return SingletonT::get();
}
/* static */ std::vector<RequestContext::RootIdInfo>
RequestContext::getRootIdsFromAllThreads() {
std::vector<RootIdInfo> result;
auto accessor = SingletonT::accessAllThreads();
for (auto it = accessor.begin(); it != accessor.end(); ++it) {
result.push_back(
{it->second.load(std::memory_order_relaxed),
it.getThreadId(),
it.getOSThreadId()});
}
return result;
}
/* static */ std::shared_ptr<RequestContext>
RequestContext::setShallowCopyContext() {
auto& parent = getStaticContext().first;
auto child = parent ? RequestContext::copyAsChild(*parent)
: std::make_shared<RequestContext>();
if (!parent) {
child->rootId_ = 0;
}
// Do not use setContext to avoid global set/unset
// Also rootId does not change so do not bother setting it.
std::swap(child, parent);
return child;
}
RequestContext* RequestContext::get() {
auto& context = getStaticContext().first;
if (!context) {
static RequestContext defaultContext(0);
return std::addressof(defaultContext);
}
return context.get();
}
} // namespace folly