852 lines
24 KiB
C++
852 lines
24 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/experimental/SingleWriterFixedHashMap.h>
|
|
#include <folly/synchronization/Hazptr.h>
|
|
#include <folly/tracing/StaticTracepoint.h>
|
|
|
|
#include <glog/logging.h>
|
|
|
|
#include <folly/MapUtil.h>
|
|
#include <folly/SingletonThreadLocal.h>
|
|
|
|
DEFINE_bool(
|
|
folly_reqctx_use_hazptr,
|
|
true,
|
|
"RequestContext implementation using hazard pointers");
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
void RequestData::releaseRefClearDelete() {
|
|
auto rc = keepAliveCounter_.fetch_sub(
|
|
kClearCount + kDeleteCount, std::memory_order_acq_rel) -
|
|
(kClearCount + kDeleteCount);
|
|
DCHECK_GE(rc, 0);
|
|
if (rc < kClearCount) {
|
|
this->onClear();
|
|
}
|
|
if (rc == 0) {
|
|
delete this;
|
|
}
|
|
}
|
|
|
|
void RequestData::DestructPtr::operator()(RequestData* ptr) {
|
|
if (ptr) {
|
|
auto keepAliveCounter =
|
|
ptr->keepAliveCounter_.fetch_sub(1, std::memory_order_acq_rel);
|
|
// Note: this is the value before decrement, hence == 1 check
|
|
DCHECK(keepAliveCounter > 0);
|
|
if (keepAliveCounter == 1) {
|
|
ptr->onClear();
|
|
delete ptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* static */ RequestData::SharedPtr RequestData::constructPtr(
|
|
RequestData* ptr) {
|
|
if (ptr) {
|
|
auto keepAliveCounter =
|
|
ptr->keepAliveCounter_.fetch_add(1, std::memory_order_relaxed);
|
|
DCHECK(keepAliveCounter >= 0);
|
|
}
|
|
return SharedPtr(ptr);
|
|
}
|
|
|
|
// 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::StateHazptr::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_;
|
|
// Hash map to keep track of Clear and Delete counts. The presence
|
|
// of a key indicates holding a Delete count for the request data
|
|
// (i.e., delete when the Delete count goes to zero). A value of
|
|
// true indicates holding a Clear counts for the request data (i.e.,
|
|
// call onClear() when the Clear count goes to zero).
|
|
F14FastMap<RequestData*, bool> refs_;
|
|
|
|
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) {
|
|
refs_.insert({p, true});
|
|
p->acquireRef();
|
|
}
|
|
}
|
|
}
|
|
|
|
/* releaseDataRefs - Called only once from ~Combined */
|
|
void releaseDataRefs() {
|
|
for (auto pair : refs_) {
|
|
if (pair.second) {
|
|
pair.first->releaseRefClearDelete();
|
|
} else {
|
|
pair.first->releaseRefDeleteOnly();
|
|
}
|
|
}
|
|
}
|
|
|
|
/* 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::StateHazptr::StateHazptr() = default;
|
|
|
|
RequestContext::StateHazptr::StateHazptr(const StateHazptr& o) {
|
|
Combined* oc = o.combined();
|
|
if (oc) {
|
|
auto p = new Combined(*oc);
|
|
p->acquireDataRefs();
|
|
setCombined(p);
|
|
}
|
|
}
|
|
|
|
RequestContext::StateHazptr::~StateHazptr() {
|
|
cohort_.shutdown_and_reclaim();
|
|
auto p = combined();
|
|
if (p) {
|
|
delete p;
|
|
}
|
|
}
|
|
|
|
FOLLY_ALWAYS_INLINE
|
|
RequestContext::StateHazptr::Combined* RequestContext::StateHazptr::combined()
|
|
const {
|
|
return combined_.load(std::memory_order_acquire);
|
|
}
|
|
|
|
RequestContext::StateHazptr::Combined*
|
|
RequestContext::StateHazptr::ensureCombined() {
|
|
auto c = combined();
|
|
if (!c) {
|
|
c = new Combined;
|
|
setCombined(c);
|
|
}
|
|
return c;
|
|
}
|
|
|
|
void RequestContext::StateHazptr::setCombined(Combined* p) {
|
|
p->set_cohort_tag(&cohort_);
|
|
combined_.store(p, std::memory_order_release);
|
|
}
|
|
|
|
bool RequestContext::StateHazptr::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) {
|
|
LOG_FIRST_N(WARNING, 1)
|
|
<< "Calling RequestContext::setContextData for "
|
|
<< token.getDebugString() << " but it is already set";
|
|
}
|
|
if (result.replaced) {
|
|
result.replaced->retire(); // Retire to hazptr library
|
|
}
|
|
return result.changed;
|
|
}
|
|
|
|
RequestContext::StateHazptr::SetContextDataResult
|
|
RequestContext::StateHazptr::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();
|
|
if (oldData) {
|
|
// Always erase non-null old data (and run its onUnset callback,
|
|
// if any). Non-null 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);
|
|
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.
|
|
if (oldData) {
|
|
cur->requestData_.insert(token, nullptr);
|
|
}
|
|
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};
|
|
}
|
|
|
|
RequestContext::StateHazptr::Combined* FOLLY_NULLABLE
|
|
RequestContext::StateHazptr::eraseOldData(
|
|
RequestContext::StateHazptr::Combined* cur,
|
|
const RequestToken& token,
|
|
RequestData* olddata,
|
|
bool safe) {
|
|
Combined* newCombined = nullptr;
|
|
// Call onUnset, if any.
|
|
if (olddata->hasCallback()) {
|
|
olddata->onUnset();
|
|
bool erased = cur->callbackData_.erase(olddata);
|
|
DCHECK(erased);
|
|
}
|
|
if (safe) {
|
|
// If the caller guarantees thread-safety, then erase the
|
|
// entry in the current version.
|
|
cur->requestData_.erase(token);
|
|
cur->refs_.erase(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);
|
|
newCombined->requestData_.erase(token);
|
|
newCombined->acquireDataRefs();
|
|
}
|
|
return newCombined;
|
|
}
|
|
|
|
RequestContext::StateHazptr::Combined* FOLLY_NULLABLE
|
|
RequestContext::StateHazptr::insertNewData(
|
|
RequestContext::StateHazptr::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
|
|
cur->callbackData_.insert(data.get(), true);
|
|
data->onSet();
|
|
}
|
|
if (data) {
|
|
cur->refs_.insert({data.get(), true});
|
|
data->acquireRef();
|
|
}
|
|
cur->requestData_.insert(token, data.release());
|
|
return newCombined;
|
|
}
|
|
|
|
FOLLY_ALWAYS_INLINE
|
|
bool RequestContext::StateHazptr::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::StateHazptr::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::StateHazptr::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::StateHazptr::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::StateHazptr::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::StateHazptr::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) {
|
|
cur->requestData_.erase(token);
|
|
return;
|
|
}
|
|
if (data->hasCallback()) {
|
|
data->onUnset();
|
|
cur->callbackData_.erase(data);
|
|
}
|
|
replaced = cur;
|
|
cur = new Combined(*replaced);
|
|
cur->requestData_.erase(token);
|
|
cur->acquireDataRefs();
|
|
setCombined(cur);
|
|
} // Unlock mutex_
|
|
DCHECK(data);
|
|
data->releaseRefClearOnly();
|
|
replaced->refs_[data] = false; // Clear reference already released
|
|
DCHECK(replaced);
|
|
replaced->retire();
|
|
}
|
|
|
|
RequestContext::StateHazptr::Combined* RequestContext::StateHazptr::expand(
|
|
RequestContext::StateHazptr::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()
|
|
: useHazptr_(FLAGS_folly_reqctx_use_hazptr),
|
|
rootId_(reinterpret_cast<intptr_t>(this)) {}
|
|
|
|
RequestContext::RequestContext(intptr_t rootid)
|
|
: useHazptr_(FLAGS_folly_reqctx_use_hazptr), 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{});
|
|
}
|
|
|
|
bool RequestContext::doSetContextDataLock(
|
|
const RequestToken& token,
|
|
std::unique_ptr<RequestData>& data,
|
|
DoSetBehaviour behaviour) {
|
|
auto wlock = state_.wlock();
|
|
auto& state = *wlock;
|
|
|
|
auto it = state.requestData_.find(token);
|
|
if (it != state.requestData_.end()) {
|
|
if (behaviour == DoSetBehaviour::SET_IF_ABSENT) {
|
|
return false;
|
|
}
|
|
if (it->second) {
|
|
if (it->second->hasCallback()) {
|
|
it->second->onUnset();
|
|
state.callbackData_.erase(it->second.get());
|
|
}
|
|
it->second.reset(nullptr);
|
|
}
|
|
if (behaviour == DoSetBehaviour::SET) {
|
|
LOG_FIRST_N(WARNING, 1)
|
|
<< "Calling RequestContext::setContextData for "
|
|
<< token.getDebugString() << " but it is already set";
|
|
return true;
|
|
}
|
|
DCHECK(behaviour == DoSetBehaviour::OVERWRITE);
|
|
}
|
|
|
|
if (data && data->hasCallback()) {
|
|
state.callbackData_.insert(data.get());
|
|
data->onSet();
|
|
}
|
|
auto ptr = RequestData::constructPtr(data.release());
|
|
if (it != state.requestData_.end()) {
|
|
it->second = std::move(ptr);
|
|
} else {
|
|
state.requestData_.insert(std::make_pair(token, std::move(ptr)));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void RequestContext::setContextData(
|
|
const RequestToken& token,
|
|
std::unique_ptr<RequestData> data) {
|
|
if (useHazptr()) {
|
|
stateHazptr_.doSetContextData(token, data, DoSetBehaviour::SET, false);
|
|
return;
|
|
}
|
|
doSetContextDataLock(token, data, DoSetBehaviour::SET);
|
|
}
|
|
|
|
bool RequestContext::setContextDataIfAbsent(
|
|
const RequestToken& token,
|
|
std::unique_ptr<RequestData> data) {
|
|
if (useHazptr()) {
|
|
return stateHazptr_.doSetContextData(
|
|
token, data, DoSetBehaviour::SET_IF_ABSENT, false);
|
|
}
|
|
return doSetContextDataLock(token, data, DoSetBehaviour::SET_IF_ABSENT);
|
|
}
|
|
|
|
void RequestContext::overwriteContextDataLock(
|
|
const RequestToken& token,
|
|
std::unique_ptr<RequestData> data) {
|
|
doSetContextDataLock(token, data, DoSetBehaviour::OVERWRITE);
|
|
}
|
|
|
|
void RequestContext::overwriteContextDataHazptr(
|
|
const RequestToken& token,
|
|
std::unique_ptr<RequestData> data,
|
|
bool safe) {
|
|
stateHazptr_.doSetContextData(token, data, DoSetBehaviour::OVERWRITE, safe);
|
|
}
|
|
|
|
bool RequestContext::hasContextData(const RequestToken& val) const {
|
|
if (useHazptr()) {
|
|
return stateHazptr_.hasContextData(val);
|
|
}
|
|
return state_.rlock()->requestData_.count(val);
|
|
}
|
|
|
|
RequestData* FOLLY_NULLABLE
|
|
RequestContext::getContextData(const RequestToken& val) {
|
|
if (useHazptr()) {
|
|
return stateHazptr_.getContextData(val);
|
|
}
|
|
const RequestData::SharedPtr dflt{nullptr};
|
|
return get_ref_default(state_.rlock()->requestData_, val, dflt).get();
|
|
}
|
|
|
|
const RequestData* FOLLY_NULLABLE
|
|
RequestContext::getContextData(const RequestToken& val) const {
|
|
if (useHazptr()) {
|
|
return stateHazptr_.getContextData(val);
|
|
}
|
|
const RequestData::SharedPtr dflt{nullptr};
|
|
return get_ref_default(state_.rlock()->requestData_, val, dflt).get();
|
|
}
|
|
|
|
void RequestContext::onSet() {
|
|
if (useHazptr()) {
|
|
stateHazptr_.onSet();
|
|
return;
|
|
}
|
|
auto rlock = state_.rlock();
|
|
for (const auto& data : rlock->callbackData_) {
|
|
data->onSet();
|
|
}
|
|
}
|
|
|
|
void RequestContext::onUnset() {
|
|
if (useHazptr()) {
|
|
stateHazptr_.onUnset();
|
|
return;
|
|
}
|
|
auto rlock = state_.rlock();
|
|
for (const auto& data : rlock->callbackData_) {
|
|
data->onUnset();
|
|
}
|
|
}
|
|
|
|
void RequestContext::clearContextData(const RequestToken& val) {
|
|
if (useHazptr()) {
|
|
stateHazptr_.clearContextData(val);
|
|
return;
|
|
}
|
|
RequestData::SharedPtr requestData;
|
|
// Delete the RequestData after giving up the wlock just in case one of the
|
|
// RequestData destructors will try to grab the lock again.
|
|
{
|
|
auto ulock = state_.ulock();
|
|
// Need non-const iterators to use under write lock.
|
|
auto& state = ulock.asNonConstUnsafe();
|
|
auto it = state.requestData_.find(val);
|
|
if (it == state.requestData_.end()) {
|
|
return;
|
|
}
|
|
|
|
auto wlock = ulock.moveFromUpgradeToWrite();
|
|
if (it->second && it->second->hasCallback()) {
|
|
it->second->onUnset();
|
|
wlock->callbackData_.erase(it->second.get());
|
|
}
|
|
|
|
requestData = std::move(it->second);
|
|
wlock->requestData_.erase(it);
|
|
}
|
|
}
|
|
|
|
namespace {
|
|
// Execute functor exec for all RequestData in data, which are not in other
|
|
// Similar to std::set_difference but avoid intermediate data structure
|
|
template <typename TData, typename TExec>
|
|
void exec_set_difference(const TData& data, const TData& other, TExec&& exec) {
|
|
auto diter = data.begin();
|
|
auto dend = data.end();
|
|
auto oiter = other.begin();
|
|
auto oend = other.end();
|
|
while (diter != dend) {
|
|
// Order of "if" optimizes for the 2 common cases:
|
|
// 1) empty other, switching to default context
|
|
// 2) identical other, switching to similar context with same callbacks
|
|
if (oiter == oend) {
|
|
exec(*diter);
|
|
++diter;
|
|
} else if (*diter == *oiter) {
|
|
++diter;
|
|
++oiter;
|
|
} else if (*diter < *oiter) {
|
|
exec(*diter);
|
|
++diter;
|
|
} else {
|
|
++oiter;
|
|
}
|
|
}
|
|
}
|
|
} // namespace
|
|
|
|
/* 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());
|
|
|
|
if ((newCtx.get() && newCtx->useHazptr()) ||
|
|
(staticCtx.first.get() && staticCtx.first->useHazptr())) {
|
|
DCHECK(!newCtx.get() || newCtx->useHazptr());
|
|
DCHECK(!staticCtx.first.get() || staticCtx.first->useHazptr());
|
|
return RequestContext::setContextHazptr(newCtx, staticCtx);
|
|
} else {
|
|
return RequestContext::setContextLock(newCtx, staticCtx);
|
|
}
|
|
}
|
|
|
|
FOLLY_ALWAYS_INLINE
|
|
/* static */ std::shared_ptr<RequestContext> RequestContext::setContextLock(
|
|
std::shared_ptr<RequestContext>& newCtx,
|
|
StaticContext& staticCtx) {
|
|
auto curCtx = staticCtx;
|
|
if (newCtx && curCtx.first) {
|
|
// Only call set/unset for all request data that differs
|
|
auto ret = folly::acquireLocked(
|
|
as_const(newCtx->state_), as_const(curCtx.first->state_));
|
|
auto& newLock = std::get<0>(ret);
|
|
auto& curLock = std::get<1>(ret);
|
|
auto& newData = newLock->callbackData_;
|
|
auto& curData = curLock->callbackData_;
|
|
exec_set_difference(
|
|
curData, newData, [](RequestData* data) { data->onUnset(); });
|
|
staticCtx.first = newCtx;
|
|
staticCtx.second = newCtx->rootId_;
|
|
exec_set_difference(
|
|
newData, curData, [](RequestData* data) { data->onSet(); });
|
|
} else {
|
|
if (curCtx.first) {
|
|
curCtx.first->onUnset();
|
|
}
|
|
staticCtx.first = newCtx;
|
|
if (newCtx) {
|
|
staticCtx.second = newCtx->rootId_;
|
|
newCtx->onSet();
|
|
} else {
|
|
staticCtx.second = 0;
|
|
}
|
|
}
|
|
return curCtx.first;
|
|
}
|
|
|
|
FOLLY_ALWAYS_INLINE
|
|
/* static */ std::shared_ptr<RequestContext> RequestContext::setContextHazptr(
|
|
std::shared_ptr<RequestContext>& newCtx,
|
|
StaticContext& staticCtx) {
|
|
auto curCtx = std::move(staticCtx);
|
|
bool checkCur = curCtx.first && curCtx.first->stateHazptr_.combined();
|
|
bool checkNew = newCtx && newCtx->stateHazptr_.combined();
|
|
if (checkCur && checkNew) {
|
|
hazptr_array<2> h;
|
|
auto curc = h[0].get_protected(curCtx.first->stateHazptr_.combined_);
|
|
auto newc = h[1].get_protected(newCtx->stateHazptr_.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();
|
|
}
|
|
}
|
|
staticCtx.first = std::move(newCtx);
|
|
staticCtx.second = staticCtx.first->rootId_;
|
|
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.first) {
|
|
curCtx.first->stateHazptr_.onUnset();
|
|
}
|
|
staticCtx.first = std::move(newCtx);
|
|
if (staticCtx.first) {
|
|
staticCtx.first->stateHazptr_.onSet();
|
|
staticCtx.second = staticCtx.first->rootId_;
|
|
} else {
|
|
staticCtx.second = 0;
|
|
}
|
|
}
|
|
return curCtx.first;
|
|
}
|
|
|
|
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, 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
|