Commit 6472b9cb authored by Maged Michael's avatar Maged Michael Committed by Facebook Github Bot

Request context: Add hazard pointer-based implementation

Summary:
Add hazard-pointer-based implementation to allow concurrent reading during updates. This is done by using lightweight concurrent structures protected by hazard pointers instead of sequential structures protected by read locks.

The implementation is gated by a gflag that is off by default.

Reviewed By: davidtgoldblatt

Differential Revision: D18622072

fbshipit-source-id: 120ba14a7a559883e64a3fdf81b35a26315312c1
parent 1cb1d800
...@@ -15,12 +15,20 @@ ...@@ -15,12 +15,20 @@
*/ */
#include <folly/io/async/Request.h> #include <folly/io/async/Request.h>
#include <folly/experimental/SingleWriterFixedHashMap.h>
#include <folly/synchronization/Hazptr.h>
#include <folly/tracing/StaticTracepoint.h> #include <folly/tracing/StaticTracepoint.h>
#include <glog/logging.h> #include <glog/logging.h>
#include <folly/MapUtil.h> #include <folly/MapUtil.h>
#include <folly/SingletonThreadLocal.h> #include <folly/SingletonThreadLocal.h>
#include <folly/portability/GFlags.h>
DEFINE_bool(
reqctx_use_hazptr,
false,
"RequestContext implementation using hazard pointers");
namespace folly { namespace folly {
...@@ -62,6 +70,19 @@ Synchronized<F14FastMap<std::string, uint32_t>>& RequestToken::getCache() { ...@@ -62,6 +70,19 @@ Synchronized<F14FastMap<std::string, uint32_t>>& RequestToken::getCache() {
return *cache; return *cache;
} }
void RequestData::acquireRef() {
auto rc = keepAliveCounter_.fetch_add(1, std::memory_order_relaxed);
DCHECK_GE(rc, 0);
}
void RequestData::releaseRefDeleteIfNoRefs() {
auto rc = keepAliveCounter_.fetch_sub(1, std::memory_order_acq_rel);
DCHECK_GT(rc, 0);
if (rc == 1) {
delete this;
}
}
void RequestData::DestructPtr::operator()(RequestData* ptr) { void RequestData::DestructPtr::operator()(RequestData* ptr) {
if (ptr) { if (ptr) {
auto keepAliveCounter = auto keepAliveCounter =
...@@ -84,14 +105,368 @@ void RequestData::DestructPtr::operator()(RequestData* ptr) { ...@@ -84,14 +105,368 @@ void RequestData::DestructPtr::operator()(RequestData* ptr) {
return SharedPtr(ptr); return SharedPtr(ptr);
} }
bool RequestContext::doSetContextData( // The Combined struct keeps the two structures for context data
const RequestToken& val, // 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_;
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() {
for (auto it = requestData_.begin(); it != requestData_.end(); ++it) {
auto p = it.value();
if (p) {
p->releaseRefDeleteIfNoRefs();
}
}
}
/* 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() {}
RequestContext::StateHazptr::StateHazptr(const StateHazptr& o) {
Combined* oc = o.combined();
if (oc) {
auto p = new Combined(*oc);
p->acquireDataRefs();
setCombined(p);
}
}
RequestContext::StateHazptr::~StateHazptr() {
batch_.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_batch_tag(&batch_);
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);
olddata->releaseRefDeleteIfNoRefs();
} 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) {
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) {
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;
}
RequestData* 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(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_reqctx_use_hazptr;
}
bool RequestContext::doSetContextDataLock(
const RequestToken& token,
std::unique_ptr<RequestData>& data, std::unique_ptr<RequestData>& data,
DoSetBehaviour behaviour) { DoSetBehaviour behaviour) {
auto wlock = state_.wlock(); auto wlock = state_.wlock();
auto& state = *wlock; auto& state = *wlock;
auto it = state.requestData_.find(val); auto it = state.requestData_.find(token);
if (it != state.requestData_.end()) { if (it != state.requestData_.end()) {
if (behaviour == DoSetBehaviour::SET_IF_ABSENT) { if (behaviour == DoSetBehaviour::SET_IF_ABSENT) {
return false; return false;
...@@ -106,7 +481,7 @@ bool RequestContext::doSetContextData( ...@@ -106,7 +481,7 @@ bool RequestContext::doSetContextData(
if (behaviour == DoSetBehaviour::SET) { if (behaviour == DoSetBehaviour::SET) {
LOG_FIRST_N(WARNING, 1) LOG_FIRST_N(WARNING, 1)
<< "Calling RequestContext::setContextData for " << "Calling RequestContext::setContextData for "
<< val.getDebugString() << " but it is already set"; << token.getDebugString() << " but it is already set";
return true; return true;
} }
DCHECK(behaviour == DoSetBehaviour::OVERWRITE); DCHECK(behaviour == DoSetBehaviour::OVERWRITE);
...@@ -120,45 +495,74 @@ bool RequestContext::doSetContextData( ...@@ -120,45 +495,74 @@ bool RequestContext::doSetContextData(
if (it != state.requestData_.end()) { if (it != state.requestData_.end()) {
it->second = std::move(ptr); it->second = std::move(ptr);
} else { } else {
state.requestData_.insert(std::make_pair(val, std::move(ptr))); state.requestData_.insert(std::make_pair(token, std::move(ptr)));
} }
return true; return true;
} }
void RequestContext::setContextData( void RequestContext::setContextData(
const RequestToken& val, const RequestToken& token,
std::unique_ptr<RequestData> data) { std::unique_ptr<RequestData> data) {
doSetContextData(val, data, DoSetBehaviour::SET); if (useHazptr()) {
stateHazptr_.doSetContextData(token, data, DoSetBehaviour::SET, false);
return;
}
doSetContextDataLock(token, data, DoSetBehaviour::SET);
} }
bool RequestContext::setContextDataIfAbsent( bool RequestContext::setContextDataIfAbsent(
const RequestToken& val, const RequestToken& token,
std::unique_ptr<RequestData> data) { std::unique_ptr<RequestData> data) {
return doSetContextData(val, data, DoSetBehaviour::SET_IF_ABSENT); if (useHazptr()) {
return stateHazptr_.doSetContextData(
token, data, DoSetBehaviour::SET_IF_ABSENT, false);
}
return doSetContextDataLock(token, data, DoSetBehaviour::SET_IF_ABSENT);
} }
void RequestContext::overwriteContextData( void RequestContext::overwriteContextDataLock(
const RequestToken& val, const RequestToken& token,
std::unique_ptr<RequestData> data) { std::unique_ptr<RequestData> data) {
doSetContextData(val, data, DoSetBehaviour::OVERWRITE); 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 { bool RequestContext::hasContextData(const RequestToken& val) const {
if (useHazptr()) {
return stateHazptr_.hasContextData(val);
}
return state_.rlock()->requestData_.count(val); return state_.rlock()->requestData_.count(val);
} }
RequestData* RequestContext::getContextData(const RequestToken& val) { RequestData* FOLLY_NULLABLE
RequestContext::getContextData(const RequestToken& val) {
if (useHazptr()) {
return stateHazptr_.getContextData(val);
}
const RequestData::SharedPtr dflt{nullptr}; const RequestData::SharedPtr dflt{nullptr};
return get_ref_default(state_.rlock()->requestData_, val, dflt).get(); return get_ref_default(state_.rlock()->requestData_, val, dflt).get();
} }
const RequestData* RequestContext::getContextData( const RequestData* FOLLY_NULLABLE
const RequestToken& val) const { RequestContext::getContextData(const RequestToken& val) const {
if (useHazptr()) {
return stateHazptr_.getContextData(val);
}
const RequestData::SharedPtr dflt{nullptr}; const RequestData::SharedPtr dflt{nullptr};
return get_ref_default(state_.rlock()->requestData_, val, dflt).get(); return get_ref_default(state_.rlock()->requestData_, val, dflt).get();
} }
void RequestContext::onSet() { void RequestContext::onSet() {
if (useHazptr()) {
stateHazptr_.onSet();
return;
}
auto rlock = state_.rlock(); auto rlock = state_.rlock();
for (const auto& data : rlock->callbackData_) { for (const auto& data : rlock->callbackData_) {
data->onSet(); data->onSet();
...@@ -166,6 +570,10 @@ void RequestContext::onSet() { ...@@ -166,6 +570,10 @@ void RequestContext::onSet() {
} }
void RequestContext::onUnset() { void RequestContext::onUnset() {
if (useHazptr()) {
stateHazptr_.onUnset();
return;
}
auto rlock = state_.rlock(); auto rlock = state_.rlock();
for (const auto& data : rlock->callbackData_) { for (const auto& data : rlock->callbackData_) {
data->onUnset(); data->onUnset();
...@@ -173,6 +581,10 @@ void RequestContext::onUnset() { ...@@ -173,6 +581,10 @@ void RequestContext::onUnset() {
} }
void RequestContext::clearContextData(const RequestToken& val) { void RequestContext::clearContextData(const RequestToken& val) {
if (useHazptr()) {
stateHazptr_.clearContextData(val);
return;
}
RequestData::SharedPtr requestData; RequestData::SharedPtr requestData;
// Delete the RequestData after giving up the wlock just in case one of the // Delete the RequestData after giving up the wlock just in case one of the
// RequestData destructors will try to grab the lock again. // RequestData destructors will try to grab the lock again.
...@@ -225,12 +637,12 @@ void exec_set_difference(const TData& data, const TData& other, TExec&& exec) { ...@@ -225,12 +637,12 @@ void exec_set_difference(const TData& data, const TData& other, TExec&& exec) {
} }
} // namespace } // namespace
std::shared_ptr<RequestContext> RequestContext::setContext( /* static */ std::shared_ptr<RequestContext> RequestContext::setContext(
std::shared_ptr<RequestContext> const& newCtx) { std::shared_ptr<RequestContext> const& newCtx) {
return setContext(copy(newCtx)); return setContext(copy(newCtx));
} }
std::shared_ptr<RequestContext> RequestContext::setContext( /* static */ std::shared_ptr<RequestContext> RequestContext::setContext(
std::shared_ptr<RequestContext>&& newCtx_) { std::shared_ptr<RequestContext>&& newCtx_) {
auto newCtx = std::move(newCtx_); // enforce that it is really moved-from auto newCtx = std::move(newCtx_); // enforce that it is really moved-from
...@@ -242,6 +654,20 @@ std::shared_ptr<RequestContext> RequestContext::setContext( ...@@ -242,6 +654,20 @@ std::shared_ptr<RequestContext> RequestContext::setContext(
FOLLY_SDT( FOLLY_SDT(
folly, request_context_switch_before, staticCtx.get(), newCtx.get()); folly, request_context_switch_before, staticCtx.get(), newCtx.get());
if ((newCtx.get() && newCtx->useHazptr()) ||
(staticCtx.get() && staticCtx->useHazptr())) {
DCHECK(!newCtx.get() || newCtx->useHazptr());
DCHECK(!staticCtx.get() || staticCtx->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,
std::shared_ptr<RequestContext>& staticCtx) {
auto curCtx = staticCtx; auto curCtx = staticCtx;
if (newCtx && curCtx) { if (newCtx && curCtx) {
// Only call set/unset for all request data that differs // Only call set/unset for all request data that differs
...@@ -268,6 +694,46 @@ std::shared_ptr<RequestContext> RequestContext::setContext( ...@@ -268,6 +694,46 @@ std::shared_ptr<RequestContext> RequestContext::setContext(
return curCtx; return curCtx;
} }
FOLLY_ALWAYS_INLINE
/* static */ std::shared_ptr<RequestContext> RequestContext::setContextHazptr(
std::shared_ptr<RequestContext>& newCtx,
std::shared_ptr<RequestContext>& staticCtx) {
auto curCtx = std::move(staticCtx);
bool checkCur = curCtx && curCtx->stateHazptr_.combined();
bool checkNew = newCtx && newCtx->stateHazptr_.combined();
if (checkCur && checkNew) {
hazptr_array<2> h;
auto curc = h[0].get_protected(curCtx->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 = std::move(newCtx);
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->stateHazptr_.onUnset();
}
staticCtx = std::move(newCtx);
if (staticCtx) {
staticCtx->stateHazptr_.onSet();
}
}
return curCtx;
}
std::shared_ptr<RequestContext>& RequestContext::getStaticContext() { std::shared_ptr<RequestContext>& RequestContext::getStaticContext() {
using SingletonT = SingletonThreadLocal<std::shared_ptr<RequestContext>>; using SingletonT = SingletonThreadLocal<std::shared_ptr<RequestContext>>;
return SingletonT::get(); return SingletonT::get();
......
...@@ -16,12 +16,15 @@ ...@@ -16,12 +16,15 @@
#pragma once #pragma once
#include <memory>
#include <string>
#include <folly/Synchronized.h> #include <folly/Synchronized.h>
#include <folly/container/F14Map.h> #include <folly/container/F14Map.h>
#include <folly/sorted_vector_types.h> #include <folly/sorted_vector_types.h>
#include <folly/synchronization/Hazptr.h>
#include <atomic>
#include <memory>
#include <mutex>
#include <string>
namespace folly { namespace folly {
...@@ -32,6 +35,7 @@ namespace folly { ...@@ -32,6 +35,7 @@ namespace folly {
*/ */
class RequestToken { class RequestToken {
public: public:
RequestToken() = default;
explicit RequestToken(const std::string& str); explicit RequestToken(const std::string& str);
bool operator==(const RequestToken& other) const { bool operator==(const RequestToken& other) const {
...@@ -62,6 +66,18 @@ struct hash<folly::RequestToken> { ...@@ -62,6 +66,18 @@ struct hash<folly::RequestToken> {
namespace folly { namespace folly {
// - A runtime flag GFLAGS_reqctx_use_hazptr determines the
// implementation of RequestContext.
// - The flag false implementation uses sequential data structures
// protected by a read-write lock.
// - The flag true implementation uses single-writer multi-readers
// data structures protected by hazard pointers for readers and a
// lock for writers.
// - Each RequestContext instances contains a bool member useHazptr_
// (readable by a public member function useHazptr()) that indicates
// the implementation of the instance depending on the value of the
// GFLAG at instance construction time..
// Some request context that follows an async request through a process // Some request context that follows an async request through a process
// Everything in the context must be thread safe // Everything in the context must be thread safe
...@@ -83,6 +99,10 @@ class RequestData { ...@@ -83,6 +99,10 @@ class RequestData {
// instance overrides the hasCallback method to return true otherwise // instance overrides the hasCallback method to return true otherwise
// the callback will not be executed // the callback will not be executed
virtual void onUnset() {} virtual void onUnset() {}
// For debugging
int refCount() {
return keepAliveCounter_.load(std::memory_order_acquire);
}
private: private:
// Start shallow copy implementation details: // Start shallow copy implementation details:
...@@ -93,6 +113,12 @@ class RequestData { ...@@ -93,6 +113,12 @@ class RequestData {
friend class RequestContext; friend class RequestContext;
// Reference-counting functions used by the hazptr-based implementation.
// Increment the reference count
void acquireRef();
// Decrement the reference count and delete if zero
void releaseRefDeleteIfNoRefs();
// Unique ptr with custom destructor, decrement the counter // Unique ptr with custom destructor, decrement the counter
// and only free if 0 // and only free if 0
struct DestructPtr { struct DestructPtr {
...@@ -121,6 +147,8 @@ class RequestData { ...@@ -121,6 +147,8 @@ class RequestData {
// copied between threads. // copied between threads.
class RequestContext { class RequestContext {
public: public:
RequestContext();
// Create a unique request context for this request. // Create a unique request context for this request.
// It will be passed between queues / threads (where implemented), // It will be passed between queues / threads (where implemented),
// so it should be valid for the lifetime of the request. // so it should be valid for the lifetime of the request.
...@@ -142,7 +170,7 @@ class RequestContext { ...@@ -142,7 +170,7 @@ class RequestContext {
// used, will print a warning message for the first time, clear the existing // used, will print a warning message for the first time, clear the existing
// RequestData instance for "val", and **not** add "data". // RequestData instance for "val", and **not** add "data".
void setContextData( void setContextData(
const RequestToken& val, const RequestToken& token,
std::unique_ptr<RequestData> data); std::unique_ptr<RequestData> data);
void setContextData( void setContextData(
const std::string& val, const std::string& val,
...@@ -154,7 +182,7 @@ class RequestContext { ...@@ -154,7 +182,7 @@ class RequestContext {
// string identifier "val". If the same string identifier has already been // string identifier "val". If the same string identifier has already been
// used, return false and do nothing. Otherwise add "data" and return true. // used, return false and do nothing. Otherwise add "data" and return true.
bool setContextDataIfAbsent( bool setContextDataIfAbsent(
const RequestToken& val, const RequestToken& token,
std::unique_ptr<RequestData> data); std::unique_ptr<RequestData> data);
bool setContextDataIfAbsent( bool setContextDataIfAbsent(
const std::string& val, const std::string& val,
...@@ -163,22 +191,22 @@ class RequestContext { ...@@ -163,22 +191,22 @@ class RequestContext {
} }
// Remove the RequestData instance with string identifier "val", if it exists. // Remove the RequestData instance with string identifier "val", if it exists.
void clearContextData(const RequestToken& val); void clearContextData(const RequestToken& token);
void clearContextData(const std::string& val) { void clearContextData(const std::string& val) {
clearContextData(RequestToken(val)); clearContextData(RequestToken(val));
} }
// Returns true if and only if the RequestData instance with string identifier // Returns true if and only if the RequestData instance with string identifier
// "val" exists in this RequestContext instnace. // "val" exists in this RequestContext instnace.
bool hasContextData(const RequestToken& val) const; bool hasContextData(const RequestToken& token) const;
bool hasContextData(const std::string& val) const { bool hasContextData(const std::string& val) const {
return hasContextData(RequestToken(val)); return hasContextData(RequestToken(val));
} }
// Get (constant) raw pointer of the RequestData instance with string // Get (constant) raw pointer of the RequestData instance with string
// identifier "val" if it exists, otherwise returns null pointer. // identifier "val" if it exists, otherwise returns null pointer.
RequestData* getContextData(const RequestToken& val); RequestData* getContextData(const RequestToken& token);
const RequestData* getContextData(const RequestToken& val) const; const RequestData* getContextData(const RequestToken& token) const;
RequestData* getContextData(const std::string& val) { RequestData* getContextData(const std::string& val) {
return getContextData(RequestToken(val)); return getContextData(RequestToken(val));
} }
...@@ -189,6 +217,11 @@ class RequestContext { ...@@ -189,6 +217,11 @@ class RequestContext {
void onSet(); void onSet();
void onUnset(); void onUnset();
// useHazptr
FOLLY_ALWAYS_INLINE bool useHazptr() const {
return useHazptr_;
}
// The following API is used to pass the context through queues / threads. // The following API is used to pass the context through queues / threads.
// saveContext is called to get a shared_ptr to the context, and // saveContext is called to get a shared_ptr to the context, and
// setContext is used to reset it on the other side of the queue. // setContext is used to reset it on the other side of the queue.
...@@ -211,6 +244,13 @@ class RequestContext { ...@@ -211,6 +244,13 @@ class RequestContext {
private: private:
static std::shared_ptr<RequestContext>& getStaticContext(); static std::shared_ptr<RequestContext>& getStaticContext();
static std::shared_ptr<RequestContext> setContextLock(
std::shared_ptr<RequestContext>& newCtx,
std::shared_ptr<RequestContext>& staticCtx);
static std::shared_ptr<RequestContext> setContextHazptr(
std::shared_ptr<RequestContext>& newCtx,
std::shared_ptr<RequestContext>& staticCtx);
// Start shallow copy guard implementation details: // Start shallow copy guard implementation details:
// All methods are private to encourage proper use // All methods are private to encourage proper use
friend struct ShallowCopyRequestContextScopeGuard; friend struct ShallowCopyRequestContextScopeGuard;
...@@ -221,33 +261,62 @@ class RequestContext { ...@@ -221,33 +261,62 @@ class RequestContext {
// Similar to setContextData, except it overwrites the data // Similar to setContextData, except it overwrites the data
// if already set (instead of warn + reset ptr). // if already set (instead of warn + reset ptr).
void overwriteContextData( void overwriteContextDataLock(
const RequestToken& val, const RequestToken& token,
std::unique_ptr<RequestData> data); std::unique_ptr<RequestData> data);
void overwriteContextData( void overwriteContextDataLock(
const std::string& val, const std::string& val,
std::unique_ptr<RequestData> data) { std::unique_ptr<RequestData> data) {
overwriteContextData(RequestToken(val), std::move(data)); overwriteContextDataLock(RequestToken(val), std::move(data));
} }
// End shallow copy guard // End shallow copy guard
// For functions with a parameter safe, if safe is true then the
// caller guarantees that there are no concurrent readers or writers
// accessing the structure.
void overwriteContextDataHazptr(
const RequestToken& token,
std::unique_ptr<RequestData> data,
bool safe = false);
void overwriteContextDataHazptr(
const std::string& val,
std::unique_ptr<RequestData> data,
bool safe = false) {
overwriteContextDataHazptr(RequestToken(val), std::move(data), safe);
}
enum class DoSetBehaviour { enum class DoSetBehaviour {
SET, SET,
SET_IF_ABSENT, SET_IF_ABSENT,
OVERWRITE, OVERWRITE,
}; };
bool doSetContextData( bool doSetContextDataLock(
const RequestToken& val, const RequestToken& token,
std::unique_ptr<RequestData>& data, std::unique_ptr<RequestData>& data,
DoSetBehaviour behaviour); DoSetBehaviour behaviour);
bool doSetContextData( bool doSetContextDataLock(
const std::string& val, const std::string& val,
std::unique_ptr<RequestData>& data, std::unique_ptr<RequestData>& data,
DoSetBehaviour behaviour) { DoSetBehaviour behaviour) {
return doSetContextData(RequestToken(val), data, behaviour); return doSetContextDataLock(RequestToken(val), data, behaviour);
}
bool doSetContextDataHazptr(
const RequestToken& token,
std::unique_ptr<RequestData>& data,
DoSetBehaviour behaviour,
bool safe = false);
bool doSetContextDataHazptr(
const std::string& val,
std::unique_ptr<RequestData>& data,
DoSetBehaviour behaviour,
bool safe = false) {
return doSetContextDataHazptr(RequestToken(val), data, behaviour, safe);
} }
// State immplementation with sequential data structures protected by a
// read-write locks.
struct State { struct State {
// This must be optimized for lookup, its hot path is getContextData // This must be optimized for lookup, its hot path is getContextData
// Efficiency of copying the container also matters in setShallowCopyContext // Efficiency of copying the container also matters in setShallowCopyContext
...@@ -258,6 +327,67 @@ class RequestContext { ...@@ -258,6 +327,67 @@ class RequestContext {
sorted_vector_set<RequestData*> callbackData_; sorted_vector_set<RequestData*> callbackData_;
}; };
folly::Synchronized<State> state_; folly::Synchronized<State> state_;
// State implementation with single-writer multi-reader data
// structures protected by hazard pointers for readers and a lock
// for writers.
struct StateHazptr {
// Hazard pointer-protected combined structure for request data
// and callbacks.
struct Combined;
hazptr_obj_batch<> batch_; // For destruction order
std::atomic<Combined*> combined_{nullptr};
std::mutex mutex_;
StateHazptr();
StateHazptr(const StateHazptr& o);
StateHazptr(StateHazptr&&) = delete;
StateHazptr& operator=(const StateHazptr&) = delete;
StateHazptr& operator=(StateHazptr&&) = delete;
~StateHazptr();
Combined* combined() const;
Combined* ensureCombined(); // Lazy allocation if needed
void setCombined(Combined* combined);
Combined* expand(Combined* combined);
bool doSetContextData(
const RequestToken& token,
std::unique_ptr<RequestData>& data,
DoSetBehaviour behaviour,
bool safe);
bool hasContextData(const RequestToken& token) const;
RequestData* getContextData(const RequestToken& token);
const RequestData* getContextData(const RequestToken& token) const;
void onSet();
void onUnset();
void clearContextData(const RequestToken& token);
private:
struct SetContextDataResult {
bool changed; // Changes were made
bool unexpected; // Update was unexpected
Combined* replaced; // The combined structure was replaced
};
SetContextDataResult doSetContextDataHelper(
const RequestToken& token,
std::unique_ptr<RequestData>& data,
DoSetBehaviour behaviour,
bool safe);
Combined* eraseOldData(
Combined* combined,
const RequestToken& token,
RequestData* oldData,
bool safe);
Combined* insertNewData(
Combined* combined,
const RequestToken& token,
std::unique_ptr<RequestData>& data,
bool found);
}; // StateHazptr
StateHazptr stateHazptr_;
bool useHazptr_;
}; };
/** /**
...@@ -310,16 +440,26 @@ struct ShallowCopyRequestContextScopeGuard { ...@@ -310,16 +440,26 @@ struct ShallowCopyRequestContextScopeGuard {
* "clearRequestData" then "setRequestData" after the guard. * "clearRequestData" then "setRequestData" after the guard.
*/ */
ShallowCopyRequestContextScopeGuard( ShallowCopyRequestContextScopeGuard(
const RequestToken& val, const RequestToken& token,
std::unique_ptr<RequestData> data) std::unique_ptr<RequestData> data)
: ShallowCopyRequestContextScopeGuard() { : ShallowCopyRequestContextScopeGuard() {
RequestContext::get()->overwriteContextData(val, std::move(data)); auto ctx = RequestContext::get();
if (ctx->useHazptr()) {
ctx->overwriteContextDataHazptr(token, std::move(data), true);
} else {
ctx->overwriteContextDataLock(token, std::move(data));
}
} }
ShallowCopyRequestContextScopeGuard( ShallowCopyRequestContextScopeGuard(
const std::string& val, const std::string& val,
std::unique_ptr<RequestData> data) std::unique_ptr<RequestData> data)
: ShallowCopyRequestContextScopeGuard() { : ShallowCopyRequestContextScopeGuard() {
RequestContext::get()->overwriteContextData(val, std::move(data)); auto ctx = RequestContext::get();
if (ctx->useHazptr()) {
ctx->overwriteContextDataHazptr(val, std::move(data), true);
} else {
ctx->overwriteContextDataLock(val, std::move(data));
}
} }
~ShallowCopyRequestContextScopeGuard() { ~ShallowCopyRequestContextScopeGuard() {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment