]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
common: add {condition_variable,shared_mutex}_debug
authorKefu Chai <kchai@redhat.com>
Thu, 16 Aug 2018 07:42:14 +0000 (15:42 +0800)
committerKefu Chai <kchai@redhat.com>
Thu, 16 Aug 2018 09:33:48 +0000 (17:33 +0800)
as drop-in replacements of their standard library counterparts, but with
some debugging facilities offered by Cond and RWLock.

Signed-off-by: Kefu Chai <kchai@redhat.com>
src/common/CMakeLists.txt
src/common/condition_variable_debug.cc [new file with mode: 0644]
src/common/condition_variable_debug.h [new file with mode: 0644]
src/common/mutex_debug.h
src/common/shared_mutex_debug.cc [new file with mode: 0644]
src/common/shared_mutex_debug.h [new file with mode: 0644]

index c8c3567a0ebf9e778b2080d4eb743d59517d044b..b82ec2b34aa006e2fd5073432c366f9b9a8ef537 100644 (file)
@@ -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 (file)
index 0000000..ed459ea
--- /dev/null
@@ -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<mutex_debug>& 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 (file)
index 0000000..3241502
--- /dev/null
@@ -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 <condition_variable>
+#include <ctime>
+#include <pthread.h>
+#include "common/ceph_time.h"
+
+namespace ceph {
+
+namespace mutex_debug_detail {
+  template<bool> class mutex_debug_impl;
+}
+
+class condition_variable_debug {
+  using mutex_debug = mutex_debug_detail::mutex_debug_impl<false>;
+
+  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<mutex_debug>& lock);
+  template<class Predicate>
+  void wait(std::unique_lock<mutex_debug>& lock, Predicate pred) {
+    while (!pred()) {
+      wait(lock);
+    }
+  }
+  template<class Clock, class Duration>
+  std::cv_status wait_until(
+    std::unique_lock<mutex_debug>& lock,
+    const std::chrono::time_point<Clock, Duration>& when) {
+    timespec ts = when.to_timespec(when);
+    return _wait_until(lock.mutex(), &ts);
+  }
+  template<class Rep, class Period>
+  std::cv_status wait_for(
+    std::unique_lock<mutex_debug>& lock,
+    const std::chrono::duration<Rep, Period>& 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
index 75117c61bdaead44c6e1ff9ecaaedd51cd390c04..920776f0acba730ca59597c78d88aa4bfedb686a 100644 (file)
@@ -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<false> mutex_debug;
diff --git a/src/common/shared_mutex_debug.cc b/src/common/shared_mutex_debug.cc
new file mode 100644 (file)
index 0000000..f0f8cb3
--- /dev/null
@@ -0,0 +1,166 @@
+#include "shared_mutex_debug.h"
+
+#include <system_error>
+
+#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 (file)
index 0000000..7a453ca
--- /dev/null
@@ -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 <pthread.h>
+#include <atomic>
+
+#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<unsigned> 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