Coverage Report

Created: 2026-04-10 07:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/rocksdb/util/thread_local.cc
Line
Count
Source
1
//  Copyright (c) 2011-present, Facebook, Inc.  All rights reserved.
2
//  This source code is licensed under both the GPLv2 (found in the
3
//  COPYING file in the root directory) and Apache 2.0 License
4
//  (found in the LICENSE.Apache file in the root directory).
5
//
6
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
7
// Use of this source code is governed by a BSD-style license that can be
8
// found in the LICENSE file. See the AUTHORS file for names of contributors.
9
10
#include "util/thread_local.h"
11
12
#include <cstdlib>
13
14
#include "port/likely.h"
15
#include "util/mutexlock.h"
16
17
namespace ROCKSDB_NAMESPACE {
18
19
struct Entry {
20
5
  Entry() : ptr(nullptr) {}
21
0
  Entry(const Entry& e) : ptr(e.ptr.load(std::memory_order_relaxed)) {}
22
  std::atomic<void*> ptr;
23
};
24
25
class StaticMeta;
26
27
// This is the structure that is declared as "thread_local" storage.
28
// The vector keep list of atomic pointer for all instances for "current"
29
// thread. The vector is indexed by an Id that is unique in process and
30
// associated with one ThreadLocalPtr instance. The Id is assigned by a
31
// global StaticMeta singleton. So if we instantiated 3 ThreadLocalPtr
32
// instances, each thread will have a ThreadData with a vector of size 3:
33
//     ---------------------------------------------------
34
//     |          | instance 1 | instance 2 | instance 3 |
35
//     ---------------------------------------------------
36
//     | thread 1 |    void*   |    void*   |    void*   | <- ThreadData
37
//     ---------------------------------------------------
38
//     | thread 2 |    void*   |    void*   |    void*   | <- ThreadData
39
//     ---------------------------------------------------
40
//     | thread 3 |    void*   |    void*   |    void*   | <- ThreadData
41
//     ---------------------------------------------------
42
struct ThreadData {
43
  explicit ThreadData(ThreadLocalPtr::StaticMeta* _inst)
44
4
      : entries(), next(nullptr), prev(nullptr), inst(_inst) {}
45
  std::vector<Entry> entries;
46
  ThreadData* next;
47
  ThreadData* prev;
48
  ThreadLocalPtr::StaticMeta* inst;
49
};
50
51
class ThreadLocalPtr::StaticMeta {
52
 public:
53
  StaticMeta();
54
55
  // Return the next available Id
56
  uint32_t GetId();
57
  // Return the next available Id without claiming it
58
  uint32_t PeekId() const;
59
  // Return the given Id back to the free pool. This also triggers
60
  // UnrefHandler for associated pointer value (if not NULL) for all threads.
61
  void ReclaimId(uint32_t id);
62
63
  // Return the pointer value for the given id for the current thread.
64
  void* Get(uint32_t id) const;
65
  // Reset the pointer value for the given id for the current thread.
66
  void Reset(uint32_t id, void* ptr);
67
  // Atomically swap the supplied ptr and return the previous value
68
  void* Swap(uint32_t id, void* ptr);
69
  // Atomically compare and swap the provided value only if it equals
70
  // to expected value.
71
  bool CompareAndSwap(uint32_t id, void* ptr, void*& expected);
72
  // Reset all thread local data to replacement, and return non-nullptr
73
  // data for all existing threads
74
  void Scrape(uint32_t id, autovector<void*>* ptrs, void* const replacement);
75
  // Update res by applying func on each thread-local value. Holds a lock that
76
  // prevents unref handler from running during this call, but clients must
77
  // still provide external synchronization since the owning thread can
78
  // access the values without internal locking, e.g., via Get() and Reset().
79
  void Fold(uint32_t id, FoldFunc func, void* res);
80
81
  // Register the UnrefHandler for id
82
  void SetHandler(uint32_t id, UnrefHandler handler);
83
84
  // protect inst, next_instance_id_, free_instance_ids_, head_,
85
  // ThreadData.entries
86
  //
87
  // Note that here we prefer function static variable instead of the usual
88
  // global static variable.  The reason is that c++ destruction order of
89
  // static variables in the reverse order of their construction order.
90
  // However, C++ does not guarantee any construction order when global
91
  // static variables are defined in different files, while the function
92
  // static variables are initialized when their function are first called.
93
  // As a result, the construction order of the function static variables
94
  // can be controlled by properly invoke their first function calls in
95
  // the right order.
96
  //
97
  // For instance, the following function contains a function static
98
  // variable.  We place a dummy function call of this inside
99
  // Env::Default() to ensure the construction order of the construction
100
  // order.
101
  static port::Mutex* Mutex();
102
103
  // Returns the member mutex of the current StaticMeta.  In general,
104
  // Mutex() should be used instead of this one.  However, in case where
105
  // the static variable inside Instance() goes out of scope, MemberMutex()
106
  // should be used.  One example is OnThreadExit() function.
107
2
  port::Mutex* MemberMutex() { return &mutex_; }
108
109
 private:
110
  // Get UnrefHandler for id with acquiring mutex
111
  // REQUIRES: mutex locked
112
  UnrefHandler GetHandler(uint32_t id);
113
114
  // Triggered before a thread terminates
115
  static void OnThreadExit(void* ptr);
116
117
  // Add current thread's ThreadData to the global chain
118
  // REQUIRES: mutex locked
119
  void AddThreadData(ThreadData* d);
120
121
  // Remove current thread's ThreadData from the global chain
122
  // REQUIRES: mutex locked
123
  void RemoveThreadData(ThreadData* d);
124
125
  static ThreadData* GetThreadLocal();
126
127
  uint32_t next_instance_id_;
128
  // Used to recycle Ids in case ThreadLocalPtr is instantiated and destroyed
129
  // frequently. This also prevents it from blowing up the vector space.
130
  autovector<uint32_t> free_instance_ids_;
131
  // Chain all thread local structure together. This is necessary since
132
  // when one ThreadLocalPtr gets destroyed, we need to loop over each
133
  // thread's version of pointer corresponding to that instance and
134
  // call UnrefHandler for it.
135
  ThreadData head_;
136
137
  std::unordered_map<uint32_t, UnrefHandler> handler_map_;
138
139
  // The private mutex.  Developers should always use Mutex() instead of
140
  // using this variable directly.
141
  port::Mutex mutex_;
142
  // Thread local storage
143
  static thread_local ThreadData* tls_;
144
145
  // Used to make thread exit trigger possible if !defined(OS_MACOSX).
146
  // Otherwise, used to retrieve thread data.
147
  pthread_key_t pthread_key_;
148
};
149
150
thread_local ThreadData* ThreadLocalPtr::StaticMeta::tls_ = nullptr;
151
152
// Windows doesn't support a per-thread destructor with its
153
// TLS primitives.  So, we build it manually by inserting a
154
// function to be called on each thread's exit.
155
// See http://www.codeproject.com/Articles/8113/Thread-Local-Storage-The-C-Way
156
// and http://www.nynaeve.net/?p=183
157
//
158
// really we do this to have clear conscience since using TLS with thread-pools
159
// is iffy
160
// although OK within a request. But otherwise, threads have no identity in its
161
// modern use.
162
163
// This runs on windows only called from the System Loader
164
#ifdef OS_WIN
165
166
// Windows cleanup routine is invoked from a System Loader with a different
167
// signature so we can not directly hookup the original OnThreadExit which is
168
// private member
169
// so we make StaticMeta class share with the us the address of the function so
170
// we can invoke it.
171
namespace wintlscleanup {
172
173
// This is set to OnThreadExit in StaticMeta singleton constructor
174
UnrefHandler thread_local_inclass_routine = nullptr;
175
pthread_key_t thread_local_key = pthread_key_t(-1);
176
177
// Static callback function to call with each thread termination.
178
void NTAPI WinOnThreadExit(PVOID module, DWORD reason, PVOID reserved) {
179
  // We decided to punt on PROCESS_EXIT
180
  if (DLL_THREAD_DETACH == reason) {
181
    if (thread_local_key != pthread_key_t(-1) &&
182
        thread_local_inclass_routine != nullptr) {
183
      void* tls = TlsGetValue(thread_local_key);
184
      if (tls != nullptr) {
185
        thread_local_inclass_routine(tls);
186
      }
187
    }
188
  }
189
}
190
191
}  // namespace wintlscleanup
192
193
// extern "C" suppresses C++ name mangling so we know the symbol name for the
194
// linker /INCLUDE:symbol pragma above.
195
extern "C" {
196
197
#ifdef _MSC_VER
198
// The linker must not discard thread_callback_on_exit.  (We force a reference
199
// to this variable with a linker /include:symbol pragma to ensure that.) If
200
// this variable is discarded, the OnThreadExit function will never be called.
201
#ifndef _X86_
202
203
// .CRT section is merged with .rdata on x64 so it must be constant data.
204
#pragma const_seg(".CRT$XLB")
205
// When defining a const variable, it must have external linkage to be sure the
206
// linker doesn't discard it.
207
extern const PIMAGE_TLS_CALLBACK p_thread_callback_on_exit;
208
const PIMAGE_TLS_CALLBACK p_thread_callback_on_exit =
209
    wintlscleanup::WinOnThreadExit;
210
// Reset the default section.
211
#pragma const_seg()
212
213
#pragma comment(linker, "/include:_tls_used")
214
#pragma comment(linker, "/include:p_thread_callback_on_exit")
215
216
#else  // _X86_
217
218
#pragma data_seg(".CRT$XLB")
219
PIMAGE_TLS_CALLBACK p_thread_callback_on_exit = wintlscleanup::WinOnThreadExit;
220
// Reset the default section.
221
#pragma data_seg()
222
223
#pragma comment(linker, "/INCLUDE:__tls_used")
224
#pragma comment(linker, "/INCLUDE:_p_thread_callback_on_exit")
225
226
#endif  // _X86_
227
228
#else
229
// https://github.com/couchbase/gperftools/blob/master/src/windows/port.cc
230
BOOL WINAPI DllMain(HINSTANCE h, DWORD dwReason, PVOID pv) {
231
  if (dwReason == DLL_THREAD_DETACH)
232
    wintlscleanup::WinOnThreadExit(h, dwReason, pv);
233
  return TRUE;
234
}
235
#endif
236
}  // extern "C"
237
238
#endif  // OS_WIN
239
240
2.47M
void ThreadLocalPtr::InitSingletons() { ThreadLocalPtr::Instance(); }
241
242
3.44M
ThreadLocalPtr::StaticMeta* ThreadLocalPtr::Instance() {
243
  // Here we prefer function static variable instead of global
244
  // static variable as function static variable is initialized
245
  // when the function is first call.  As a result, we can properly
246
  // control their construction order by properly preparing their
247
  // first function call.
248
  //
249
  // Note that here we decide to make "inst" a static pointer w/o deleting
250
  // it at the end instead of a static variable.  This is to avoid the following
251
  // destruction order disaster happens when a child thread using ThreadLocalPtr
252
  // dies AFTER the main thread dies:  When a child thread happens to use
253
  // ThreadLocalPtr, it will try to delete its thread-local data on its
254
  // OnThreadExit when the child thread dies.  However, OnThreadExit depends
255
  // on the following variable.  As a result, if the main thread dies before any
256
  // child thread happen to use ThreadLocalPtr dies, then the destruction of
257
  // the following variable will go first, then OnThreadExit, therefore causing
258
  // invalid access.
259
  //
260
  // The above problem can be solved by using thread_local to store tls_.
261
  // thread_local supports dynamic construction and destruction of
262
  // non-primitive typed variables.  As a result, we can guarantee the
263
  // destruction order even when the main thread dies before any child threads.
264
3.44M
  static ThreadLocalPtr::StaticMeta* inst = new ThreadLocalPtr::StaticMeta();
265
3.44M
  return inst;
266
3.44M
}
267
268
512k
port::Mutex* ThreadLocalPtr::StaticMeta::Mutex() { return &Instance()->mutex_; }
269
270
2
void ThreadLocalPtr::StaticMeta::OnThreadExit(void* ptr) {
271
2
  auto* tls = static_cast<ThreadData*>(ptr);
272
2
  assert(tls != nullptr);
273
274
  // Use the cached StaticMeta::Instance() instead of directly calling
275
  // the variable inside StaticMeta::Instance() might already go out of
276
  // scope here in case this OnThreadExit is called after the main thread
277
  // dies.
278
2
  auto* inst = tls->inst;
279
2
  pthread_setspecific(inst->pthread_key_, nullptr);
280
281
2
  MutexLock l(inst->MemberMutex());
282
2
  inst->RemoveThreadData(tls);
283
  // Unref stored pointers of current thread from all instances
284
2
  uint32_t id = 0;
285
5
  for (auto& e : tls->entries) {
286
5
    void* raw = e.ptr.load();
287
5
    if (raw != nullptr) {
288
0
      auto unref = inst->GetHandler(id);
289
0
      if (unref != nullptr) {
290
0
        unref(raw);
291
0
      }
292
0
    }
293
5
    ++id;
294
5
  }
295
  // Delete thread local structure no matter if it is Mac platform
296
2
  delete tls;
297
2
}
298
299
ThreadLocalPtr::StaticMeta::StaticMeta()
300
2
    : next_instance_id_(0), head_(this), pthread_key_(0) {
301
2
  if (pthread_key_create(&pthread_key_, &OnThreadExit) != 0) {
302
0
    abort();
303
0
  }
304
305
  // OnThreadExit is not getting called on the main thread.
306
  // Call through the static destructor mechanism to avoid memory leak.
307
  //
308
  // Caveats: ~A() will be invoked _after_ ~StaticMeta for the global
309
  // singleton (destructors are invoked in reverse order of constructor
310
  // _completion_); the latter must not mutate internal members. This
311
  // cleanup mechanism inherently relies on use-after-release of the
312
  // StaticMeta, and is brittle with respect to compiler-specific handling
313
  // of memory backing destructed statically-scoped objects. Perhaps
314
  // registering with atexit(3) would be more robust.
315
  //
316
// This is not required on Windows.
317
2
#if !defined(OS_WIN)
318
2
  static struct A {
319
2
    ~A() {
320
2
      if (tls_) {
321
2
        OnThreadExit(tls_);
322
2
      }
323
2
    }
324
2
  } a;
325
2
#endif  // !defined(OS_WIN)
326
327
2
  head_.next = &head_;
328
2
  head_.prev = &head_;
329
330
#ifdef OS_WIN
331
  // Share with Windows its cleanup routine and the key
332
  wintlscleanup::thread_local_inclass_routine = OnThreadExit;
333
  wintlscleanup::thread_local_key = pthread_key_;
334
#endif
335
2
}
336
337
2
void ThreadLocalPtr::StaticMeta::AddThreadData(ThreadData* d) {
338
2
  Mutex()->AssertHeld();
339
2
  d->next = &head_;
340
2
  d->prev = head_.prev;
341
2
  head_.prev->next = d;
342
2
  head_.prev = d;
343
2
}
344
345
2
void ThreadLocalPtr::StaticMeta::RemoveThreadData(ThreadData* d) {
346
2
  Mutex()->AssertHeld();
347
2
  d->next->prev = d->prev;
348
2
  d->prev->next = d->next;
349
2
  d->next = d->prev = d;
350
2
}
351
352
75.0k
ThreadData* ThreadLocalPtr::StaticMeta::GetThreadLocal() {
353
75.0k
  if (UNLIKELY(tls_ == nullptr)) {
354
2
    auto* inst = Instance();
355
2
    tls_ = new ThreadData(inst);
356
2
    {
357
      // Register it in the global chain, needs to be done before thread exit
358
      // handler registration
359
2
      MutexLock l(Mutex());
360
2
      inst->AddThreadData(tls_);
361
2
    }
362
    // Even it is not OS_MACOSX, need to register value for pthread_key_ so that
363
    // its exit handler will be triggered.
364
2
    if (pthread_setspecific(inst->pthread_key_, tls_) != 0) {
365
0
      {
366
0
        MutexLock l(Mutex());
367
0
        inst->RemoveThreadData(tls_);
368
0
      }
369
0
      delete tls_;
370
0
      abort();
371
0
    }
372
2
  }
373
75.0k
  return tls_;
374
75.0k
}
375
376
0
void* ThreadLocalPtr::StaticMeta::Get(uint32_t id) const {
377
0
  auto* tls = GetThreadLocal();
378
0
  if (UNLIKELY(id >= tls->entries.size())) {
379
0
    return nullptr;
380
0
  }
381
0
  return tls->entries[id].ptr.load(std::memory_order_acquire);
382
0
}
383
384
0
void ThreadLocalPtr::StaticMeta::Reset(uint32_t id, void* ptr) {
385
0
  auto* tls = GetThreadLocal();
386
0
  if (UNLIKELY(id >= tls->entries.size())) {
387
    // Need mutex to protect entries access within ReclaimId
388
0
    MutexLock l(Mutex());
389
0
    tls->entries.resize(id + 1);
390
0
  }
391
0
  tls->entries[id].ptr.store(ptr, std::memory_order_release);
392
0
}
393
394
37.5k
void* ThreadLocalPtr::StaticMeta::Swap(uint32_t id, void* ptr) {
395
37.5k
  auto* tls = GetThreadLocal();
396
37.5k
  if (UNLIKELY(id >= tls->entries.size())) {
397
    // Need mutex to protect entries access within ReclaimId
398
2
    MutexLock l(Mutex());
399
2
    tls->entries.resize(id + 1);
400
2
  }
401
37.5k
  return tls->entries[id].ptr.exchange(ptr, std::memory_order_acquire);
402
37.5k
}
403
404
bool ThreadLocalPtr::StaticMeta::CompareAndSwap(uint32_t id, void* ptr,
405
37.5k
                                                void*& expected) {
406
37.5k
  auto* tls = GetThreadLocal();
407
37.5k
  if (UNLIKELY(id >= tls->entries.size())) {
408
    // Need mutex to protect entries access within ReclaimId
409
0
    MutexLock l(Mutex());
410
0
    tls->entries.resize(id + 1);
411
0
  }
412
37.5k
  return tls->entries[id].ptr.compare_exchange_strong(
413
37.5k
      expected, ptr, std::memory_order_release, std::memory_order_relaxed);
414
37.5k
}
415
416
void ThreadLocalPtr::StaticMeta::Scrape(uint32_t id, autovector<void*>* ptrs,
417
8.66k
                                        void* const replacement) {
418
8.66k
  MutexLock l(Mutex());
419
17.3k
  for (ThreadData* t = head_.next; t != &head_; t = t->next) {
420
8.66k
    if (id < t->entries.size()) {
421
8.66k
      void* ptr =
422
8.66k
          t->entries[id].ptr.exchange(replacement, std::memory_order_acquire);
423
8.66k
      if (ptr != nullptr) {
424
4.79k
        ptrs->push_back(ptr);
425
4.79k
      }
426
8.66k
    }
427
8.66k
  }
428
8.66k
}
429
430
0
void ThreadLocalPtr::StaticMeta::Fold(uint32_t id, FoldFunc func, void* res) {
431
0
  MutexLock l(Mutex());
432
0
  for (ThreadData* t = head_.next; t != &head_; t = t->next) {
433
0
    if (id < t->entries.size()) {
434
0
      void* ptr = t->entries[id].ptr.load();
435
0
      if (ptr != nullptr) {
436
0
        func(ptr, res);
437
0
      }
438
0
    }
439
0
  }
440
0
}
441
442
0
uint32_t ThreadLocalPtr::TEST_PeekId() { return Instance()->PeekId(); }
443
444
125k
void ThreadLocalPtr::StaticMeta::SetHandler(uint32_t id, UnrefHandler handler) {
445
125k
  MutexLock l(Mutex());
446
125k
  handler_map_[id] = handler;
447
125k
}
448
449
125k
UnrefHandler ThreadLocalPtr::StaticMeta::GetHandler(uint32_t id) {
450
125k
  Mutex()->AssertHeld();
451
125k
  auto iter = handler_map_.find(id);
452
125k
  if (iter == handler_map_.end()) {
453
0
    return nullptr;
454
0
  }
455
125k
  return iter->second;
456
125k
}
457
458
125k
uint32_t ThreadLocalPtr::StaticMeta::GetId() {
459
125k
  MutexLock l(Mutex());
460
125k
  if (free_instance_ids_.empty()) {
461
5
    return next_instance_id_++;
462
5
  }
463
464
125k
  uint32_t id = free_instance_ids_.back();
465
125k
  free_instance_ids_.pop_back();
466
125k
  return id;
467
125k
}
468
469
0
uint32_t ThreadLocalPtr::StaticMeta::PeekId() const {
470
0
  MutexLock l(Mutex());
471
0
  if (!free_instance_ids_.empty()) {
472
0
    return free_instance_ids_.back();
473
0
  }
474
0
  return next_instance_id_;
475
0
}
476
477
125k
void ThreadLocalPtr::StaticMeta::ReclaimId(uint32_t id) {
478
  // This id is not used, go through all thread local data and release
479
  // corresponding value
480
125k
  MutexLock l(Mutex());
481
125k
  auto unref = GetHandler(id);
482
251k
  for (ThreadData* t = head_.next; t != &head_; t = t->next) {
483
125k
    if (id < t->entries.size()) {
484
125k
      void* ptr = t->entries[id].ptr.exchange(nullptr);
485
125k
      if (ptr != nullptr && unref != nullptr) {
486
22.6k
        unref(ptr);
487
22.6k
      }
488
125k
    }
489
125k
  }
490
125k
  handler_map_[id] = nullptr;
491
125k
  free_instance_ids_.push_back(id);
492
125k
}
493
494
ThreadLocalPtr::ThreadLocalPtr(UnrefHandler handler)
495
125k
    : id_(Instance()->GetId()) {
496
125k
  if (handler != nullptr) {
497
125k
    Instance()->SetHandler(id_, handler);
498
125k
  }
499
125k
}
500
501
125k
ThreadLocalPtr::~ThreadLocalPtr() { Instance()->ReclaimId(id_); }
502
503
0
void* ThreadLocalPtr::Get() const { return Instance()->Get(id_); }
504
505
0
void ThreadLocalPtr::Reset(void* ptr) { Instance()->Reset(id_, ptr); }
506
507
37.5k
void* ThreadLocalPtr::Swap(void* ptr) { return Instance()->Swap(id_, ptr); }
508
509
37.5k
bool ThreadLocalPtr::CompareAndSwap(void* ptr, void*& expected) {
510
37.5k
  return Instance()->CompareAndSwap(id_, ptr, expected);
511
37.5k
}
512
513
8.66k
void ThreadLocalPtr::Scrape(autovector<void*>* ptrs, void* const replacement) {
514
8.66k
  Instance()->Scrape(id_, ptrs, replacement);
515
8.66k
}
516
517
0
void ThreadLocalPtr::Fold(FoldFunc func, void* res) {
518
0
  Instance()->Fold(id_, func, res);
519
0
}
520
521
}  // namespace ROCKSDB_NAMESPACE