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
119664
  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
9496377
  template <class T> T& getTyped() {
47
9496377
    ASSERT(std::dynamic_pointer_cast<T>(get()) != nullptr);
48
9496377
    return *static_cast<T*>(get().get());
49
9496377
  }
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
67089
  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
12463
  static std::unique_ptr<TypedSlot> makeUnique(SlotAllocator& allocator) {
121
12463
    return std::make_unique<TypedSlot>(allocator);
122
12463
  }
123

            
124
60708
  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
120
  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
60644
  void set(InitializeCb cb) { slot_->set(cb); }
148

            
149
  /**
150
   * @return an optional reference to the thread local object.
151
   */
152
2886
  OptRef<T> get() { return getOpt(slot_->get()); }
153
193
  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
7032
  T* operator->() { return &(slot_->getTyped<T>()); }
162
1283
  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
5498565
  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
33177
  void runOnAllThreads(const UpdateCb& cb) { slot_->runOnAllThreads(makeSlotUpdateCb(cb)); }
184
16737
  void runOnAllThreads(const UpdateCb& cb, const std::function<void()>& complete_cb) {
185
16737
    slot_->runOnAllThreads(makeSlotUpdateCb(cb), complete_cb);
186
16737
  }
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
562
  bool isShutdown() const { return slot_->isShutdown(); };
194

            
195
private:
196
113389
  static OptRef<T> getOpt(ThreadLocalObjectSharedPtr obj) {
197
113389
    if (obj) {
198
113332
      return OptRef<T>(obj->asType<T>());
199
113332
    }
200
57
    return OptRef<T>();
201
113389
  }
202

            
203
49914
  Slot::UpdateCb makeSlotUpdateCb(UpdateCb cb) {
204
110312
    return [cb](ThreadLocalObjectSharedPtr obj) { cb(getOpt(obj)); };
205
49914
  }
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