Line data Source code
1 : #pragma once 2 : 3 : #include <cstdint> 4 : #include <functional> 5 : #include <memory> 6 : 7 : #include "envoy/common/optref.h" 8 : #include "envoy/common/pure.h" 9 : #include "envoy/event/dispatcher.h" 10 : #include "envoy/thread_local/thread_local_object.h" 11 : 12 : #include "source/common/common/assert.h" 13 : 14 : namespace Envoy { 15 : namespace ThreadLocal { 16 : 17 : /** 18 : * An individual allocated TLS slot. When the slot is destroyed the stored thread local will 19 : * be freed on each thread. 20 : */ 21 : class Slot { 22 : public: 23 2320 : virtual ~Slot() = default; 24 : 25 : /** 26 : * Returns if there is thread local data for this thread. 27 : * 28 : * This should return true for Envoy worker threads and false for threads which do not have thread 29 : * local storage allocated. 30 : * 31 : * @return true if registerThread has been called for this thread, false otherwise. 32 : */ 33 : virtual bool currentThreadRegistered() PURE; 34 : 35 : /** 36 : * @return ThreadLocalObjectSharedPtr a thread local object stored in the slot. 37 : */ 38 : virtual ThreadLocalObjectSharedPtr get() PURE; 39 : 40 : /** 41 : * This is a helper on top of get() that casts the object stored in the slot to the specified 42 : * type. Since the slot only stores pointers to the base interface, the static_cast operates 43 : * in production for performance, and the dynamic_cast validates correctness in tests and debug 44 : * builds. 45 : */ 46 62383 : template <class T> T& getTyped() { 47 62383 : ASSERT(std::dynamic_pointer_cast<T>(get()) != nullptr); 48 62383 : return *static_cast<T*>(get().get()); 49 62383 : } 50 : 51 : /** 52 : * Set thread local data on all threads previously registered via registerThread(). 53 : * @param initializeCb supplies the functor that will be called *on each thread*. The functor 54 : * returns the thread local object which is then stored. The storage is via 55 : * a shared_ptr. Thus, this is a flexible mechanism that can be used to share 56 : * the same data across all threads or to share different data on each thread. 57 : * 58 : * NOTE: The initialize callback is not supposed to capture the Slot, or its owner, as the owner 59 : * may be destructed in main thread before the update_cb gets called in a worker thread. 60 : */ 61 : using InitializeCb = std::function<ThreadLocalObjectSharedPtr(Event::Dispatcher& dispatcher)>; 62 : virtual void set(InitializeCb cb) PURE; 63 : 64 : protected: 65 : template <class T> friend class TypedSlot; 66 : 67 : /** 68 : * UpdateCb takes is passed a shared point to the current stored data. Use of 69 : * this API is deprecated; please use TypedSlot::runOnAllThreads instead. 70 : * 71 : * NOTE: The update callback is not supposed to capture the Slot, or its 72 : * owner, as the owner may be destructed in main thread before the update_cb 73 : * gets called in a worker thread. 74 : **/ 75 : using UpdateCb = std::function<void(ThreadLocalObjectSharedPtr)>; 76 : 77 : // Callers must use the TypedSlot API, below. 78 : virtual void runOnAllThreads(const UpdateCb& update_cb) PURE; 79 : virtual void runOnAllThreads(const UpdateCb& update_cb, 80 : const std::function<void()>& complete_cb) PURE; 81 : 82 : /** 83 : * Returns whether or not global threading has been shutdown. 84 : * 85 : * @return true if global threading has been shutdown or false if not. 86 : */ 87 : virtual bool isShutdown() const PURE; 88 : }; 89 : 90 : using SlotPtr = std::unique_ptr<Slot>; 91 : 92 : /** 93 : * Interface used to allocate thread local slots. 94 : */ 95 : class SlotAllocator { 96 : public: 97 1495 : virtual ~SlotAllocator() = default; 98 : 99 : /** 100 : * @return SlotPtr a dedicated slot for use in further calls to get(), set(), etc. 101 : */ 102 : virtual SlotPtr allocateSlot() PURE; 103 : }; 104 : 105 : // Provides a typesafe API for slots. The slot data must be derived from 106 : // ThreadLocalObject. If there is no slot data, you can instantiated TypedSlot 107 : // with the default type param: TypedSlot<> tls_; 108 : // 109 : // TODO(jmarantz): Rename the Slot class to something like RawSlot, where the 110 : // only reference is from TypedSlot, which we can then rename to Slot. 111 : template <class T> class TypedSlot { 112 : public: 113 : /** 114 : * Helper method to create a unique_ptr for a typed slot. This helper 115 : * reduces some verbose parameterization at call-sites. 116 : * 117 : * @param allocator factory to allocate untyped Slot objects. 118 : * @return a TypedSlotPtr<T> (the type is defined below). 119 : */ 120 110 : static std::unique_ptr<TypedSlot> makeUnique(SlotAllocator& allocator) { 121 110 : return std::make_unique<TypedSlot>(allocator); 122 110 : } 123 : 124 813 : explicit TypedSlot(SlotAllocator& allocator) : slot_(allocator.allocateSlot()) {} 125 : 126 : /** 127 : * Returns if there is thread local data for this thread. 128 : * 129 : * This should return true for Envoy worker threads and false for threads which do not have thread 130 : * local storage allocated. 131 : * 132 : * @return true if registerThread has been called for this thread, false otherwise. 133 : */ 134 0 : bool currentThreadRegistered() { return slot_->currentThreadRegistered(); } 135 : 136 : /** 137 : * Set thread local data on all threads previously registered via registerThread(). 138 : * @param initializeCb supplies the functor that will be called *on each thread*. The functor 139 : * returns the thread local object which is then stored. The storage is via 140 : * a shared_ptr. Thus, this is a flexible mechanism that can be used to share 141 : * the same data across all threads or to share different data on each thread. 142 : * 143 : * NOTE: The initialize callback is not supposed to capture the Slot, or its owner, as the owner 144 : * may be destructed in main thread before the update_cb gets called in a worker thread. 145 : */ 146 : using InitializeCb = std::function<std::shared_ptr<T>(Event::Dispatcher& dispatcher)>; 147 660 : void set(InitializeCb cb) { slot_->set(cb); } 148 : 149 : /** 150 : * @return an optional reference to the thread local object. 151 : */ 152 0 : OptRef<T> get() { return getOpt(slot_->get()); } 153 : const OptRef<T> get() const { return getOpt(slot_->get()); } 154 : 155 : /** 156 : * Helper function to call methods on T. The caller is responsible 157 : * for ensuring that get().has_value() is true. 158 : * 159 : * @return a pointer to the thread local object. 160 : */ 161 81 : T* operator->() { return &(slot_->getTyped<T>()); } 162 53 : const T* operator->() const { return &(slot_->getTyped<T>()); } 163 : 164 : /** 165 : * Helper function to get access to a T&. The caller is responsible for 166 : * ensuring that get().has_value() is true. 167 : * 168 : * @return a reference to the thread local object. 169 : */ 170 49016 : T& operator*() { return slot_->getTyped<T>(); } 171 : const T& operator*() const { return slot_->getTyped<T>(); } 172 : 173 : /** 174 : * UpdateCb is passed a mutable pointer to the current stored data. Callers 175 : * can assume that the passed-in OptRef has a value if they have called set(), 176 : * yielding a non-null shared_ptr, prior to runOnAllThreads(). 177 : * 178 : * NOTE: The update callback is not supposed to capture the TypedSlot, or its 179 : * owner, as the owner may be destructed in main thread before the update_cb 180 : * gets called in a worker thread. 181 : */ 182 : using UpdateCb = std::function<void(OptRef<T> obj)>; 183 202 : void runOnAllThreads(const UpdateCb& cb) { slot_->runOnAllThreads(makeSlotUpdateCb(cb)); } 184 10 : void runOnAllThreads(const UpdateCb& cb, const std::function<void()>& complete_cb) { 185 10 : slot_->runOnAllThreads(makeSlotUpdateCb(cb), complete_cb); 186 10 : } 187 : 188 : /** 189 : * Returns whether or not global threading has been shutdown. 190 : * 191 : * @return true if global threading has been shutdown or false if not. 192 : */ 193 0 : bool isShutdown() const { return slot_->isShutdown(); }; 194 : 195 : private: 196 409 : static OptRef<T> getOpt(ThreadLocalObjectSharedPtr obj) { 197 409 : if (obj) { 198 409 : return OptRef<T>(obj->asType<T>()); 199 409 : } 200 0 : return OptRef<T>(); 201 409 : } 202 : 203 212 : Slot::UpdateCb makeSlotUpdateCb(UpdateCb cb) { 204 409 : return [cb](ThreadLocalObjectSharedPtr obj) { cb(getOpt(obj)); }; 205 212 : } 206 : 207 : const SlotPtr slot_; 208 : }; 209 : 210 : template <class T = ThreadLocalObject> using TypedSlotPtr = std::unique_ptr<TypedSlot<T>>; 211 : 212 : /** 213 : * Interface for getting and setting thread local data as well as registering a thread 214 : */ 215 : class Instance : public SlotAllocator { 216 : public: 217 : /** 218 : * A thread (via its dispatcher) must be registered before set() is called on any allocated slots 219 : * to receive thread local data updates. 220 : * @param dispatcher supplies the thread's dispatcher. 221 : * @param main_thread supplies whether this is the main program thread or not. (The only 222 : * difference is that callbacks fire immediately on the main thread when posted 223 : * from the main thread). 224 : */ 225 : virtual void registerThread(Event::Dispatcher& dispatcher, bool main_thread) PURE; 226 : 227 : /** 228 : * This should be called by the main thread before any worker threads start to exit. This will 229 : * block TLS removal during slot destruction, given that worker threads are about to call 230 : * shutdownThread(). This avoids having to implement de-registration of threads. 231 : */ 232 : virtual void shutdownGlobalThreading() PURE; 233 : 234 : /** 235 : * The owning thread is about to exit. This will free all thread local variables. It must be 236 : * called on the thread that is shutting down. 237 : */ 238 : virtual void shutdownThread() PURE; 239 : 240 : /** 241 : * @return Event::Dispatcher& the thread local dispatcher. 242 : */ 243 : virtual Event::Dispatcher& dispatcher() PURE; 244 : 245 : /** 246 : * Returns whether or not global threading has been shutdown. 247 : * 248 : * @return true if global threading has been shutdown or false if not. 249 : */ 250 : virtual bool isShutdown() const PURE; 251 : }; 252 : 253 : } // namespace ThreadLocal 254 : } // namespace Envoy