Better port::Mutex::AssertHeld() and AssertNotHeld()

Summary:
Using ThreadLocalPtr as a flag to determine if a mutex is locked or not enables us to implement AssertNotHeld(). It also makes AssertHeld() actually correct.

I had to remove port::Mutex as a dependency for util/thread_local.h, but that's fine since we can just use std::mutex :)

Test Plan: make check

Reviewers: ljin, dhruba, haobo, sdong, yhchiang

Reviewed By: ljin

CC: leveldb

Differential Revision: https://reviews.facebook.net/D18171
main
Igor Canadi 11 years ago
parent 29123408b0
commit ddafceb6c2
  1. 2
      db/db_test.cc
  2. 21
      port/port_posix.cc
  3. 19
      port/port_posix.h
  4. 33
      util/thread_local.cc
  5. 5
      util/thread_local.h

@ -415,7 +415,7 @@ class DBTest {
options.db_log_dir = test::TmpDir(); options.db_log_dir = test::TmpDir();
break; break;
case kWalDir: case kWalDir:
options.wal_dir = "/tmp/wal"; options.wal_dir = test::TmpDir() + "/wal_dir";
break; break;
case kManifestFileSize: case kManifestFileSize:
options.max_manifest_file_size = 50; // 50 bytes options.max_manifest_file_size = 50; // 50 bytes

@ -9,11 +9,13 @@
#include "port/port_posix.h" #include "port/port_posix.h"
#include <memory>
#include <cstdlib> #include <cstdlib>
#include <stdio.h> #include <stdio.h>
#include <assert.h> #include <assert.h>
#include <string.h> #include <string.h>
#include "util/logging.h" #include "util/logging.h"
#include "util/thread_local.h"
namespace rocksdb { namespace rocksdb {
namespace port { namespace port {
@ -26,6 +28,9 @@ static void PthreadCall(const char* label, int result) {
} }
Mutex::Mutex(bool adaptive) { Mutex::Mutex(bool adaptive) {
#ifndef NDEBUG
locked_.reset(new ThreadLocalPtr());
#endif
#ifdef OS_LINUX #ifdef OS_LINUX
if (!adaptive) { if (!adaptive) {
PthreadCall("init mutex", pthread_mutex_init(&mu_, NULL)); PthreadCall("init mutex", pthread_mutex_init(&mu_, NULL));
@ -49,20 +54,26 @@ Mutex::~Mutex() { PthreadCall("destroy mutex", pthread_mutex_destroy(&mu_)); }
void Mutex::Lock() { void Mutex::Lock() {
PthreadCall("lock", pthread_mutex_lock(&mu_)); PthreadCall("lock", pthread_mutex_lock(&mu_));
#ifndef NDEBUG #ifndef NDEBUG
locked_ = true; locked_->Reset(this);
#endif #endif
} }
void Mutex::Unlock() { void Mutex::Unlock() {
#ifndef NDEBUG #ifndef NDEBUG
locked_ = false; locked_->Reset(nullptr);
#endif #endif
PthreadCall("unlock", pthread_mutex_unlock(&mu_)); PthreadCall("unlock", pthread_mutex_unlock(&mu_));
} }
void Mutex::AssertHeld() { void Mutex::AssertHeld() {
#ifndef NDEBUG #ifndef NDEBUG
assert(locked_); assert(locked_->Get() == this);
#endif
}
void Mutex::AssertNotHeld() {
#ifndef NDEBUG
assert(locked_->Get() == nullptr);
#endif #endif
} }
@ -75,11 +86,11 @@ CondVar::~CondVar() { PthreadCall("destroy cv", pthread_cond_destroy(&cv_)); }
void CondVar::Wait() { void CondVar::Wait() {
#ifndef NDEBUG #ifndef NDEBUG
mu_->locked_ = false; mu_->locked_->Reset(nullptr);
#endif #endif
PthreadCall("wait", pthread_cond_wait(&cv_, &mu_->mu_)); PthreadCall("wait", pthread_cond_wait(&cv_, &mu_->mu_));
#ifndef NDEBUG #ifndef NDEBUG
mu_->locked_ = true; mu_->locked_->Reset(mu_);
#endif #endif
} }

@ -9,8 +9,7 @@
// //
// See port_example.h for documentation for the following types/functions. // See port_example.h for documentation for the following types/functions.
#ifndef STORAGE_LEVELDB_PORT_PORT_POSIX_H_ #pragma once
#define STORAGE_LEVELDB_PORT_PORT_POSIX_H_
#undef PLATFORM_IS_LITTLE_ENDIAN #undef PLATFORM_IS_LITTLE_ENDIAN
#if defined(OS_MACOSX) #if defined(OS_MACOSX)
@ -51,6 +50,7 @@
#include <lz4hc.h> #include <lz4hc.h>
#endif #endif
#include <memory>
#include <stdint.h> #include <stdint.h>
#include <string> #include <string>
#include <string.h> #include <string.h>
@ -83,6 +83,9 @@
#endif #endif
namespace rocksdb { namespace rocksdb {
class ThreadLocalPtr;
namespace port { namespace port {
static const bool kLittleEndian = PLATFORM_IS_LITTLE_ENDIAN; static const bool kLittleEndian = PLATFORM_IS_LITTLE_ENDIAN;
@ -90,6 +93,10 @@ static const bool kLittleEndian = PLATFORM_IS_LITTLE_ENDIAN;
class CondVar; class CondVar;
// DO NOT declare this Mutex as static ever. Inside it depends on ThreadLocalPtr
// and its Lock() and Unlock() function depend on ThreadLocalPtr::StaticMeta,
// which is also declared static. We can't really control static
// deinitialization order.
class Mutex { class Mutex {
public: public:
/* implicit */ Mutex(bool adaptive = false); /* implicit */ Mutex(bool adaptive = false);
@ -97,15 +104,15 @@ class Mutex {
void Lock(); void Lock();
void Unlock(); void Unlock();
// this will assert if the mutex is not locked
// it does NOT verify that mutex is held by a calling thread
void AssertHeld(); void AssertHeld();
void AssertNotHeld();
private: private:
friend class CondVar; friend class CondVar;
pthread_mutex_t mu_; pthread_mutex_t mu_;
#ifndef NDEBUG #ifndef NDEBUG
bool locked_; std::unique_ptr<ThreadLocalPtr> locked_;
#endif #endif
// No copying // No copying
@ -480,5 +487,3 @@ inline bool LZ4HC_Compress(const CompressionOptions &opts, const char* input,
} // namespace port } // namespace port
} // namespace rocksdb } // namespace rocksdb
#endif // STORAGE_LEVELDB_PORT_PORT_POSIX_H_

@ -8,21 +8,23 @@
// found in the LICENSE file. See the AUTHORS file for names of contributors. // found in the LICENSE file. See the AUTHORS file for names of contributors.
#include "util/thread_local.h" #include "util/thread_local.h"
#include <mutex>
#include "util/mutexlock.h" #include "util/mutexlock.h"
#include "port/likely.h" #include "port/likely.h"
namespace rocksdb { namespace rocksdb {
std::unique_ptr<ThreadLocalPtr::StaticMeta> ThreadLocalPtr::StaticMeta::inst_; std::unique_ptr<ThreadLocalPtr::StaticMeta> ThreadLocalPtr::StaticMeta::inst_;
port::Mutex ThreadLocalPtr::StaticMeta::mutex_; std::mutex ThreadLocalPtr::StaticMeta::mutex_;
#if !defined(OS_MACOSX) #if !defined(OS_MACOSX)
__thread ThreadLocalPtr::ThreadData* ThreadLocalPtr::StaticMeta::tls_ = nullptr; __thread ThreadLocalPtr::ThreadData* ThreadLocalPtr::StaticMeta::tls_ = nullptr;
#endif #endif
ThreadLocalPtr::StaticMeta* ThreadLocalPtr::StaticMeta::Instance() { ThreadLocalPtr::StaticMeta* ThreadLocalPtr::StaticMeta::Instance() {
if (UNLIKELY(inst_ == nullptr)) { if (UNLIKELY(inst_ == nullptr)) {
MutexLock l(&mutex_); std::lock_guard<std::mutex> l(mutex_);
if (inst_ == nullptr) { if (inst_ == nullptr) {
inst_.reset(new StaticMeta()); inst_.reset(new StaticMeta());
} }
@ -37,7 +39,7 @@ void ThreadLocalPtr::StaticMeta::OnThreadExit(void* ptr) {
auto* inst = Instance(); auto* inst = Instance();
pthread_setspecific(inst->pthread_key_, nullptr); pthread_setspecific(inst->pthread_key_, nullptr);
MutexLock l(&mutex_); std::lock_guard<std::mutex> l(mutex_);
inst->RemoveThreadData(tls); inst->RemoveThreadData(tls);
// Unref stored pointers of current thread from all instances // Unref stored pointers of current thread from all instances
uint32_t id = 0; uint32_t id = 0;
@ -64,7 +66,6 @@ ThreadLocalPtr::StaticMeta::StaticMeta() : next_instance_id_(0) {
} }
void ThreadLocalPtr::StaticMeta::AddThreadData(ThreadLocalPtr::ThreadData* d) { void ThreadLocalPtr::StaticMeta::AddThreadData(ThreadLocalPtr::ThreadData* d) {
mutex_.AssertHeld();
d->next = &head_; d->next = &head_;
d->prev = head_.prev; d->prev = head_.prev;
head_.prev->next = d; head_.prev->next = d;
@ -73,7 +74,6 @@ void ThreadLocalPtr::StaticMeta::AddThreadData(ThreadLocalPtr::ThreadData* d) {
void ThreadLocalPtr::StaticMeta::RemoveThreadData( void ThreadLocalPtr::StaticMeta::RemoveThreadData(
ThreadLocalPtr::ThreadData* d) { ThreadLocalPtr::ThreadData* d) {
mutex_.AssertHeld();
d->next->prev = d->prev; d->next->prev = d->prev;
d->prev->next = d->next; d->prev->next = d->next;
d->next = d->prev = d; d->next = d->prev = d;
@ -93,14 +93,14 @@ ThreadLocalPtr::ThreadData* ThreadLocalPtr::StaticMeta::GetThreadLocal() {
{ {
// Register it in the global chain, needs to be done before thread exit // Register it in the global chain, needs to be done before thread exit
// handler registration // handler registration
MutexLock l(&mutex_); std::lock_guard<std::mutex> l(mutex_);
inst->AddThreadData(tls_); inst->AddThreadData(tls_);
} }
// Even it is not OS_MACOSX, need to register value for pthread_key_ so that // Even it is not OS_MACOSX, need to register value for pthread_key_ so that
// its exit handler will be triggered. // its exit handler will be triggered.
if (pthread_setspecific(inst->pthread_key_, tls_) != 0) { if (pthread_setspecific(inst->pthread_key_, tls_) != 0) {
{ {
MutexLock l(&mutex_); std::lock_guard<std::mutex> l(mutex_);
inst->RemoveThreadData(tls_); inst->RemoveThreadData(tls_);
} }
delete tls_; delete tls_;
@ -122,7 +122,7 @@ void ThreadLocalPtr::StaticMeta::Reset(uint32_t id, void* ptr) {
auto* tls = GetThreadLocal(); auto* tls = GetThreadLocal();
if (UNLIKELY(id >= tls->entries.size())) { if (UNLIKELY(id >= tls->entries.size())) {
// Need mutex to protect entries access within ReclaimId // Need mutex to protect entries access within ReclaimId
MutexLock l(&mutex_); std::lock_guard<std::mutex> l(mutex_);
tls->entries.resize(id + 1); tls->entries.resize(id + 1);
} }
tls->entries[id].ptr.store(ptr, std::memory_order_relaxed); tls->entries[id].ptr.store(ptr, std::memory_order_relaxed);
@ -132,7 +132,7 @@ void* ThreadLocalPtr::StaticMeta::Swap(uint32_t id, void* ptr) {
auto* tls = GetThreadLocal(); auto* tls = GetThreadLocal();
if (UNLIKELY(id >= tls->entries.size())) { if (UNLIKELY(id >= tls->entries.size())) {
// Need mutex to protect entries access within ReclaimId // Need mutex to protect entries access within ReclaimId
MutexLock l(&mutex_); std::lock_guard<std::mutex> l(mutex_);
tls->entries.resize(id + 1); tls->entries.resize(id + 1);
} }
return tls->entries[id].ptr.exchange(ptr, std::memory_order_relaxed); return tls->entries[id].ptr.exchange(ptr, std::memory_order_relaxed);
@ -143,7 +143,7 @@ bool ThreadLocalPtr::StaticMeta::CompareAndSwap(uint32_t id, void* ptr,
auto* tls = GetThreadLocal(); auto* tls = GetThreadLocal();
if (UNLIKELY(id >= tls->entries.size())) { if (UNLIKELY(id >= tls->entries.size())) {
// Need mutex to protect entries access within ReclaimId // Need mutex to protect entries access within ReclaimId
MutexLock l(&mutex_); std::lock_guard<std::mutex> l(mutex_);
tls->entries.resize(id + 1); tls->entries.resize(id + 1);
} }
return tls->entries[id].ptr.compare_exchange_strong(expected, ptr, return tls->entries[id].ptr.compare_exchange_strong(expected, ptr,
@ -152,7 +152,7 @@ bool ThreadLocalPtr::StaticMeta::CompareAndSwap(uint32_t id, void* ptr,
void ThreadLocalPtr::StaticMeta::Scrape(uint32_t id, autovector<void*>* ptrs, void ThreadLocalPtr::StaticMeta::Scrape(uint32_t id, autovector<void*>* ptrs,
void* const replacement) { void* const replacement) {
MutexLock l(&mutex_); std::lock_guard<std::mutex> l(mutex_);
for (ThreadData* t = head_.next; t != &head_; t = t->next) { for (ThreadData* t = head_.next; t != &head_; t = t->next) {
if (id < t->entries.size()) { if (id < t->entries.size()) {
void* ptr = void* ptr =
@ -165,12 +165,11 @@ void ThreadLocalPtr::StaticMeta::Scrape(uint32_t id, autovector<void*>* ptrs,
} }
void ThreadLocalPtr::StaticMeta::SetHandler(uint32_t id, UnrefHandler handler) { void ThreadLocalPtr::StaticMeta::SetHandler(uint32_t id, UnrefHandler handler) {
MutexLock l(&mutex_); std::lock_guard<std::mutex> l(mutex_);
handler_map_[id] = handler; handler_map_[id] = handler;
} }
UnrefHandler ThreadLocalPtr::StaticMeta::GetHandler(uint32_t id) { UnrefHandler ThreadLocalPtr::StaticMeta::GetHandler(uint32_t id) {
mutex_.AssertHeld();
auto iter = handler_map_.find(id); auto iter = handler_map_.find(id);
if (iter == handler_map_.end()) { if (iter == handler_map_.end()) {
return nullptr; return nullptr;
@ -179,7 +178,7 @@ UnrefHandler ThreadLocalPtr::StaticMeta::GetHandler(uint32_t id) {
} }
uint32_t ThreadLocalPtr::StaticMeta::GetId() { uint32_t ThreadLocalPtr::StaticMeta::GetId() {
MutexLock l(&mutex_); std::lock_guard<std::mutex> l(mutex_);
if (free_instance_ids_.empty()) { if (free_instance_ids_.empty()) {
return next_instance_id_++; return next_instance_id_++;
} }
@ -190,7 +189,7 @@ uint32_t ThreadLocalPtr::StaticMeta::GetId() {
} }
uint32_t ThreadLocalPtr::StaticMeta::PeekId() const { uint32_t ThreadLocalPtr::StaticMeta::PeekId() const {
MutexLock l(&mutex_); std::lock_guard<std::mutex> l(mutex_);
if (!free_instance_ids_.empty()) { if (!free_instance_ids_.empty()) {
return free_instance_ids_.back(); return free_instance_ids_.back();
} }
@ -200,7 +199,7 @@ uint32_t ThreadLocalPtr::StaticMeta::PeekId() const {
void ThreadLocalPtr::StaticMeta::ReclaimId(uint32_t id) { void ThreadLocalPtr::StaticMeta::ReclaimId(uint32_t id) {
// This id is not used, go through all thread local data and release // This id is not used, go through all thread local data and release
// corresponding value // corresponding value
MutexLock l(&mutex_); std::lock_guard<std::mutex> l(mutex_);
auto unref = GetHandler(id); auto unref = GetHandler(id);
for (ThreadData* t = head_.next; t != &head_; t = t->next) { for (ThreadData* t = head_.next; t != &head_; t = t->next) {
if (id < t->entries.size()) { if (id < t->entries.size()) {

@ -10,13 +10,12 @@
#pragma once #pragma once
#include <atomic> #include <atomic>
#include <mutex>
#include <memory> #include <memory>
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
#include "util/autovector.h" #include "util/autovector.h"
#include "port/port_posix.h"
#include "util/thread_local.h"
namespace rocksdb { namespace rocksdb {
@ -153,7 +152,7 @@ class ThreadLocalPtr {
// protect inst, next_instance_id_, free_instance_ids_, head_, // protect inst, next_instance_id_, free_instance_ids_, head_,
// ThreadData.entries // ThreadData.entries
static port::Mutex mutex_; static std::mutex mutex_;
#if !defined(OS_MACOSX) #if !defined(OS_MACOSX)
// Thread local storage // Thread local storage
static __thread ThreadData* tls_; static __thread ThreadData* tls_;

Loading…
Cancel
Save