/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 |