proxygen
ThreadLocalDetail.h
Go to the documentation of this file.
1 /*
2  * Copyright 2011-present Facebook, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #pragma once
18 
19 #include <limits.h>
20 
21 #include <atomic>
22 #include <functional>
23 #include <mutex>
24 #include <string>
25 #include <vector>
26 
27 #include <glog/logging.h>
28 
29 #include <folly/Exception.h>
30 #include <folly/Function.h>
31 #include <folly/Portability.h>
32 #include <folly/ScopeGuard.h>
33 #include <folly/SharedMutex.h>
35 #include <folly/detail/AtFork.h>
36 #include <folly/memory/Malloc.h>
39 
41 
42 // In general, emutls cleanup is not guaranteed to play nice with the way
43 // StaticMeta mixes direct pthread calls and the use of __thread. This has
44 // caused problems on multiple platforms so don't use __thread there.
45 //
46 // XXX: Ideally we would instead determine if emutls is in use at runtime as it
47 // is possible to configure glibc on Linux to use emutls regardless.
48 #if !FOLLY_MOBILE && !defined(__APPLE__) && !defined(_MSC_VER)
49 #define FOLLY_TLD_USE_FOLLY_TLS 1
50 #else
51 #undef FOLLY_TLD_USE_FOLLY_TLS
52 #endif
53 
54 namespace folly {
55 
57 struct AccessModeStrict {};
58 
59 namespace threadlocal_detail {
60 
62 
63 struct ThreadEntry;
64 /* This represents a node in doubly linked list where all the nodes
65  * are part of an ElementWrapper struct that has the same id.
66  * we cannot use prev and next as ThreadEntryNode pointers since the
67  * ThreadEntry::elements can be reallocated and the pointers will change
68  * in this case. So we keep a pointer to the parent ThreadEntry struct
69  * one for the prev and next and also the id.
70  * We will traverse and update the list only when holding the
71  * StaticMetaBase::lock_
72  */
78 
79  void initIfZero(bool locked);
80 
81  void init(ThreadEntry* entry, uint32_t newId) {
82  id = newId;
83  parent = prev = next = entry;
84  }
85 
86  void initZero(ThreadEntry* entry, uint32_t newId) {
87  id = newId;
88  parent = entry;
89  prev = next = nullptr;
90  }
91 
92  // if the list this node is part of is empty
93  FOLLY_ALWAYS_INLINE bool empty() const {
94  return (next == parent);
95  }
96 
97  FOLLY_ALWAYS_INLINE bool zero() const {
98  return (!prev);
99  }
100 
102  return parent;
103  }
104 
106 
108 
109  void push_back(ThreadEntry* head);
110 
111  void eraseZero();
112 };
113 
119  using DeleterFunType = void(void*, TLPDestructionMode);
120 
122  if (ptr == nullptr) {
123  return false;
124  }
125 
126  DCHECK(deleter1 != nullptr);
127  ownsDeleter ? (*deleter2)(ptr, mode) : (*deleter1)(ptr, mode);
128  return true;
129  }
130 
131  void* release() {
132  auto retPtr = ptr;
133 
134  if (ptr != nullptr) {
135  cleanup();
136  }
137 
138  return retPtr;
139  }
140 
141  template <class Ptr>
142  void set(Ptr p) {
143  auto guard = makeGuard([&] { delete p; });
144  DCHECK(ptr == nullptr);
145  DCHECK(deleter1 == nullptr);
146 
147  if (p) {
148  node.initIfZero(true /*locked*/);
149  ptr = p;
150  deleter1 = [](void* pt, TLPDestructionMode) {
151  delete static_cast<Ptr>(pt);
152  };
153  ownsDeleter = false;
154  guard.dismiss();
155  }
156  }
157 
158  template <class Ptr, class Deleter>
159  void set(Ptr p, const Deleter& d) {
160  auto guard = makeGuard([&] {
161  if (p) {
163  }
164  });
165  DCHECK(ptr == nullptr);
166  DCHECK(deleter2 == nullptr);
167  if (p) {
168  node.initIfZero(true /*locked*/);
169  ptr = p;
170  auto d2 = d; // gcc-4.8 doesn't decay types correctly in lambda captures
171  deleter2 = new std::function<DeleterFunType>(
172  [d2](void* pt, TLPDestructionMode mode) {
173  d2(static_cast<Ptr>(pt), mode);
174  });
175  ownsDeleter = true;
176  guard.dismiss();
177  }
178  }
179 
180  void cleanup() {
181  if (ownsDeleter) {
182  delete deleter2;
183  }
184  ptr = nullptr;
185  deleter1 = nullptr;
186  ownsDeleter = false;
187  }
188 
189  void* ptr;
190  union {
192  std::function<DeleterFunType>* deleter2;
193  };
196 };
197 
198 struct StaticMetaBase;
199 struct ThreadEntryList;
200 
209 struct ThreadEntry {
210  ElementWrapper* elements{nullptr};
211  std::atomic<size_t> elementsCapacity{0};
212  ThreadEntry* next{nullptr};
213  ThreadEntry* prev{nullptr};
215  ThreadEntry* listNext{nullptr};
216  StaticMetaBase* meta{nullptr};
217  bool removed_{false};
218 
220  return elementsCapacity.load(std::memory_order_relaxed);
221  }
222 
223  void setElementsCapacity(size_t capacity) noexcept {
224  elementsCapacity.store(capacity, std::memory_order_relaxed);
225  }
226 };
227 
229  ThreadEntry* head{nullptr};
230  size_t count{0};
231 };
232 
234 
235 FOLLY_ALWAYS_INLINE ThreadEntryNode* ThreadEntryNode::getPrev() {
236  return &prev->elements[id].node;
237 }
238 
239 FOLLY_ALWAYS_INLINE ThreadEntryNode* ThreadEntryNode::getNext() {
240  return &next->elements[id].node;
241 }
242 
257  public:
258  static constexpr size_t kMaxKeys = 1UL << 16;
259 
261  // If static constructor priorities are not supported then
262  // ~PthreadKeyUnregister logic is not safe.
263 #if !defined(__APPLE__) && !defined(_MSC_VER)
264  MSLGuard lg(lock_);
265  while (size_) {
266  pthread_key_delete(keys_[--size_]);
267  }
268 #endif
269  }
270 
271  static void registerKey(pthread_key_t key) {
272  instance_.registerKeyImpl(key);
273  }
274 
275  private:
281  constexpr PthreadKeyUnregister() : lock_(), size_(0), keys_() {}
283 
284  void registerKeyImpl(pthread_key_t key) {
285  MSLGuard lg(lock_);
286  if (size_ == kMaxKeys) {
287  throw std::logic_error("pthread_key limit has already been reached");
288  }
289  keys_[size_++] = key;
290  }
291 
293  size_t size_;
294  pthread_key_t keys_[kMaxKeys];
295 
297 };
298 
300  // Represents an ID of a thread local object. Initially set to the maximum
301  // uint. This representation allows us to avoid a branch in accessing TLS data
302  // (because if you test capacity > id if id = maxint then the test will always
303  // fail). It allows us to keep a constexpr constructor and avoid SIOF.
304  class EntryID {
305  public:
306  std::atomic<uint32_t> value;
307 
308  constexpr EntryID() : value(kEntryIDInvalid) {}
309 
310  EntryID(EntryID&& other) noexcept : value(other.value.load()) {
311  other.value = kEntryIDInvalid;
312  }
313 
315  assert(this != &other);
316  value = other.value.load();
317  other.value = kEntryIDInvalid;
318  return *this;
319  }
320 
321  EntryID(const EntryID& other) = delete;
322  EntryID& operator=(const EntryID& other) = delete;
323 
325  // It's OK for this to be relaxed, even though we're effectively doing
326  // double checked locking in using this value. We only care about the
327  // uniqueness of IDs, getOrAllocate does not modify any other memory
328  // this thread will use.
329  return value.load(std::memory_order_relaxed);
330  }
331 
333  uint32_t id = getOrInvalid();
334  if (id != kEntryIDInvalid) {
335  return id;
336  }
337  // The lock inside allocate ensures that a single value is allocated
338  return meta.allocate(this);
339  }
340  };
341 
342  StaticMetaBase(ThreadEntry* (*threadEntry)(), bool strict);
343 
345  t->next = &head_;
346  t->prev = head_.prev;
347  head_.prev->next = t;
348  head_.prev = t;
349  }
350 
351  void erase(ThreadEntry* t) {
352  t->next->prev = t->prev;
353  t->prev->next = t->next;
354  t->next = t->prev = t;
355  }
356 
357  FOLLY_EXPORT static ThreadEntryList* getThreadEntryList();
358 
359  static void onThreadExit(void* ptr);
360 
361  // returns the elementsCapacity for the
362  // current thread ThreadEntry struct
363  uint32_t elementsCapacity() const;
364 
365  uint32_t allocate(EntryID* ent);
366 
367  void destroy(EntryID* ent);
368 
373  void reserve(EntryID* id);
374 
375  ElementWrapper& getElement(EntryID* ent);
376 
377  // reserve an id in the head_ ThreadEntry->elements
378  // array if not already there
379  void reserveHeadUnlocked(uint32_t id);
380 
381  // push back an entry in the doubly linked list
382  // that corresponds to idx id
383  void pushBackLocked(ThreadEntry* t, uint32_t id);
384  void pushBackUnlocked(ThreadEntry* t, uint32_t id);
385 
386  // static helper method to reallocate the ThreadEntry::elements
387  // returns != nullptr if the ThreadEntry::elements was reallocated
388  // nullptr if the ThreadEntry::elements was just extended
389  // and throws stdd:bad_alloc if memory cannot be allocated
390  static ElementWrapper*
391  reallocate(ThreadEntry* threadEntry, uint32_t idval, size_t& newCapacity);
392 
394  std::vector<uint32_t> freeIds_;
397  pthread_key_t pthreadKey_;
399  ThreadEntry* (*threadEntry_)();
400  bool strict_;
401 
402  protected:
404 };
405 
406 // Held in a singleton to track our global instances.
407 // We have one of these per "Tag", by default one for the whole system
408 // (Tag=void).
409 //
410 // Creating and destroying ThreadLocalPtr objects, as well as thread exit
411 // for threads that use ThreadLocalPtr objects collide on a lock inside
412 // StaticMeta; you can specify multiple Tag types to break that lock.
413 template <class Tag, class AccessMode>
414 struct StaticMeta final : StaticMetaBase {
416  : StaticMetaBase(
417  &StaticMeta::getThreadEntrySlow,
418  std::is_same<AccessMode, AccessModeStrict>::value) {
420  this,
421  /*prepare*/ &StaticMeta::preFork,
422  /*parent*/ &StaticMeta::onForkParent,
423  /*child*/ &StaticMeta::onForkChild);
424  }
425 
426  ~StaticMeta() = delete;
427 
429  // Leak it on exit, there's only one per process and we don't have to
430  // worry about synchronization with exiting threads.
431  /* library-local */ static auto instance =
432  detail::createGlobal<StaticMeta<Tag, AccessMode>, void>();
433  return *instance;
434  }
435 
437  // Eliminate as many branches and as much extra code as possible in the
438  // cached fast path, leaving only one branch here and one indirection below.
439  uint32_t id = ent->getOrInvalid();
440 #ifdef FOLLY_TLD_USE_FOLLY_TLS
441  static FOLLY_TLS ThreadEntry* threadEntry{};
442  static FOLLY_TLS size_t capacity{};
443 #else
444  ThreadEntry* threadEntry{};
445  size_t capacity{};
446 #endif
447  if (FOLLY_UNLIKELY(capacity <= id)) {
448  getSlowReserveAndCache(ent, id, threadEntry, capacity);
449  }
450  return threadEntry->elements[id];
451  }
452 
454  EntryID* ent,
455  uint32_t& id,
456  ThreadEntry*& threadEntry,
457  size_t& capacity) {
458  auto& inst = instance();
459  threadEntry = inst.threadEntry_();
460  if (UNLIKELY(threadEntry->getElementsCapacity() <= id)) {
461  inst.reserve(ent);
462  id = ent->getOrInvalid();
463  }
464  capacity = threadEntry->getElementsCapacity();
465  assert(capacity > id);
466  }
467 
469  auto& meta = instance();
470  auto key = meta.pthreadKey_;
471  ThreadEntry* threadEntry =
472  static_cast<ThreadEntry*>(pthread_getspecific(key));
473  if (!threadEntry) {
474  ThreadEntryList* threadEntryList = StaticMeta::getThreadEntryList();
475 #ifdef FOLLY_TLD_USE_FOLLY_TLS
476  static FOLLY_TLS ThreadEntry threadEntrySingleton;
477  threadEntry = &threadEntrySingleton;
478 #else
479  threadEntry = new ThreadEntry();
480 #endif
481  // if the ThreadEntry already exists
482  // but pthread_getspecific returns NULL
483  // do not add the same entry twice to the list
484  // since this would create a loop in the list
485  if (!threadEntry->list) {
486  threadEntry->list = threadEntryList;
487  threadEntry->listNext = threadEntryList->head;
488  threadEntryList->head = threadEntry;
489  }
490 
491  // if we're adding a thread entry
492  // we need to increment the list count
493  // even if the entry is reused
494  threadEntryList->count++;
495 
496  threadEntry->meta = &meta;
497  int ret = pthread_setspecific(key, threadEntry);
498  checkPosixError(ret, "pthread_setspecific failed");
499  }
500  return threadEntry;
501  }
502 
503  static bool preFork() {
504  return instance().lock_.try_lock(); // Make sure it's created
505  }
506 
507  static void onForkParent() {
508  instance().lock_.unlock();
509  }
510 
511  static void onForkChild() {
512  // only the current thread survives
513  auto& head = instance().head_;
514  // init the head list
515  head.next = head.prev = &head;
516  // init the circular lists
517  auto elementsCapacity = head.getElementsCapacity();
518  for (size_t i = 0u; i < elementsCapacity; ++i) {
519  head.elements[i].node.init(&head, static_cast<uint32_t>(i));
520  }
521  // init the thread entry
522  ThreadEntry* threadEntry = instance().threadEntry_();
523  elementsCapacity = threadEntry->getElementsCapacity();
524  for (size_t i = 0u; i < elementsCapacity; ++i) {
525  if (!threadEntry->elements[i].node.zero()) {
526  threadEntry->elements[i].node.initZero(
527  threadEntry, static_cast<uint32_t>(i));
528  threadEntry->elements[i].node.initIfZero(false /*locked*/);
529  }
530  }
531 
532  // If this thread was in the list before the fork, add it back.
533  if (elementsCapacity != 0) {
534  instance().push_back(threadEntry);
535  }
536  instance().lock_.unlock();
537  }
538 };
539 } // namespace threadlocal_detail
540 } // namespace folly
void(void *, TLPDestructionMode) DeleterFunType
FOLLY_EXPORT static FOLLY_NOINLINE ThreadEntry * getThreadEntrySlow()
void * ptr
FOLLY_ALWAYS_INLINE ThreadEntry * getThreadEntry()
static FOLLY_NOINLINE void getSlowReserveAndCache(EntryID *ent, uint32_t &id, ThreadEntry *&threadEntry, size_t &capacity)
FOLLY_ALWAYS_INLINE bool empty() const
#define FOLLY_ALWAYS_INLINE
Definition: CPortability.h:151
LogLevel max
Definition: LogLevel.cpp:31
std::lock_guard< MicroSpinLock > MSLGuard
STL namespace.
static StaticMeta< Tag, AccessMode > & instance()
#define FOLLY_UNLIKELY(x)
Definition: Likely.h:36
—— Concurrent Priority Queue Implementation ——
Definition: AtomicBitSet.h:29
#define FOLLY_EXPORT
Definition: CPortability.h:133
std::function< DeleterFunType > * deleter2
requires E e noexcept(noexcept(s.error(std::move(e))))
def load()
Definition: deadlock.py:441
folly::Optional< PskKeyExchangeMode > mode
static void destroy()
void init(ThreadEntry *entry, uint32_t newId)
void BENCHFUN() push_back(size_t iters, size_t arg)
bool dispose(TLPDestructionMode mode)
#define FOLLY_NOINLINE
Definition: CPortability.h:142
void checkPosixError(int err, Args &&...args)
Definition: Exception.h:83
size_t getElementsCapacity() const noexcept
Encoder::MutableCompressedList list
GuardImpl guard(ErrorHandler &&handler)
Definition: Base.h:840
std::enable_if< std::is_integral< Src >::value &&IsSomeString< Tgt >::value &&sizeof(Src)< 4 >::typetoAppend(Src value, Tgt *result){typedef typename std::conditional< std::is_signed< Src >::value, int64_t, uint64_t >::type Intermediate;toAppend< Tgt >static_cast< Intermediate >value), result);}template< class Src >typename std::enable_if< std::is_integral< Src >::value &&sizeof(Src)< 4 &&!std::is_same< Src, char >::value, size_t >::typeestimateSpaceNeeded(Src value){typedef typename std::conditional< std::is_signed< Src >::value, int64_t, uint64_t >::type Intermediate;return estimateSpaceNeeded(static_cast< Intermediate >value));}template< class Tgt, class Src >typename std::enable_if< std::is_enum< Src >::value &&IsSomeString< Tgt >::value >::typetoAppend(Src value, Tgt *result){toAppend(static_cast< typename std::underlying_type< Src >::type >value), result);}template< class Src >typename std::enable_if< std::is_enum< Src >::value, size_t >::typeestimateSpaceNeeded(Src value){return estimateSpaceNeeded(static_cast< typename std::underlying_type< Src >::type >value));}namespace detail{constexpr int kConvMaxDecimalInShortestLow=-6;constexpr int kConvMaxDecimalInShortestHigh=21;}template< class Tgt, class Src >typename std::enable_if< std::is_floating_point< Src >::value &&IsSomeString< Tgt >::value >::typetoAppend(Src value, Tgt *result, double_conversion::DoubleToStringConverter::DtoaMode mode, unsigned int numDigits){using namespace double_conversion;DoubleToStringConverter conv(DoubleToStringConverter::NO_FLAGS,"Infinity","NaN", 'E', detail::kConvMaxDecimalInShortestLow, detail::kConvMaxDecimalInShortestHigh, 6, 1);char buffer[256];StringBuilder builder(buffer, sizeof(buffer));switch(mode){case DoubleToStringConverter::SHORTEST:conv.ToShortest(value,&builder);break;case DoubleToStringConverter::SHORTEST_SINGLE:conv.ToShortestSingle(static_cast< float >value),&builder);break;case DoubleToStringConverter::FIXED:conv.ToFixed(value, int(numDigits),&builder);break;default:CHECK(mode==DoubleToStringConverter::PRECISION);conv.ToPrecision(value, int(numDigits),&builder);break;}const size_t length=size_t(builder.position());builder.Finalize();result->append(buffer, length);}template< class Tgt, class Src >typename std::enable_if< std::is_floating_point< Src >::value &&IsSomeString< Tgt >::value >::typetoAppend(Src value, Tgt *result){toAppend(value, result, double_conversion::DoubleToStringConverter::SHORTEST, 0);}template< class Src >typename std::enable_if< std::is_floating_point< Src >::value, size_t >::typeestimateSpaceNeeded(Src value){constexpr int kMaxMantissaSpace=double_conversion::DoubleToStringConverter::kBase10MaximalLength+1;constexpr int kMaxExponentSpace=2+3;static const int kMaxPositiveSpace=std::max({kMaxMantissaSpace+kMaxExponentSpace, kMaxMantissaSpace-detail::kConvMaxDecimalInShortestLow, detail::kConvMaxDecimalInShortestHigh,});return size_t(kMaxPositiveSpace+(value< 0?1:0));}template< class Src >struct HasLengthEstimator:std::false_type{};template< class Src >constexpr typename std::enable_if< !std::is_fundamental< Src >::value &&!IsSomeString< Src >::value &&!std::is_convertible< Src, const char * >::value &&!std::is_convertible< Src, StringPiece >::value &&!std::is_enum< Src >::value &&!HasLengthEstimator< Src >::value, size_t >::typeestimateSpaceNeeded(const Src &){return sizeof(Src)+1;}namespace detail{template< class Tgt >typename std::enable_if< IsSomeString< Tgt >::value, size_t >::typeestimateSpaceToReserve(size_t sofar, Tgt *){return sofar;}template< class T, class...Ts >size_t estimateSpaceToReserve(size_t sofar, const T &v, const Ts &...vs){return estimateSpaceToReserve(sofar+estimateSpaceNeeded(v), vs...);}template< class...Ts >void reserveInTarget(const Ts &...vs){getLastElement(vs...) -> reserve(estimateSpaceToReserve(0, vs...))
Definition: Conv.h:792
int * count
FOLLY_ALWAYS_INLINE bool zero() const
FOLLY_NODISCARD detail::ScopeGuardImplDecay< F, true > makeGuard(F &&f) noexcept(noexcept(detail::ScopeGuardImplDecay< F, true >(static_cast< F && >(f))))
Definition: ScopeGuard.h:184
std::mutex mutex
constexpr uint32_t kEntryIDInvalid
const
Definition: upload.py:398
uint64_t value(const typename LockFreeRingBuffer< T, Atom >::Cursor &rbcursor)
void initZero(ThreadEntry *entry, uint32_t newId)
#define UNLIKELY(x)
Definition: Likely.h:48
void cleanup()
Definition: Init.cpp:59
static void registerHandler(void *object, folly::Function< bool()> prepare, folly::Function< void()> parent, folly::Function< void()> child)
Definition: AtFork.cpp:108
folly::Function< void()> parent
Definition: AtFork.cpp:34
void setElementsCapacity(size_t capacity) noexcept
def next(obj)
Definition: ast.py:58