From: Kefu Chai Date: Thu, 16 Aug 2018 07:42:14 +0000 (+0800) Subject: common: add {condition_variable,shared_mutex}_debug X-Git-Tag: v14.0.1~540^2~6 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=8bf5d0975efc0a96c18c904424c49adb80bf73b4;p=ceph.git common: add {condition_variable,shared_mutex}_debug as drop-in replacements of their standard library counterparts, but with some debugging facilities offered by Cond and RWLock. Signed-off-by: Kefu Chai --- diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index c8c3567a0ebf..b82ec2b34aa0 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -54,6 +54,7 @@ set(common_srcs code_environment.cc common_init.cc compat.cc + condition_variable_debug.cc config.cc config_values.cc dns_resolve.cc @@ -85,6 +86,7 @@ set(common_srcs reverse.c run_cmd.cc scrub_types.cc + shared_mutex_debug.cc signal.cc snap_types.cc str_list.cc diff --git a/src/common/condition_variable_debug.cc b/src/common/condition_variable_debug.cc new file mode 100644 index 000000000000..ed459ea18cc1 --- /dev/null +++ b/src/common/condition_variable_debug.cc @@ -0,0 +1,77 @@ +#include "condition_variable_debug.h" +#include "common/mutex_debug.h" + +namespace ceph { + +condition_variable_debug::condition_variable_debug() + : waiter_mutex{nullptr} +{ + int r = pthread_cond_init(&cond, nullptr); + if (r) { + throw std::system_error(r, std::generic_category()); + } +} + +condition_variable_debug::~condition_variable_debug() +{ + pthread_cond_destroy(&cond); +} + +void condition_variable_debug::wait(std::unique_lock& lock) +{ + // make sure this cond is used with one mutex only + assert(waiter_mutex == nullptr || + waiter_mutex == lock.mutex()); + waiter_mutex = lock.mutex(); + assert(waiter_mutex->is_locked()); + waiter_mutex->_pre_unlock(); + if (int r = pthread_cond_wait(&cond, waiter_mutex->native_handle()); + r != 0) { + throw std::system_error(r, std::generic_category()); + } + waiter_mutex->_post_lock(); +} + +void condition_variable_debug::notify_one() +{ + // make sure signaler is holding the waiter's lock. + assert(waiter_mutex == nullptr || + waiter_mutex->is_locked()); + if (int r = pthread_cond_signal(&cond); r != 0) { + throw std::system_error(r, std::generic_category()); + } +} + +void condition_variable_debug::notify_all(bool sloppy) +{ + // make sure signaler is holding the waiter's lock. + assert(waiter_mutex == NULL || + waiter_mutex->is_locked()); + if (int r = pthread_cond_broadcast(&cond); r != 0 && !sloppy) { + throw std::system_error(r, std::generic_category()); + } +} + +std::cv_status condition_variable_debug::_wait_until(mutex_debug* mutex, + timespec* ts) +{ + // make sure this cond is used with one mutex only + assert(waiter_mutex == nullptr || + waiter_mutex == mutex); + waiter_mutex = mutex; + assert(waiter_mutex->is_locked()); + + waiter_mutex->_pre_unlock(); + int r = pthread_cond_timedwait(&cond, waiter_mutex->native_handle(), ts); + switch (r) { + case 0: + waiter_mutex->_post_lock(); + return std::cv_status::no_timeout; + case ETIMEDOUT: + return std::cv_status::timeout; + default: + throw std::system_error(r, std::generic_category()); + } +} + +} // namespace ceph diff --git a/src/common/condition_variable_debug.h b/src/common/condition_variable_debug.h new file mode 100644 index 000000000000..3241502ff812 --- /dev/null +++ b/src/common/condition_variable_debug.h @@ -0,0 +1,59 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#pragma once + +#include +#include +#include +#include "common/ceph_time.h" + +namespace ceph { + +namespace mutex_debug_detail { + template class mutex_debug_impl; +} + +class condition_variable_debug { + using mutex_debug = mutex_debug_detail::mutex_debug_impl; + + pthread_cond_t cond; + mutex_debug* waiter_mutex; + + condition_variable_debug& + operator=(const condition_variable_debug&) = delete; + condition_variable_debug(const condition_variable_debug&) = delete; + +public: + condition_variable_debug(); + ~condition_variable_debug(); + void wait(std::unique_lock& lock); + template + void wait(std::unique_lock& lock, Predicate pred) { + while (!pred()) { + wait(lock); + } + } + template + std::cv_status wait_until( + std::unique_lock& lock, + const std::chrono::time_point& when) { + timespec ts = when.to_timespec(when); + return _wait_until(lock.mutex(), &ts); + } + template + std::cv_status wait_for( + std::unique_lock& lock, + const std::chrono::duration& awhile) { + ceph::real_time when{ceph::real_clock::now()}; + when += awhile; + timespec ts = ceph::real_clock::to_timespec(when); + return _wait_until(lock.mutex(), &ts); + } + void notify_one(); + void notify_all(bool sloppy = false); +private: + std::cv_status _wait_until(mutex_debug* mutex, timespec* ts); +}; + +} // namespace ceph diff --git a/src/common/mutex_debug.h b/src/common/mutex_debug.h index 75117c61bdae..920776f0acba 100644 --- a/src/common/mutex_debug.h +++ b/src/common/mutex_debug.h @@ -191,6 +191,9 @@ public: throw std::system_error(r, std::generic_category()); } } + pthread_mutex_t* native_handle() { + return &m; + } }; } // namespace mutex_debug_detail typedef mutex_debug_detail::mutex_debug_impl mutex_debug; diff --git a/src/common/shared_mutex_debug.cc b/src/common/shared_mutex_debug.cc new file mode 100644 index 000000000000..f0f8cb35b5ce --- /dev/null +++ b/src/common/shared_mutex_debug.cc @@ -0,0 +1,166 @@ +#include "shared_mutex_debug.h" + +#include + +#include "acconfig.h" +#include "common/valgrind.h" + +namespace ceph { + +shared_mutex_debug::shared_mutex_debug(const std::string& n, + bool track_lock, + bool enable_lock_dep, + bool prioritize_write) + : mutex_debugging_base{n, false /* backtrace */, + nullptr /* cct for perf counter*/}, + track(track_lock), + lockdep(enable_lock_dep) +{ +#ifdef HAVE_PTHREAD_RWLOCKATTR_SETKIND_NP + if (prioritize_write) { + pthread_rwlockattr_t attr; + pthread_rwlockattr_init(&attr); + // PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP + // Setting the lock kind to this avoids writer starvation as long as + // long as any read locking is not done in a recursive fashion. + pthread_rwlockattr_setkind_np(&attr, + PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP); + pthread_rwlock_init(&rwlock, &attr); + pthread_rwlockattr_destroy(&attr); + } else +#endif + // Next block is in {} to possibly connect to the above if when code is used. + { + pthread_rwlock_init(&rwlock, NULL); + } + ANNOTATE_BENIGN_RACE_SIZED(&id, sizeof(id), "shared_mutex_debug lockdep id"); + ANNOTATE_BENIGN_RACE_SIZED(&nlock, sizeof(nlock), "shared_mutex_debug nwlock"); + ANNOTATE_BENIGN_RACE_SIZED(&nrlock, sizeof(nrlock), "shared_mutex_debug nrlock"); +} + +// exclusive +void shared_mutex_debug::lock() +{ + if (g_lockdep && lockdep) { + _will_lock(); + } + if (int r = pthread_rwlock_wrlock(&rwlock); r != 0) { + throw std::system_error(r, std::generic_category()); + } + if (lockdep && g_lockdep) { + _locked(); + } + _post_lock(); +} + +bool shared_mutex_debug::try_lock() +{ + int r = pthread_rwlock_trywrlock(&rwlock); + switch (r) { + case 0: + if (lockdep && g_lockdep) { + _locked(); + } + _post_lock(); + return true; + case EBUSY: + return false; + default: + throw std::system_error(r, std::generic_category()); + } +} + +void shared_mutex_debug::unlock() +{ + _pre_unlock(); + if (lockdep && g_lockdep) { + _will_unlock(); + } + if (int r = pthread_rwlock_unlock(&rwlock); r != 0) { + throw std::system_error(r, std::generic_category()); + } +} + +// shared locking +void shared_mutex_debug::lock_shared() +{ + if (lockdep && g_lockdep) { + _will_lock(); + } + if (int r = pthread_rwlock_rdlock(&rwlock); r != 0) { + throw std::system_error(r, std::generic_category()); + } + if (lockdep && g_lockdep) { + _locked(); + } + _post_lock_shared(); +} + +bool shared_mutex_debug::try_lock_shared() +{ + if (lockdep && g_lockdep) { + _will_unlock(); + } + switch (int r = pthread_rwlock_rdlock(&rwlock); r) { + case 0: + if (lockdep && g_lockdep) { + _locked(); + } + _post_lock_shared(); + return true; + case EBUSY: + return false; + default: + throw std::system_error(r, std::generic_category()); + } +} + +void shared_mutex_debug::unlock_shared() +{ + _pre_unlock_shared(); + if (lockdep && g_lockdep) { + _will_unlock(); + } + if (int r = pthread_rwlock_unlock(&rwlock); r != 0) { + throw std::system_error(r, std::generic_category()); + } +} + +// exclusive locking +void shared_mutex_debug::_pre_unlock() +{ + if (track) { + assert(nlock > 0); + --nlock; + assert(locked_by == std::this_thread::get_id()); + assert(nlock == 0); + locked_by = std::thread::id(); + } +} + +void shared_mutex_debug::_post_lock() +{ + if (track) { + assert(nlock == 0); + locked_by = std::this_thread::get_id(); + ++nlock; + } +} + +// shared locking +void shared_mutex_debug::_pre_unlock_shared() +{ + if (track) { + assert(nrlock > 0); + nrlock--; + } +} + +void shared_mutex_debug::_post_lock_shared() +{ + if (track) { + ++nrlock; + } +} + +} // namespace ceph diff --git a/src/common/shared_mutex_debug.h b/src/common/shared_mutex_debug.h new file mode 100644 index 000000000000..7a453ca11986 --- /dev/null +++ b/src/common/shared_mutex_debug.h @@ -0,0 +1,48 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#pragma once + +#include +#include + +#include "common/mutex_debug.h" + +namespace ceph { + +class shared_mutex_debug : + public ceph::mutex_debug_detail::mutex_debugging_base +{ + pthread_rwlock_t rwlock; + const bool track; + const bool lockdep; + std::atomic nrlock{0}; + +public: + // Mutex concept is DefaultConstructible + shared_mutex_debug() + : shared_mutex_debug{std::string{}} + {} + shared_mutex_debug(const std::string& n, + bool track_lock=true, + bool enable_lock_dep=true, + bool prioritize_write=false); + // exclusive locking + void lock(); + bool try_lock(); + void unlock(); + // shared locking + void lock_shared(); + bool try_lock_shared(); + void unlock_shared(); + +private: + // exclusive locking + void _pre_unlock(); + void _post_lock(); + // shared locking + void _pre_unlock_shared(); + void _post_lock_shared(); +}; + +} // namespace ceph