Commit e432bbcd authored by Dave Watson's avatar Dave Watson Committed by Facebook Github Bot

RequestToken

Summary:
Introduce a RequestToken class to RequestContext.  Map indicies are now uint32_t.

New interfaces are exposed using RequestToken, previous std::string interface forwards
to new interface.  This is a regression for the previous std::string interface since it
must do two hash lookups now, however, the new interface is faster.

Reviewed By: A5he

Differential Revision: D9197415

fbshipit-source-id: ffb8be51c609233fecfdff205a3ea4d4bb68de4b
parent d539094f
...@@ -24,6 +24,46 @@ ...@@ -24,6 +24,46 @@
namespace folly { namespace folly {
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<std::unordered_map<std::string, uint32_t>>&
RequestToken::getCache() {
static Indestructible<Synchronized<std::unordered_map<std::string, uint32_t>>>
cache;
return *cache;
}
void RequestData::DestructPtr::operator()(RequestData* ptr) { void RequestData::DestructPtr::operator()(RequestData* ptr) {
if (ptr) { if (ptr) {
auto keepAliveCounter = auto keepAliveCounter =
...@@ -47,7 +87,7 @@ void RequestData::DestructPtr::operator()(RequestData* ptr) { ...@@ -47,7 +87,7 @@ void RequestData::DestructPtr::operator()(RequestData* ptr) {
} }
bool RequestContext::doSetContextData( bool RequestContext::doSetContextData(
const std::string& val, const RequestToken& val,
std::unique_ptr<RequestData>& data, std::unique_ptr<RequestData>& data,
DoSetBehaviour behaviour) { DoSetBehaviour behaviour) {
auto ulock = state_.ulock(); auto ulock = state_.ulock();
...@@ -58,8 +98,9 @@ bool RequestContext::doSetContextData( ...@@ -58,8 +98,9 @@ bool RequestContext::doSetContextData(
if (behaviour == DoSetBehaviour::SET_IF_ABSENT) { if (behaviour == DoSetBehaviour::SET_IF_ABSENT) {
return false; return false;
} else if (behaviour == DoSetBehaviour::SET) { } else if (behaviour == DoSetBehaviour::SET) {
LOG_FIRST_N(WARNING, 1) << "Calling RequestContext::setContextData for " LOG_FIRST_N(WARNING, 1)
<< val << " but it is already set"; << "Calling RequestContext::setContextData for "
<< val.getDebugString() << " but it is already set";
} }
conflict = true; conflict = true;
} }
...@@ -88,34 +129,34 @@ bool RequestContext::doSetContextData( ...@@ -88,34 +129,34 @@ bool RequestContext::doSetContextData(
} }
void RequestContext::setContextData( void RequestContext::setContextData(
const std::string& val, const RequestToken& val,
std::unique_ptr<RequestData> data) { std::unique_ptr<RequestData> data) {
doSetContextData(val, data, DoSetBehaviour::SET); doSetContextData(val, data, DoSetBehaviour::SET);
} }
bool RequestContext::setContextDataIfAbsent( bool RequestContext::setContextDataIfAbsent(
const std::string& val, const RequestToken& val,
std::unique_ptr<RequestData> data) { std::unique_ptr<RequestData> data) {
return doSetContextData(val, data, DoSetBehaviour::SET_IF_ABSENT); return doSetContextData(val, data, DoSetBehaviour::SET_IF_ABSENT);
} }
void RequestContext::overwriteContextData( void RequestContext::overwriteContextData(
const std::string& val, const RequestToken& val,
std::unique_ptr<RequestData> data) { std::unique_ptr<RequestData> data) {
doSetContextData(val, data, DoSetBehaviour::OVERWRITE); doSetContextData(val, data, DoSetBehaviour::OVERWRITE);
} }
bool RequestContext::hasContextData(const std::string& val) const { bool RequestContext::hasContextData(const RequestToken& val) const {
return state_.rlock()->requestData_.count(val); return state_.rlock()->requestData_.count(val);
} }
RequestData* RequestContext::getContextData(const std::string& val) { RequestData* RequestContext::getContextData(const RequestToken& 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* RequestContext::getContextData(
const std::string& val) const { const RequestToken& val) const {
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();
} }
...@@ -134,7 +175,7 @@ void RequestContext::onUnset() { ...@@ -134,7 +175,7 @@ void RequestContext::onUnset() {
} }
} }
void RequestContext::clearContextData(const std::string& val) { void RequestContext::clearContextData(const RequestToken& val) {
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.
......
...@@ -25,6 +25,43 @@ ...@@ -25,6 +25,43 @@
namespace folly { namespace folly {
/*
* A token to be used to fetch data from RequestContext.
* Generally you will want this to be a static, created only once using a
* string, and then only copied. The string constructor is expensive.
*/
class RequestToken {
public:
explicit RequestToken(const std::string& str);
bool operator==(const RequestToken& other) const {
return token_ == other.token_;
}
// Slow, use only for debug log messages.
std::string getDebugString() const;
friend struct std::hash<folly::RequestToken>;
private:
static Synchronized<std::unordered_map<std::string, uint32_t>>& getCache();
uint32_t token_;
};
} // namespace folly
namespace std {
template <>
struct hash<folly::RequestToken> {
size_t operator()(const folly::RequestToken& token) const {
return hash<uint32_t>()(token.token_);
}
};
} // namespace std
namespace folly {
// 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
...@@ -96,27 +133,49 @@ class RequestContext { ...@@ -96,27 +133,49 @@ 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 std::string& val, const RequestToken& val,
std::unique_ptr<RequestData> data); std::unique_ptr<RequestData> data);
void setContextData(
const std::string& val,
std::unique_ptr<RequestData> data) {
setContextData(RequestToken(val), std::move(data));
}
// Add RequestData instance "data" to this RequestContext instance, with // Add RequestData instance "data" to this RequestContext instance, with
// 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 std::string& val, const RequestToken& val,
std::unique_ptr<RequestData> data); std::unique_ptr<RequestData> data);
bool setContextDataIfAbsent(
const std::string& val,
std::unique_ptr<RequestData> data) {
return setContextDataIfAbsent(RequestToken(val), std::move(data));
}
// 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 std::string& val); void clearContextData(const RequestToken& val);
void clearContextData(const std::string& 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 std::string& val) const; bool hasContextData(const RequestToken& val) const;
bool hasContextData(const std::string& val) const {
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 std::string& val); RequestData* getContextData(const RequestToken& val);
const RequestData* getContextData(const std::string& val) const; const RequestData* getContextData(const RequestToken& val) const;
RequestData* getContextData(const std::string& val) {
return getContextData(RequestToken(val));
}
const RequestData* getContextData(const std::string& val) const {
return getContextData(RequestToken(val));
}
void onSet(); void onSet();
void onUnset(); void onUnset();
...@@ -152,8 +211,13 @@ class RequestContext { ...@@ -152,8 +211,13 @@ 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 overwriteContextData(
const std::string& val, const RequestToken& val,
std::unique_ptr<RequestData> data); std::unique_ptr<RequestData> data);
void overwriteContextData(
const std::string& val,
std::unique_ptr<RequestData> data) {
overwriteContextData(RequestToken(val), std::move(data));
}
// End shallow copy guard // End shallow copy guard
enum class DoSetBehaviour { enum class DoSetBehaviour {
...@@ -163,14 +227,20 @@ class RequestContext { ...@@ -163,14 +227,20 @@ class RequestContext {
}; };
bool doSetContextData( bool doSetContextData(
const std::string& val, const RequestToken& val,
std::unique_ptr<RequestData>& data, std::unique_ptr<RequestData>& data,
DoSetBehaviour behaviour); DoSetBehaviour behaviour);
bool doSetContextData(
const std::string& val,
std::unique_ptr<RequestData>& data,
DoSetBehaviour behaviour) {
return doSetContextData(RequestToken(val), data, behaviour);
}
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
F14FastMap<std::string, RequestData::SharedPtr> requestData_; F14FastMap<RequestToken, RequestData::SharedPtr> requestData_;
// This must be optimized for iteration, its hot path is setContext // This must be optimized for iteration, its hot path is setContext
// We also use the fact that it's ordered to efficiently compute // We also use the fact that it's ordered to efficiently compute
// the difference with previous context // the difference with previous context
...@@ -226,6 +296,12 @@ struct ShallowCopyRequestContextScopeGuard { ...@@ -226,6 +296,12 @@ struct ShallowCopyRequestContextScopeGuard {
* Helper constructor which is a more efficient equivalent to * Helper constructor which is a more efficient equivalent to
* "clearRequestData" then "setRequestData" after the guard. * "clearRequestData" then "setRequestData" after the guard.
*/ */
ShallowCopyRequestContextScopeGuard(
const RequestToken& val,
std::unique_ptr<RequestData> data)
: ShallowCopyRequestContextScopeGuard() {
RequestContext::get()->overwriteContextData(val, std::move(data));
}
ShallowCopyRequestContextScopeGuard( ShallowCopyRequestContextScopeGuard(
const std::string& val, const std::string& val,
std::unique_ptr<RequestData> data) std::unique_ptr<RequestData> data)
......
...@@ -23,6 +23,8 @@ ...@@ -23,6 +23,8 @@
using namespace folly; using namespace folly;
RequestToken testtoken("test");
class TestData : public RequestData { class TestData : public RequestData {
public: public:
explicit TestData(int data) : data_(data) {} explicit TestData(int data) : data_(data) {}
...@@ -108,9 +110,9 @@ TEST_F(RequestContextTest, SimpleTest) { ...@@ -108,9 +110,9 @@ TEST_F(RequestContextTest, SimpleTest) {
RequestContext::get()->setContextData("test", std::make_unique<TestData>(10)); RequestContext::get()->setContextData("test", std::make_unique<TestData>(10));
base.runInEventBaseThread([&]() { base.runInEventBaseThread([&]() {
EXPECT_TRUE(RequestContext::get() != nullptr); EXPECT_TRUE(RequestContext::get() != nullptr);
auto data = auto data = dynamic_cast<TestData*>(
dynamic_cast<TestData*>(RequestContext::get()->getContextData("test")) RequestContext::get()->getContextData(testtoken))
->data_; ->data_;
EXPECT_EQ(10, data); EXPECT_EQ(10, data);
base.terminateLoopSoon(); base.terminateLoopSoon();
}); });
...@@ -163,7 +165,7 @@ TEST_F(RequestContextTest, setIfAbsentTest) { ...@@ -163,7 +165,7 @@ TEST_F(RequestContextTest, setIfAbsentTest) {
"test", std::make_unique<TestData>(20))); "test", std::make_unique<TestData>(20)));
EXPECT_EQ( EXPECT_EQ(
10, 10,
dynamic_cast<TestData*>(RequestContext::get()->getContextData("test")) dynamic_cast<TestData*>(RequestContext::get()->getContextData(testtoken))
->data_); ->data_);
EXPECT_TRUE(RequestContext::get()->setContextDataIfAbsent( EXPECT_TRUE(RequestContext::get()->setContextDataIfAbsent(
...@@ -189,8 +191,8 @@ TEST_F(RequestContextTest, testSetUnset) { ...@@ -189,8 +191,8 @@ TEST_F(RequestContextTest, testSetUnset) {
// Override RequestContext // Override RequestContext
RequestContext::create(); RequestContext::create();
auto ctx2 = RequestContext::saveContext(); auto ctx2 = RequestContext::saveContext();
ctx2->setContextData("test", std::make_unique<TestData>(20)); ctx2->setContextData(testtoken, std::make_unique<TestData>(20));
auto testData2 = dynamic_cast<TestData*>(ctx2->getContextData("test")); auto testData2 = dynamic_cast<TestData*>(ctx2->getContextData(testtoken));
// onSet called in setContextData // onSet called in setContextData
EXPECT_EQ(1, testData2->set_); EXPECT_EQ(1, testData2->set_);
...@@ -229,7 +231,7 @@ TEST_F(RequestContextTest, deadlockTest) { ...@@ -229,7 +231,7 @@ TEST_F(RequestContextTest, deadlockTest) {
RequestContext::get()->setContextData( RequestContext::get()->setContextData(
"test", std::make_unique<DeadlockTestData>("test2")); "test", std::make_unique<DeadlockTestData>("test2"));
RequestContext::get()->clearContextData("test"); RequestContext::get()->clearContextData(testtoken);
} }
// A common use case is to use set/unset to maintain a thread global // A common use case is to use set/unset to maintain a thread global
......
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