b2795b799e
Summary: This is not a generic thread local implementation in the sense that it only takes pointer. But it does support multiple instances per thread and lets user plugin function to perform cleanup when thread exits or an instance gets destroyed. Test Plan: unit test for now Reviewers: haobo, igor, sdong, dhruba Reviewed By: igor CC: leveldb, kailiu Differential Revision: https://reviews.facebook.net/D16131
237 lines
6.5 KiB
C++
237 lines
6.5 KiB
C++
// Copyright (c) 2013, Facebook, Inc. All rights reserved.
|
|
// This source code is licensed under the BSD-style license found in the
|
|
// LICENSE file in the root directory of this source tree. An additional grant
|
|
// of patent rights can be found in the PATENTS file in the same directory.
|
|
//
|
|
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
|
|
|
#include "util/thread_local.h"
|
|
#include "util/mutexlock.h"
|
|
|
|
#if defined(__GNUC__) && __GNUC__ >= 4
|
|
#define UNLIKELY(x) (__builtin_expect((x), 0))
|
|
#else
|
|
#define UNLIKELY(x) (x)
|
|
#endif
|
|
|
|
namespace rocksdb {
|
|
|
|
std::unique_ptr<ThreadLocalPtr::StaticMeta> ThreadLocalPtr::StaticMeta::inst_;
|
|
port::Mutex ThreadLocalPtr::StaticMeta::mutex_;
|
|
#if !defined(OS_MACOSX)
|
|
__thread ThreadLocalPtr::ThreadData* ThreadLocalPtr::StaticMeta::tls_ = nullptr;
|
|
#endif
|
|
|
|
ThreadLocalPtr::StaticMeta* ThreadLocalPtr::StaticMeta::Instance() {
|
|
if (UNLIKELY(inst_ == nullptr)) {
|
|
MutexLock l(&mutex_);
|
|
if (inst_ == nullptr) {
|
|
inst_.reset(new StaticMeta());
|
|
}
|
|
}
|
|
return inst_.get();
|
|
}
|
|
|
|
void ThreadLocalPtr::StaticMeta::OnThreadExit(void* ptr) {
|
|
auto* tls = static_cast<ThreadData*>(ptr);
|
|
assert(tls != nullptr);
|
|
|
|
auto* inst = Instance();
|
|
pthread_setspecific(inst->pthread_key_, nullptr);
|
|
|
|
MutexLock l(&mutex_);
|
|
inst->RemoveThreadData(tls);
|
|
// Unref stored pointers of current thread from all instances
|
|
uint32_t id = 0;
|
|
for (auto& e : tls->entries) {
|
|
void* raw = e.ptr.load(std::memory_order_relaxed);
|
|
if (raw != nullptr) {
|
|
auto unref = inst->GetHandler(id);
|
|
if (unref != nullptr) {
|
|
unref(raw);
|
|
}
|
|
}
|
|
++id;
|
|
}
|
|
// Delete thread local structure no matter if it is Mac platform
|
|
delete tls;
|
|
}
|
|
|
|
ThreadLocalPtr::StaticMeta::StaticMeta() : next_instance_id_(0) {
|
|
if (pthread_key_create(&pthread_key_, &OnThreadExit) != 0) {
|
|
throw std::runtime_error("pthread_key_create failed");
|
|
}
|
|
head_.next = &head_;
|
|
head_.prev = &head_;
|
|
}
|
|
|
|
void ThreadLocalPtr::StaticMeta::AddThreadData(ThreadLocalPtr::ThreadData* d) {
|
|
mutex_.AssertHeld();
|
|
d->next = &head_;
|
|
d->prev = head_.prev;
|
|
head_.prev->next = d;
|
|
head_.prev = d;
|
|
}
|
|
|
|
void ThreadLocalPtr::StaticMeta::RemoveThreadData(
|
|
ThreadLocalPtr::ThreadData* d) {
|
|
mutex_.AssertHeld();
|
|
d->next->prev = d->prev;
|
|
d->prev->next = d->next;
|
|
d->next = d->prev = d;
|
|
}
|
|
|
|
ThreadLocalPtr::ThreadData* ThreadLocalPtr::StaticMeta::GetThreadLocal() {
|
|
#if defined(OS_MACOSX)
|
|
// Make this local variable name look like a member variable so that we
|
|
// can share all the code below
|
|
ThreadData* tls_ =
|
|
static_cast<ThreadData*>(pthread_getspecific(Instance()->pthread_key_));
|
|
#endif
|
|
|
|
if (UNLIKELY(tls_ == nullptr)) {
|
|
auto* inst = Instance();
|
|
tls_ = new ThreadData();
|
|
{
|
|
// Register it in the global chain, needs to be done before thread exit
|
|
// handler registration
|
|
MutexLock l(&mutex_);
|
|
inst->AddThreadData(tls_);
|
|
}
|
|
// Even it is not OS_MACOSX, need to register value for pthread_key_ so that
|
|
// its exit handler will be triggered.
|
|
if (pthread_setspecific(inst->pthread_key_, tls_) != 0) {
|
|
{
|
|
MutexLock l(&mutex_);
|
|
inst->RemoveThreadData(tls_);
|
|
}
|
|
delete tls_;
|
|
throw std::runtime_error("pthread_setspecific failed");
|
|
}
|
|
}
|
|
return tls_;
|
|
}
|
|
|
|
void* ThreadLocalPtr::StaticMeta::Get(uint32_t id) const {
|
|
auto* tls = GetThreadLocal();
|
|
if (UNLIKELY(id >= tls->entries.size())) {
|
|
return nullptr;
|
|
}
|
|
return tls->entries[id].ptr.load(std::memory_order_relaxed);
|
|
}
|
|
|
|
void ThreadLocalPtr::StaticMeta::Reset(uint32_t id, void* ptr) {
|
|
auto* tls = GetThreadLocal();
|
|
if (UNLIKELY(id >= tls->entries.size())) {
|
|
// Need mutex to protect entries access within ReclaimId
|
|
MutexLock l(&mutex_);
|
|
tls->entries.resize(id + 1);
|
|
}
|
|
tls->entries[id].ptr.store(ptr, std::memory_order_relaxed);
|
|
}
|
|
|
|
void* ThreadLocalPtr::StaticMeta::Swap(uint32_t id, void* ptr) {
|
|
auto* tls = GetThreadLocal();
|
|
if (UNLIKELY(id >= tls->entries.size())) {
|
|
// Need mutex to protect entries access within ReclaimId
|
|
MutexLock l(&mutex_);
|
|
tls->entries.resize(id + 1);
|
|
}
|
|
return tls->entries[id].ptr.exchange(ptr, std::memory_order_relaxed);
|
|
}
|
|
|
|
void ThreadLocalPtr::StaticMeta::Scrape(uint32_t id, autovector<void*>* ptrs) {
|
|
MutexLock l(&mutex_);
|
|
for (ThreadData* t = head_.next; t != &head_; t = t->next) {
|
|
if (id < t->entries.size()) {
|
|
void* ptr =
|
|
t->entries[id].ptr.exchange(nullptr, std::memory_order_relaxed);
|
|
if (ptr != nullptr) {
|
|
ptrs->push_back(ptr);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ThreadLocalPtr::StaticMeta::SetHandler(uint32_t id, UnrefHandler handler) {
|
|
MutexLock l(&mutex_);
|
|
handler_map_[id] = handler;
|
|
}
|
|
|
|
UnrefHandler ThreadLocalPtr::StaticMeta::GetHandler(uint32_t id) {
|
|
mutex_.AssertHeld();
|
|
auto iter = handler_map_.find(id);
|
|
if (iter == handler_map_.end()) {
|
|
return nullptr;
|
|
}
|
|
return iter->second;
|
|
}
|
|
|
|
uint32_t ThreadLocalPtr::StaticMeta::GetId() {
|
|
MutexLock l(&mutex_);
|
|
if (free_instance_ids_.empty()) {
|
|
return next_instance_id_++;
|
|
}
|
|
|
|
uint32_t id = free_instance_ids_.back();
|
|
free_instance_ids_.pop_back();
|
|
return id;
|
|
}
|
|
|
|
uint32_t ThreadLocalPtr::StaticMeta::PeekId() const {
|
|
MutexLock l(&mutex_);
|
|
if (!free_instance_ids_.empty()) {
|
|
return free_instance_ids_.back();
|
|
}
|
|
return next_instance_id_;
|
|
}
|
|
|
|
void ThreadLocalPtr::StaticMeta::ReclaimId(uint32_t id) {
|
|
// This id is not used, go through all thread local data and release
|
|
// corresponding value
|
|
MutexLock l(&mutex_);
|
|
auto unref = GetHandler(id);
|
|
for (ThreadData* t = head_.next; t != &head_; t = t->next) {
|
|
if (id < t->entries.size()) {
|
|
void* ptr =
|
|
t->entries[id].ptr.exchange(nullptr, std::memory_order_relaxed);
|
|
if (ptr != nullptr && unref != nullptr) {
|
|
unref(ptr);
|
|
}
|
|
}
|
|
}
|
|
handler_map_[id] = nullptr;
|
|
free_instance_ids_.push_back(id);
|
|
}
|
|
|
|
ThreadLocalPtr::ThreadLocalPtr(UnrefHandler handler)
|
|
: id_(StaticMeta::Instance()->GetId()) {
|
|
if (handler != nullptr) {
|
|
StaticMeta::Instance()->SetHandler(id_, handler);
|
|
}
|
|
}
|
|
|
|
ThreadLocalPtr::~ThreadLocalPtr() {
|
|
StaticMeta::Instance()->ReclaimId(id_);
|
|
}
|
|
|
|
void* ThreadLocalPtr::Get() const {
|
|
return StaticMeta::Instance()->Get(id_);
|
|
}
|
|
|
|
void ThreadLocalPtr::Reset(void* ptr) {
|
|
StaticMeta::Instance()->Reset(id_, ptr);
|
|
}
|
|
|
|
void* ThreadLocalPtr::Swap(void* ptr) {
|
|
return StaticMeta::Instance()->Swap(id_, ptr);
|
|
}
|
|
|
|
void ThreadLocalPtr::Scrape(autovector<void*>* ptrs) {
|
|
StaticMeta::Instance()->Scrape(id_, ptrs);
|
|
}
|
|
|
|
} // namespace rocksdb
|