/* * 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. */ #pragma once #include #include #include #include #include #include #include #include #include DECLARE_bool(folly_reqctx_use_hazptr); 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: RequestToken() = default; 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; private: static Synchronized>& getCache(); uint32_t token_; }; } // namespace folly namespace std { template <> struct hash { size_t operator()(const folly::RequestToken& token) const { return hash()(token.token_); } }; } // namespace std 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 // Everything in the context must be thread safe class RequestData { public: virtual ~RequestData() = default; // Avoid calling RequestContext::setContextData, setContextDataIfAbsent, or // clearContextData from these callbacks. Doing so will cause deadlock. We // could fix these deadlocks, but only at significant performance penalty, so // just don't do it! // hasCallback() applies only to onSet() and onUnset(). // onClear() is always executed exactly once. virtual bool hasCallback() = 0; // Callback executed when setting RequestContext. Make sure your RequestData // instance overrides the hasCallback method to return true otherwise // the callback will not be executed virtual void onSet() {} // Callback executed when unsetting RequestContext. Make sure your RequestData // instance overrides the hasCallback method to return true otherwise // the callback will not be executed virtual void onUnset() {} // Callback executed exactly once upon the release of the last // reference to the request data (as a result of either a call to // clearContextData or the destruction of a request context that // contains a reference to the data). It can be overridden in // derived classes. There may be concurrent executions of onSet() // and onUnset() with that of onClear(). virtual void onClear() {} // For debugging int refCount() { return keepAliveCounter_.load(std::memory_order_acquire); } private: // Start shallow copy implementation details: // For efficiency, RequestContext provides a raw ptr interface. // To support shallow copy, we need a shared ptr. // To keep it as safe as possible (even if a raw ptr is passed back), // the counter lives directly in RequestData. friend class RequestContext; static constexpr int kDeleteCount = 0x1; static constexpr int kClearCount = 0x1000; // Reference-counting functions used by the hazptr-based implementation. // Increment the reference count void acquireRef(); // Decrement the reference count. Clear only if last. void releaseRefClearOnly(); // Decrement the reference count. Delete only if last. void releaseRefDeleteOnly(); // Decrement the reference count. Clear and delete if last. void releaseRefClearDelete(); // Unique ptr with custom destructor, decrement the counter // and only free if 0 struct DestructPtr { void operator()(RequestData* ptr); }; struct SharedPtr : public std::unique_ptr { SharedPtr() = default; using std::unique_ptr::unique_ptr; SharedPtr(const SharedPtr& other) : SharedPtr(constructPtr(other.get())) {} SharedPtr& operator=(const SharedPtr& other) { return operator=(constructPtr(other.get())); } SharedPtr(SharedPtr&&) = default; SharedPtr& operator=(SharedPtr&&) = default; }; // Initialize the pseudo-shared ptr, increment the counter static SharedPtr constructPtr(RequestData* ptr); std::atomic keepAliveCounter_{0}; // End shallow copy }; // If you do not call create() to create a unique request context, // this default request context will always be returned, and is never // copied between threads. class RequestContext { public: RequestContext(); RequestContext(RequestContext&& ctx) = delete; RequestContext& operator=(const RequestContext&) = delete; RequestContext& operator=(RequestContext&&) = delete; // copy ctor is disabled, use copyAsRoot/copyAsChild instead. static std::shared_ptr copyAsRoot( const RequestContext& ctx, intptr_t rootid); static std::shared_ptr copyAsChild(const RequestContext& ctx); // Create a unique request context for this request. // It will be passed between queues / threads (where implemented), // so it should be valid for the lifetime of the request. static void create() { setContext(std::make_shared()); } // Get the current context. static RequestContext* get(); intptr_t getRootId() const { return rootId_; } struct RootIdInfo { intptr_t id; std::thread::id tid; uint64_t tidOS; }; static std::vector getRootIdsFromAllThreads(); // The following APIs are used to add, remove and access RequestData instance // in the RequestContext instance, normally used for per-RequestContext // tracking or callback on set and unset. These APIs are Thread-safe. // These APIs are performance sensitive, so please ask if you need help // profiling any use of these APIs. // Add RequestData instance "data" to this RequestContext instance, with // string identifier "val". If the same string identifier has already been // used, will print a warning message for the first time, clear the existing // RequestData instance for "val", and **not** add "data". void setContextData( const RequestToken& token, std::unique_ptr data); void setContextData( const std::string& val, std::unique_ptr data) { setContextData(RequestToken(val), std::move(data)); } // Add RequestData instance "data" to this RequestContext instance, with // string identifier "val". If the same string identifier has already been // used, return false and do nothing. Otherwise add "data" and return true. bool setContextDataIfAbsent( const RequestToken& token, std::unique_ptr data); bool setContextDataIfAbsent( const std::string& val, std::unique_ptr data) { return setContextDataIfAbsent(RequestToken(val), std::move(data)); } // Remove the RequestData instance with string identifier "val", if it exists. 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 // "val" exists in this RequestContext instnace. 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 // identifier "val" if it exists, otherwise returns null pointer. RequestData* getContextData(const RequestToken& val); 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 onUnset(); // useHazptr FOLLY_ALWAYS_INLINE bool useHazptr() const { return useHazptr_; } // The following API is used to pass the context through queues / threads. // 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. // // Whenever possible, use RequestContextScopeGuard instead of setContext // to make sure that RequestContext is reset to the original value when // we exit the scope. // // A shared_ptr is used, because many request may fan out across // multiple threads, or do post-send processing, etc. static std::shared_ptr setContext( std::shared_ptr const& ctx); static std::shared_ptr setContext( std::shared_ptr&& newCtx_); static std::shared_ptr saveContext() { return getStaticContext().first; } private: struct Tag {}; RequestContext(const RequestContext& ctx) = default; public: RequestContext(const RequestContext& ctx, intptr_t rootid, Tag tag); RequestContext(const RequestContext& ctx, Tag tag); explicit RequestContext(intptr_t rootId); using StaticContext = std::pair, intptr_t>; private: static StaticContext& getStaticContext(); static std::shared_ptr setContextLock( std::shared_ptr& newCtx, StaticContext& staticCtx); static std::shared_ptr setContextHazptr( std::shared_ptr& newCtx, StaticContext& staticCtx); // Start shallow copy guard implementation details: // All methods are private to encourage proper use friend struct ShallowCopyRequestContextScopeGuard; // This sets a shallow copy of the current context as current, // then return the previous context (so it can be reset later). static std::shared_ptr setShallowCopyContext(); // Similar to setContextData, except it overwrites the data // if already set (instead of warn + reset ptr). void overwriteContextDataLock( const RequestToken& token, std::unique_ptr data); void overwriteContextDataLock( const std::string& val, std::unique_ptr data) { overwriteContextDataLock(RequestToken(val), std::move(data)); } // 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 data, bool safe = false); void overwriteContextDataHazptr( const std::string& val, std::unique_ptr data, bool safe = false) { overwriteContextDataHazptr(RequestToken(val), std::move(data), safe); } enum class DoSetBehaviour { SET, SET_IF_ABSENT, OVERWRITE, }; bool doSetContextDataLock( const RequestToken& token, std::unique_ptr& data, DoSetBehaviour behaviour); bool doSetContextDataLock( const std::string& val, std::unique_ptr& data, DoSetBehaviour behaviour) { return doSetContextDataLock(RequestToken(val), data, behaviour); } bool doSetContextDataHazptr( const RequestToken& token, std::unique_ptr& data, DoSetBehaviour behaviour, bool safe = false); bool doSetContextDataHazptr( const std::string& val, std::unique_ptr& 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 { // This must be optimized for lookup, its hot path is getContextData // Efficiency of copying the container also matters in setShallowCopyContext F14FastMap requestData_; // This must be optimized for iteration, its hot path is setContext // We also use the fact that it's ordered to efficiently compute // the difference with previous context sorted_vector_set callbackData_; }; folly::Synchronized 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_cohort<> cohort_; // For destruction order std::atomic combined_{nullptr}; std::mutex mutex_; StateHazptr(); StateHazptr(const StateHazptr& o); StateHazptr(StateHazptr&&) = delete; StateHazptr& operator=(const StateHazptr&) = delete; StateHazptr& operator=(StateHazptr&&) = delete; ~StateHazptr(); private: friend class RequestContext; struct SetContextDataResult { bool changed; // Changes were made bool unexpected; // Update was unexpected Combined* replaced; // The combined structure was replaced }; Combined* combined() const; Combined* ensureCombined(); // Lazy allocation if needed void setCombined(Combined* p); Combined* expand(Combined* combined); bool doSetContextData( const RequestToken& token, std::unique_ptr& 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); SetContextDataResult doSetContextDataHelper( const RequestToken& token, std::unique_ptr& data, DoSetBehaviour behaviour, bool safe); Combined* eraseOldData( Combined* cur, const RequestToken& token, RequestData* oldData, bool safe); Combined* insertNewData( Combined* cur, const RequestToken& token, std::unique_ptr& data, bool found); }; // StateHazptr StateHazptr stateHazptr_; bool useHazptr_; // Shallow copies keep a note of the root context intptr_t rootId_; }; /** * Note: you probably want to use ShallowCopyRequestContextScopeGuard * This resets all other RequestData for the duration of the scope! */ class RequestContextScopeGuard { private: std::shared_ptr prev_; public: RequestContextScopeGuard(const RequestContextScopeGuard&) = delete; RequestContextScopeGuard& operator=(const RequestContextScopeGuard&) = delete; RequestContextScopeGuard(RequestContextScopeGuard&&) = delete; RequestContextScopeGuard& operator=(RequestContextScopeGuard&&) = delete; // Create a new RequestContext and reset to the original value when // this goes out of scope. RequestContextScopeGuard() : prev_(RequestContext::saveContext()) { RequestContext::create(); } // Set a RequestContext that was previously captured by saveContext(). It will // be automatically reset to the original value when this goes out of scope. explicit RequestContextScopeGuard(std::shared_ptr const& ctx) : prev_(RequestContext::setContext(ctx)) {} explicit RequestContextScopeGuard(std::shared_ptr&& ctx) : prev_(RequestContext::setContext(std::move(ctx))) {} ~RequestContextScopeGuard() { RequestContext::setContext(std::move(prev_)); } }; /** * This guard maintains all the RequestData pointers of the parent. * This allows to overwrite a specific RequestData pointer for the * scope's duration, without breaking others. * * Only modified pointers will have their set/onset methods called */ struct ShallowCopyRequestContextScopeGuard { ShallowCopyRequestContextScopeGuard() : prev_(RequestContext::setShallowCopyContext()) {} /** * Shallow copy then overwrite one specific RequestData * * Helper constructor which is a more efficient equivalent to * "clearRequestData" then "setRequestData" after the guard. */ ShallowCopyRequestContextScopeGuard( const RequestToken& token, std::unique_ptr data) : ShallowCopyRequestContextScopeGuard() { auto ctx = RequestContext::get(); if (ctx->useHazptr()) { ctx->overwriteContextDataHazptr(token, std::move(data), true); } else { ctx->overwriteContextDataLock(token, std::move(data)); } } ShallowCopyRequestContextScopeGuard( const std::string& val, std::unique_ptr data) : ShallowCopyRequestContextScopeGuard() { auto ctx = RequestContext::get(); if (ctx->useHazptr()) { ctx->overwriteContextDataHazptr(val, std::move(data), true); } else { ctx->overwriteContextDataLock(val, std::move(data)); } } ~ShallowCopyRequestContextScopeGuard() { RequestContext::setContext(std::move(prev_)); } ShallowCopyRequestContextScopeGuard( const ShallowCopyRequestContextScopeGuard&) = delete; ShallowCopyRequestContextScopeGuard& operator=( const ShallowCopyRequestContextScopeGuard&) = delete; ShallowCopyRequestContextScopeGuard(ShallowCopyRequestContextScopeGuard&&) = delete; ShallowCopyRequestContextScopeGuard& operator=( ShallowCopyRequestContextScopeGuard&&) = delete; private: std::shared_ptr prev_; }; } // namespace folly