This is an automated email from the ASF dual-hosted git repository.

wwbmmm pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/brpc.git


The following commit(s) were added to refs/heads/master by this push:
     new f4e00acc Support detection of mutex deadlock caused by double lock 
(#2765)
f4e00acc is described below

commit f4e00acc30b8824ba7d43c215a50c0f54d682ea0
Author: Bright Chen <chenguangmin...@foxmail.com>
AuthorDate: Sun Oct 13 20:37:53 2024 +0800

    Support detection of mutex deadlock caused by double lock (#2765)
    
    * Support detection of bthread mutex and FastMutex deadlock caused by 
double lock
    
    * Support detection of pthread mutex deadlock caused by double lock
---
 .github/workflows/ci-linux.yml  |  12 +-
 BUILD.bazel                     |   3 +
 CMakeLists.txt                  |   8 +-
 bazel/config/BUILD.bazel        |   6 +
 config_brpc.sh                  |   6 +-
 src/bthread/mutex.cpp           | 280 ++++++++++++++++++++++++++++++++++++----
 src/bthread/mutex.h             |   9 +-
 src/bthread/types.h             |  15 ++-
 test/bthread_mutex_unittest.cpp |   5 +
 9 files changed, 308 insertions(+), 36 deletions(-)

diff --git a/.github/workflows/ci-linux.yml b/.github/workflows/ci-linux.yml
index 99c0bd1b..14961ebb 100644
--- a/.github/workflows/ci-linux.yml
+++ b/.github/workflows/ci-linux.yml
@@ -61,7 +61,7 @@ jobs:
     - uses: ./.github/actions/install-all-dependences
     - uses: ./.github/actions/init-make-config
       with:
-       options: --cc=gcc --cxx=g++ --with-thrift --with-glog --with-rdma  
--with-debug-bthread-sche-safety
+       options: --cc=gcc --cxx=g++ --with-thrift --with-glog --with-rdma  
--with-debug-bthread-sche-safety --with-debug-lock
     - name: compile
       run: |
         make -j ${{env.proc_num}}
@@ -76,7 +76,7 @@ jobs:
            export CC=gcc && export CXX=g++
            mkdir build
            cd build
-           cmake -DWITH_MESALINK=OFF -DWITH_GLOG=ON -DWITH_THRIFT=ON 
-DWITH_RDMA=ON -DWITH_DEBUG_BTHREAD_SCHE_SAFETY=ON ..
+           cmake -DWITH_MESALINK=OFF -DWITH_GLOG=ON -DWITH_THRIFT=ON 
-DWITH_RDMA=ON -DWITH_DEBUG_BTHREAD_SCHE_SAFETY=ON -DWITH_DEBUG_LOCK=ON ..
     - name: compile
       run: |
            cd build
@@ -86,7 +86,7 @@ jobs:
     runs-on: ubuntu-20.04
     steps:
     - uses: actions/checkout@v2
-    - run: bazel test --verbose_failures --define with_mesalink=false --define 
with_glog=true --define with_thrift=true --define 
with_debug_bthread_sche_safety=true -- //... -//example/...
+    - run: bazel test --verbose_failures --define with_mesalink=false --define 
with_glog=true --define with_thrift=true --define 
with_debug_bthread_sche_safety=true --define with_debug_lock=true -- //... 
-//example/...
 
   clang-compile-with-make:
     runs-on: ubuntu-20.04
@@ -135,7 +135,7 @@ jobs:
     - uses: ./.github/actions/install-all-dependences
     - uses: ./.github/actions/init-make-config
       with:
-        options: --cc=clang --cxx=clang++ --with-thrift --with-glog 
--with-rdma  --with-debug-bthread-sche-safety
+        options: --cc=clang --cxx=clang++ --with-thrift --with-glog 
--with-rdma  --with-debug-bthread-sche-safety --with-debug-lock
     - name: compile
       run: |
            make -j ${{env.proc_num}}
@@ -150,7 +150,7 @@ jobs:
            export CC=clang && export CXX=clang++
            mkdir build
            cd build
-           cmake -DWITH_MESALINK=OFF -DWITH_GLOG=ON -DWITH_THRIFT=ON 
-DWITH_RDMA=ON -DWITH_DEBUG_BTHREAD_SCHE_SAFETY=ON ..
+           cmake -DWITH_MESALINK=OFF -DWITH_GLOG=ON -DWITH_THRIFT=ON 
-DWITH_RDMA=ON -DWITH_DEBUG_BTHREAD_SCHE_SAFETY=ON -DWITH_DEBUG_LOCK=ON ..
     - name: compile
       run: |
            cd build
@@ -160,7 +160,7 @@ jobs:
     runs-on: ubuntu-20.04
     steps:
     - uses: actions/checkout@v2
-    - run: bazel build --verbose_failures --action_env=CC=clang-12 --define 
with_mesalink=false --define with_glog=true --define with_thrift=true --define 
with_debug_bthread_sche_safety=true -- //... -//example/...
+    - run: bazel build --verbose_failures --action_env=CC=clang-12 --define 
with_mesalink=false --define with_glog=true --define with_thrift=true --define 
with_debug_bthread_sche_safety=true --define with_debug_lock=true -- //... 
-//example/...
 
   clang-unittest:
     runs-on: ubuntu-20.04
diff --git a/BUILD.bazel b/BUILD.bazel
index 19d04a93..0e2e66ed 100644
--- a/BUILD.bazel
+++ b/BUILD.bazel
@@ -48,6 +48,9 @@ COPTS = [
 }) + select({
     "//bazel/config:brpc_with_debug_bthread_sche_safety": 
["-DBRPC_DEBUG_BTHREAD_SCHE_SAFETY=1"],
     "//conditions:default": ["-DBRPC_DEBUG_BTHREAD_SCHE_SAFETY=0"],
+}) + select({
+      "//bazel/config:brpc_with_debug_lock": ["-DBRPC_DEBUG_LOCK=1"],
+      "//conditions:default": ["-DBRPC_DEBUG_LOCK=0"],
 })
 
 LINKOPTS = [
diff --git a/CMakeLists.txt b/CMakeLists.txt
index c8fa7716..71c611e1 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -27,6 +27,7 @@ option(WITH_THRIFT "With thrift framed protocol supported" 
OFF)
 option(WITH_SNAPPY "With snappy" OFF)
 option(WITH_RDMA "With RDMA" OFF)
 option(WITH_DEBUG_BTHREAD_SCHE_SAFETY "With debugging bthread sche safety" OFF)
+option(WITH_DEBUG_LOCK "With debugging lock" OFF)
 option(BUILD_UNIT_TESTS "Whether to build unit tests" OFF)
 option(BUILD_FUZZ_TESTS "Whether to build fuzz tests" OFF)
 option(BUILD_BRPC_TOOLS "Whether to build brpc tools" ON)
@@ -67,6 +68,11 @@ if(WITH_DEBUG_SYMBOLS)
     set(DEBUG_SYMBOL "-g")
 endif()
 
+set(WITH_DEBUG_LOCK_VAL "0")
+if(WITH_DEBUG_LOCK)
+    set(WITH_DEBUG_LOCK_VAL "1")
+endif()
+
 if(WITH_THRIFT)
     set(THRIFT_CPP_FLAG "-DENABLE_THRIFT_FRAMED_PROTOCOL")
     find_library(THRIFT_LIB NAMES thrift)
@@ -123,7 +129,7 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
     set(CMAKE_CPP_FLAGS "${CMAKE_CPP_FLAGS} -Wno-deprecated-declarations 
-Wno-inconsistent-missing-override")
 endif()
 
-set(CMAKE_CPP_FLAGS "${CMAKE_CPP_FLAGS} ${DEFINE_CLOCK_GETTIME} 
-DBRPC_WITH_GLOG=${WITH_GLOG_VAL} -DBRPC_WITH_RDMA=${WITH_RDMA_VAL} 
-DGFLAGS_NS=${GFLAGS_NS} 
-DBRPC_DEBUG_BTHREAD_SCHE_SAFETY=${WITH_DEBUG_BTHREAD_SCHE_SAFETY_VAL}")
+set(CMAKE_CPP_FLAGS "${CMAKE_CPP_FLAGS} ${DEFINE_CLOCK_GETTIME} 
-DBRPC_WITH_GLOG=${WITH_GLOG_VAL} -DBRPC_WITH_RDMA=${WITH_RDMA_VAL} 
-DGFLAGS_NS=${GFLAGS_NS} 
-DBRPC_DEBUG_BTHREAD_SCHE_SAFETY=${WITH_DEBUG_BTHREAD_SCHE_SAFETY_VAL} 
-DBRPC_DEBUG_LOCK=${WITH_DEBUG_LOCK_VAL}")
 if(WITH_MESALINK)
     set(CMAKE_CPP_FLAGS "${CMAKE_CPP_FLAGS} -DUSE_MESALINK")
 endif()
diff --git a/bazel/config/BUILD.bazel b/bazel/config/BUILD.bazel
index d7c6d533..10965747 100644
--- a/bazel/config/BUILD.bazel
+++ b/bazel/config/BUILD.bazel
@@ -114,4 +114,10 @@ config_setting(
     name = "brpc_with_debug_bthread_sche_safety",
     define_values = {"with_debug_bthread_sche_safety": "true"},
     visibility = ["//visibility:public"],
+)
+
+config_setting(
+    name = "brpc_with_debug_lock",
+    define_values = {"with_debug_lock": "true"},
+    visibility = ["//visibility:public"],
 )
\ No newline at end of file
diff --git a/config_brpc.sh b/config_brpc.sh
index 9b6b188c..fa607292 100755
--- a/config_brpc.sh
+++ b/config_brpc.sh
@@ -38,13 +38,14 @@ else
     LDD=ldd
 fi
 
-TEMP=`getopt -o v: --long 
headers:,libs:,cc:,cxx:,with-glog,with-thrift,with-rdma,with-mesalink,with-debug-bthread-sche-safety,nodebugsymbols
 -n 'config_brpc' -- "$@"`
+TEMP=`getopt -o v: --long 
headers:,libs:,cc:,cxx:,with-glog,with-thrift,with-rdma,with-mesalink,with-debug-bthread-sche-safety,with-debug-lock,nodebugsymbols
 -n 'config_brpc' -- "$@"`
 WITH_GLOG=0
 WITH_THRIFT=0
 WITH_RDMA=0
 WITH_MESALINK=0
 BRPC_DEBUG_BTHREAD_SCHE_SAFETY=0
 DEBUGSYMBOLS=-g
+BRPC_DEBUG_LOCK=0
 
 if [ $? != 0 ] ; then >&2 $ECHO "Terminating..."; exit 1 ; fi
 
@@ -69,6 +70,7 @@ while true; do
         --with-rdma) WITH_RDMA=1; shift 1 ;;
         --with-mesalink) WITH_MESALINK=1; shift 1 ;;
         --with-debug-bthread-sche-safety ) BRPC_DEBUG_BTHREAD_SCHE_SAFETY=1; 
shift 1 ;;
+        --with-debug-lock ) BRPC_DEBUG_LOCK=1; shift 1 ;;
         --nodebugsymbols ) DEBUGSYMBOLS=; shift 1 ;;
         -- ) shift; break ;;
         * ) break ;;
@@ -409,7 +411,7 @@ append_to_output "STATIC_LINKINGS=$STATIC_LINKINGS"
 append_to_output "DYNAMIC_LINKINGS=$DYNAMIC_LINKINGS"
 
 # CPP means C PreProcessing, not C PlusPlus
-CPPFLAGS="-DBRPC_WITH_GLOG=$WITH_GLOG -DGFLAGS_NS=$GFLAGS_NS 
-DBRPC_DEBUG_BTHREAD_SCHE_SAFETY=$BRPC_DEBUG_BTHREAD_SCHE_SAFETY"
+CPPFLAGS="-DBRPC_WITH_GLOG=$WITH_GLOG -DGFLAGS_NS=$GFLAGS_NS 
-DBRPC_DEBUG_BTHREAD_SCHE_SAFETY=$BRPC_DEBUG_BTHREAD_SCHE_SAFETY 
-DBRPC_DEBUG_LOCK=$BRPC_DEBUG_LOCK"
 
 # Avoid over-optimizations of TLS variables by GCC>=4.8
 # See: https://github.com/apache/brpc/issues/1693
diff --git a/src/bthread/mutex.cpp b/src/bthread/mutex.cpp
index 30872561..fa5f55b5 100644
--- a/src/bthread/mutex.cpp
+++ b/src/bthread/mutex.cpp
@@ -278,7 +278,7 @@ static pthread_mutex_t g_cp_mutex = 
PTHREAD_MUTEX_INITIALIZER;
 // The map storing information for profiling pthread_mutex. Different from
 // bthread_mutex, we can't save stuff into pthread_mutex, we neither can
 // save the info in TLS reliably, since a mutex can be unlocked in a different
-// thread from the one locked (although rare)
+// thread from the one locked (although rare, undefined behavior)
 // This map must be very fast, since it's accessed inside the lock.
 // Layout of the map:
 //  * Align each entry by cacheline so that different threads do not collide.
@@ -383,10 +383,15 @@ void 
make_contention_site_invalid(bthread_contention_site_t* cs) {
 // First call to sys_pthread_mutex_lock sets sys_pthread_mutex_lock to the
 // real function so that next calls go to the real function directly. This
 // technique avoids calling pthread_once each time.
+typedef int (*MutexInitOp)(pthread_mutex_t*, const pthread_mutexattr_t*);
 typedef int (*MutexOp)(pthread_mutex_t*);
+int first_sys_pthread_mutex_init(pthread_mutex_t* mutex, const 
pthread_mutexattr_t* mutexattr);
+int first_sys_pthread_mutex_destroy(pthread_mutex_t* mutex);
 int first_sys_pthread_mutex_lock(pthread_mutex_t* mutex);
 int first_sys_pthread_mutex_trylock(pthread_mutex_t* mutex);
 int first_sys_pthread_mutex_unlock(pthread_mutex_t* mutex);
+static MutexInitOp sys_pthread_mutex_init = first_sys_pthread_mutex_init;
+static MutexOp sys_pthread_mutex_destroy = first_sys_pthread_mutex_destroy;
 static MutexOp sys_pthread_mutex_lock = first_sys_pthread_mutex_lock;
 static MutexOp sys_pthread_mutex_trylock = first_sys_pthread_mutex_trylock;
 static MutexOp sys_pthread_mutex_unlock = first_sys_pthread_mutex_unlock;
@@ -438,6 +443,10 @@ static void init_sys_mutex_lock() {
     // TODO: may need dlvsym when GLIBC has multiple versions of a same symbol.
     // 
http://blog.fesnel.com/blog/2009/08/25/preloading-with-multiple-symbol-versions
     if (_dl_sym) {
+        sys_pthread_mutex_init = (MutexInitOp)_dl_sym(
+            RTLD_NEXT, "pthread_mutex_init", (void*)init_sys_mutex_lock);
+        sys_pthread_mutex_destroy = (MutexOp)_dl_sym(
+            RTLD_NEXT, "pthread_mutex_destroy", (void*)init_sys_mutex_lock);
         sys_pthread_mutex_lock = (MutexOp)_dl_sym(
             RTLD_NEXT, "pthread_mutex_lock", (void*)init_sys_mutex_lock);
         sys_pthread_mutex_unlock = (MutexOp)_dl_sym(
@@ -450,6 +459,8 @@ static void init_sys_mutex_lock() {
 #endif // HAS_PTHREAD_MUTEX_TIMEDLOCK
     } else {
         // _dl_sym may be undefined reference in some system, fallback to dlsym
+        sys_pthread_mutex_init = (MutexInitOp)dlsym(RTLD_NEXT, 
"pthread_mutex_init");
+        sys_pthread_mutex_destroy = (MutexOp)dlsym(RTLD_NEXT, 
"pthread_mutex_destroy");
         sys_pthread_mutex_lock = (MutexOp)dlsym(RTLD_NEXT, 
"pthread_mutex_lock");
         sys_pthread_mutex_unlock = (MutexOp)dlsym(RTLD_NEXT, 
"pthread_mutex_unlock");
         sys_pthread_mutex_trylock = (MutexOp)dlsym(RTLD_NEXT, 
"pthread_mutex_trylock");
@@ -459,6 +470,8 @@ static void init_sys_mutex_lock() {
     }
 #elif defined(OS_MACOSX)
     // TODO: look workaround for dlsym on mac
+    sys_pthread_mutex_init = (MutexInitOp)dlsym(RTLD_NEXT, 
"pthread_mutex_init");
+    sys_pthread_mutex_destroy = (MutexOp)dlsym(RTLD_NEXT, 
"pthread_mutex_destroy");
     sys_pthread_mutex_lock = (MutexOp)dlsym(RTLD_NEXT, "pthread_mutex_lock");
     sys_pthread_mutex_trylock = (MutexOp)dlsym(RTLD_NEXT, 
"pthread_mutex_trylock");
     sys_pthread_mutex_unlock = (MutexOp)dlsym(RTLD_NEXT, 
"pthread_mutex_unlock");
@@ -468,6 +481,16 @@ static void init_sys_mutex_lock() {
 // Make sure pthread functions are ready before main().
 const int ALLOW_UNUSED dummy = pthread_once(&init_sys_mutex_lock_once, 
init_sys_mutex_lock);
 
+int first_sys_pthread_mutex_init(pthread_mutex_t* mutex, const 
pthread_mutexattr_t* mutexattr) {
+    pthread_once(&init_sys_mutex_lock_once, init_sys_mutex_lock);
+    return sys_pthread_mutex_init(mutex, mutexattr);
+}
+
+int first_sys_pthread_mutex_destroy(pthread_mutex_t* mutex) {
+    pthread_once(&init_sys_mutex_lock_once, init_sys_mutex_lock);
+    return sys_pthread_mutex_destroy(mutex);
+}
+
 int first_sys_pthread_mutex_lock(pthread_mutex_t* mutex) {
     pthread_once(&init_sys_mutex_lock_once, init_sys_mutex_lock);
     return sys_pthread_mutex_lock(mutex);
@@ -640,14 +663,158 @@ void submit_contention(const bthread_contention_site_t& 
csite, int64_t now_ns) {
     tls_warn_up = true;
 }
 
+#if BRPC_DEBUG_LOCK
+#define MUTEX_RESET_OWNER_COMMON(owner)                                        
             \
+    ((butil::atomic<bool>*)&(owner).hold)                                      
      \
+        ->store(false, butil::memory_order_relaxed)
+
+#define PTHREAD_MUTEX_SET_OWNER(owner)                                         
      \
+    owner.id = pthread_numeric_id();                                           
      \
+    ((butil::atomic<bool>*)&(owner).hold)                                      
      \
+        ->store(true, butil::memory_order_release)
+
+// Check if the mutex has been locked by the current thread.
+// Double lock on the same thread will cause deadlock.
+#define PTHREAD_MUTEX_CHECK_OWNER(owner)                                       
      \
+    bool hold = ((butil::atomic<bool>*)&(owner).hold)                          
      \
+        ->load(butil::memory_order_acquire);                                   
      \
+    if (hold && (owner).id == pthread_numeric_id()) {                          
      \
+        butil::debug::StackTrace trace(true);                                  
      \
+        LOG(ERROR) << "Detected deadlock caused by double lock of 
FastPthreadMutex:" \
+                   << std::endl << trace.ToString();                           
      \
+    }
+#else
+#define MUTEX_RESET_OWNER_COMMON(owner) ((void)0)
+#define PTHREAD_MUTEX_SET_OWNER(owner) ((void)0)
+#define PTHREAD_MUTEX_CHECK_OWNER(owner) ((void)0)
+#endif // BRPC_DEBUG_LOCK
+
 namespace internal {
+
 #ifndef NO_PTHREAD_MUTEX_HOOK
+
+#if BRPC_DEBUG_LOCK
+struct BAIDU_CACHELINE_ALIGNMENT MutexOwnerMapEntry {
+    butil::static_atomic<bool> valid;
+    pthread_mutex_t* mutex;
+    mutex_owner_t owner;
+};
+
+// The map storing owner information for pthread_mutex. Different from
+// bthread_mutex, we can't save stuff into pthread_mutex, we neither can
+// save the info in TLS reliably, since a mutex can be unlocked in a different
+// thread from the one locked (although rare).
+static MutexOwnerMapEntry g_mutex_owner_map[MUTEX_MAP_SIZE] = {}; // 
zero-initialize
+
+static void InitMutexOwnerMapEntry(pthread_mutex_t* mutex,
+                                   const pthread_mutexattr_t* mutexattr) {
+    int type = PTHREAD_MUTEX_DEFAULT;
+    if (NULL != mutexattr) {
+        pthread_mutexattr_gettype(mutexattr, &type);
+    }
+    // Only normal mutexes are tracked.
+    if (type != PTHREAD_MUTEX_NORMAL) {
+        return;
+    }
+
+    // Fast path: If the hash entry is not used, use it.
+    MutexOwnerMapEntry& hash_entry =
+        g_mutex_owner_map[hash_mutex_ptr(mutex) & (MUTEX_MAP_SIZE - 1)];
+    if (!hash_entry.valid.exchange(true, butil::memory_order_relaxed)) {
+        MUTEX_RESET_OWNER_COMMON(hash_entry.owner);
+        return;
+    }
+
+    // Slow path: Find an unused entry.
+    for (auto& entry : g_mutex_owner_map) {
+        if (!entry.valid.exchange(true, butil::memory_order_relaxed)) {
+            MUTEX_RESET_OWNER_COMMON(entry.owner);
+            return;
+        }
+    }
+}
+
+static BUTIL_FORCE_INLINE
+MutexOwnerMapEntry* FindMutexOwnerMapEntry(pthread_mutex_t* mutex) {
+    if (NULL == mutex) {
+        return NULL;
+    }
+
+    // Fast path.
+    MutexOwnerMapEntry* hash_entry =
+        &g_mutex_owner_map[hash_mutex_ptr(mutex) & (MUTEX_MAP_SIZE - 1)];
+    if (hash_entry->valid.load(butil::memory_order_relaxed) && 
hash_entry->mutex == mutex) {
+        return hash_entry;
+    }
+    // Slow path.
+    for (auto& entry : g_mutex_owner_map) {
+        if (entry.valid.load(butil::memory_order_relaxed) && entry.mutex == 
mutex) {
+            return &entry;
+        }
+    }
+    return NULL;
+}
+
+static void DestroyMutexOwnerMapEntry(pthread_mutex_t* mutex) {
+    MutexOwnerMapEntry* entry = FindMutexOwnerMapEntry(mutex);
+    if (NULL != entry) {
+        entry->valid.store(false, butil::memory_order_relaxed);
+    }
+}
+
+#define INIT_MUTEX_OWNER_MAP_ENTRY(mutex, mutexattr) \
+    ::bthread::internal::InitMutexOwnerMapEntry(mutex, mutexattr)
+
+#define DESTROY_MUTEX_OWNER_MAP_ENTRY(mutex) \
+    ::bthread::internal::DestroyMutexOwnerMapEntry(mutex)
+
+#define FIND_SYS_PTHREAD_MUTEX_OWNER_MAP_ENTRY(mutex) \
+    MutexOwnerMapEntry* entry = 
::bthread::internal::FindMutexOwnerMapEntry(mutex)
+
+#define SYS_PTHREAD_MUTEX_CHECK_OWNER              \
+    if (NULL != entry) {                           \
+        PTHREAD_MUTEX_CHECK_OWNER(entry->owner);   \
+    }
+
+#define SYS_PTHREAD_MUTEX_SET_OWNER                \
+    if (NULL != entry) {                           \
+        PTHREAD_MUTEX_SET_OWNER(entry->owner);     \
+    }
+
+#define SYS_PTHREAD_MUTEX_RESET_OWNER(mutex)       \
+    FIND_SYS_PTHREAD_MUTEX_OWNER_MAP_ENTRY(mutex); \
+    if (NULL != entry) {                           \
+        MUTEX_RESET_OWNER_COMMON(entry->owner);           \
+    }
+
+#else
+#define INIT_MUTEX_OWNER_MAP_ENTRY(mutex, mutexattr) ((void)0)
+#define DESTROY_MUTEX_OWNER_MAP_ENTRY(mutex) ((void)0)
+#define FIND_SYS_PTHREAD_MUTEX_OWNER_MAP_ENTRY(mutex) ((void)0)
+#define SYS_PTHREAD_MUTEX_CHECK_OWNER ((void)0)
+#define SYS_PTHREAD_MUTEX_SET_OWNER ((void)0)
+#define SYS_PTHREAD_MUTEX_RESET_OWNER(mutex) ((void)0)
+#endif // BRPC_DEBUG_LOCK
+
+
 #if HAS_PTHREAD_MUTEX_TIMEDLOCK
 BUTIL_FORCE_INLINE int pthread_mutex_lock_internal(pthread_mutex_t* mutex,
                                                    const struct timespec* 
abstime) {
-    int rc = NULL == abstime ?
-             sys_pthread_mutex_lock(mutex) :
-             sys_pthread_mutex_timedlock(mutex, abstime);
+    int rc = 0;
+    if (NULL == abstime) {
+        FIND_SYS_PTHREAD_MUTEX_OWNER_MAP_ENTRY(mutex);
+        SYS_PTHREAD_MUTEX_CHECK_OWNER;
+        rc = sys_pthread_mutex_lock(mutex);
+        if (0 == rc) {
+            SYS_PTHREAD_MUTEX_SET_OWNER;
+        }
+    } else {
+        rc = sys_pthread_mutex_timedlock(mutex, abstime);
+        if (0 == rc) {
+            FIND_SYS_PTHREAD_MUTEX_OWNER_MAP_ENTRY(mutex);
+            SYS_PTHREAD_MUTEX_SET_OWNER;
+        }
+    }
     if (0 == rc) {
         ADD_TLS_PTHREAD_LOCK_COUNT;
     }
@@ -656,8 +823,11 @@ BUTIL_FORCE_INLINE int 
pthread_mutex_lock_internal(pthread_mutex_t* mutex,
 #else
 BUTIL_FORCE_INLINE int pthread_mutex_lock_internal(pthread_mutex_t* mutex,
                                                    const struct timespec*/* 
Not supported */) {
+    FIND_SYS_PTHREAD_MUTEX_OWNER_MAP_ENTRY(mutex);
+    SYS_PTHREAD_MUTEX_CHECK_OWNER;
     int rc = sys_pthread_mutex_lock(mutex);
     if (0 == rc) {
+        SYS_PTHREAD_MUTEX_SET_OWNER;
         ADD_TLS_PTHREAD_LOCK_COUNT;
     }
     return rc;
@@ -667,12 +837,15 @@ BUTIL_FORCE_INLINE int 
pthread_mutex_lock_internal(pthread_mutex_t* mutex,
 BUTIL_FORCE_INLINE int pthread_mutex_trylock_internal(pthread_mutex_t* mutex) {
     int rc = sys_pthread_mutex_trylock(mutex);
     if (0 == rc) {
+        FIND_SYS_PTHREAD_MUTEX_OWNER_MAP_ENTRY(mutex);
+        SYS_PTHREAD_MUTEX_SET_OWNER;
         ADD_TLS_PTHREAD_LOCK_COUNT;
     }
     return rc;
 }
 
 BUTIL_FORCE_INLINE int pthread_mutex_unlock_internal(pthread_mutex_t* mutex) {
+    SYS_PTHREAD_MUTEX_RESET_OWNER(mutex);
     SUB_TLS_PTHREAD_LOCK_COUNT;
     return sys_pthread_mutex_unlock(mutex);
 }
@@ -838,9 +1011,51 @@ const MutexInternal MUTEX_LOCKED_RAW = {{1},{0},0};
 BAIDU_CASSERT(sizeof(unsigned) == sizeof(MutexInternal),
               sizeof_mutex_internal_must_equal_unsigned);
 
+#if BRPC_DEBUG_LOCK
+
+#define BTHREAD_MUTEX_SET_OWNER                                                
             \
+    do {                                                                       
             \
+        TaskGroup* task_group = 
BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group);            \
+        if (NULL != task_group && !task_group->is_current_main_task()) {       
             \
+            m->owner.id = bthread_self();                                      
             \
+        } else {                                                               
             \
+            m->owner.id = pthread_numeric_id();                                
             \
+        }                                                                      
             \
+        ((butil::atomic<bool>*)&m->owner.hold)                                 
             \
+            ->store(true, butil::memory_order_release);                        
             \
+    } while(false)
+
+// Check if the mutex has been locked by the current thread.
+// Double lock on the same thread will cause deadlock.
+#define BTHREAD_MUTEX_CHECK_OWNER                                              
              \
+        bool hold = ((butil::atomic<bool>*)&m->owner.hold)                     
              \
+            ->load(butil::memory_order_acquire);                               
              \
+        bool double_lock =                                                     
              \
+            hold && (m->owner.id == bthread_self() || m->owner.id == 
pthread_numeric_id());  \
+        if (double_lock) {                                                     
              \
+            butil::debug::StackTrace trace(true);                              
              \
+            LOG(ERROR) << "Detected deadlock caused by double lock of 
bthread_mutex_t:"      \
+                       << std::endl << trace.ToString();                       
              \
+       }
+#else
+#define BTHREAD_MUTEX_SET_OWNER ((void)0)
+#define BTHREAD_MUTEX_CHECK_OWNER ((void)0)
+#endif // BRPC_DEBUG_LOCK
+
+inline int mutex_trylock_impl(bthread_mutex_t* m) {
+    MutexInternal* split = (MutexInternal*)m->butex;
+    if (!split->locked.exchange(1, butil::memory_order_acquire)) {
+        BTHREAD_MUTEX_SET_OWNER;
+        return 0;
+    }
+    return EBUSY;
+}
+
 const int MAX_SPIN_ITER = 4;
 
-inline int mutex_lock_contended_impl(bthread_mutex_t* m, const struct 
timespec* abstime) {
+inline int mutex_lock_contended_impl(bthread_mutex_t* __restrict m,
+                                     const struct timespec* __restrict 
abstime) {
+    BTHREAD_MUTEX_CHECK_OWNER;
     // When a bthread first contends for a lock, active spinning makes sense.
     // Spin only few times and only if local `rq' is empty.
     TaskGroup* g = BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group);
@@ -873,12 +1088,17 @@ inline int mutex_lock_contended_impl(bthread_mutex_t* m, 
const struct timespec*
             queue_lifo = true;
         }
     }
+    BTHREAD_MUTEX_SET_OWNER;
     return 0;
 }
 
 #ifdef BTHREAD_USE_FAST_PTHREAD_MUTEX
 namespace internal {
 
+FastPthreadMutex::FastPthreadMutex() : _futex(0) {
+    MUTEX_RESET_OWNER_COMMON(_owner);
+}
+
 int FastPthreadMutex::lock_contended(const struct timespec* abstime) {
     int64_t abstime_us = 0;
     if (NULL != abstime) {
@@ -905,6 +1125,8 @@ int FastPthreadMutex::lock_contended(const struct 
timespec* abstime) {
             return errno;
         }
     }
+    PTHREAD_MUTEX_SET_OWNER(_owner);
+    ADD_TLS_PTHREAD_LOCK_COUNT;
     return 0;
 }
 
@@ -913,14 +1135,15 @@ void FastPthreadMutex::lock() {
         return;
     }
 
+    PTHREAD_MUTEX_CHECK_OWNER(_owner);
     (void)lock_contended(NULL);
-    ADD_TLS_PTHREAD_LOCK_COUNT;
 }
 
 bool FastPthreadMutex::try_lock() {
     auto split = (bthread::MutexInternal*)&_futex;
     bool lock = !split->locked.exchange(1, butil::memory_order_acquire);
     if (lock) {
+        PTHREAD_MUTEX_SET_OWNER(_owner);
         ADD_TLS_PTHREAD_LOCK_COUNT;
     }
     return lock;
@@ -930,15 +1153,12 @@ bool FastPthreadMutex::timed_lock(const struct timespec* 
abstime) {
     if (try_lock()) {
         return true;
     }
-    int rc = lock_contended(abstime);
-    if (rc == 0) {
-        ADD_TLS_PTHREAD_LOCK_COUNT;
-    }
-    return rc == 0;
+    return 0 == lock_contended(abstime);
 }
 
 void FastPthreadMutex::unlock() {
     SUB_TLS_PTHREAD_LOCK_COUNT;
+    MUTEX_RESET_OWNER_COMMON(_owner);
     auto whole = (butil::atomic<unsigned>*)&_futex;
     const unsigned prev = whole->exchange(0, butil::memory_order_release);
     // CAUTION: the mutex may be destroyed, check comments before butex_create
@@ -971,6 +1191,7 @@ __BEGIN_DECLS
 int bthread_mutex_init(bthread_mutex_t* __restrict m,
                        const bthread_mutexattr_t* __restrict attr) {
     bthread::make_contention_site_invalid(&m->csite);
+    MUTEX_RESET_OWNER_COMMON(m->owner);
     m->butex = bthread::butex_create_checked<unsigned>();
     if (!m->butex) {
         return ENOMEM;
@@ -986,11 +1207,7 @@ int bthread_mutex_destroy(bthread_mutex_t* m) {
 }
 
 int bthread_mutex_trylock(bthread_mutex_t* m) {
-    bthread::MutexInternal* split = (bthread::MutexInternal*)m->butex;
-    if (!split->locked.exchange(1, butil::memory_order_acquire)) {
-        return 0;
-    }
-    return EBUSY;
+    return bthread::mutex_trylock_impl(m);
 }
 
 int bthread_mutex_lock_contended(bthread_mutex_t* m) {
@@ -999,8 +1216,7 @@ int bthread_mutex_lock_contended(bthread_mutex_t* m) {
 
 static int bthread_mutex_lock_impl(bthread_mutex_t* __restrict m,
                                    const struct timespec* __restrict abstime) {
-    auto split = (bthread::MutexInternal*)m->butex;
-    if (!split->locked.exchange(1, butil::memory_order_acquire)) {
+    if (0 == bthread::mutex_trylock_impl(m)) {
         return 0;
     }
     // Don't sample when contention profiler is off.
@@ -1047,6 +1263,7 @@ int bthread_mutex_unlock(bthread_mutex_t* m) {
         saved_csite = m->csite;
         bthread::make_contention_site_invalid(&m->csite);
     }
+    MUTEX_RESET_OWNER_COMMON(m->owner);
     const unsigned prev = whole->exchange(0, butil::memory_order_release);
     // CAUTION: the mutex may be destroyed, check comments before butex_create
     if (prev == BTHREAD_MUTEX_LOCKED) {
@@ -1081,20 +1298,35 @@ int bthread_mutexattr_destroy(bthread_mutexattr_t* 
attr) {
 }
 
 #ifndef NO_PTHREAD_MUTEX_HOOK
-int pthread_mutex_lock(pthread_mutex_t* __mutex) {
-    return bthread::pthread_mutex_lock_impl(__mutex);
+
+int pthread_mutex_init(pthread_mutex_t * __restrict mutex,
+                       const pthread_mutexattr_t* __restrict mutexattr) {
+    INIT_MUTEX_OWNER_MAP_ENTRY(mutex, mutexattr);
+    return bthread::sys_pthread_mutex_init(mutex, mutexattr);
+}
+
+int pthread_mutex_destroy(pthread_mutex_t* mutex) {
+    DESTROY_MUTEX_OWNER_MAP_ENTRY(mutex);
+    return bthread::sys_pthread_mutex_destroy(mutex);
 }
-int pthread_mutex_trylock(pthread_mutex_t* __mutex) {
-    return bthread::pthread_mutex_trylock_impl(__mutex);
+
+int pthread_mutex_lock(pthread_mutex_t* mutex) {
+    return bthread::pthread_mutex_lock_impl(mutex);
 }
+
 #if defined(OS_LINUX) && defined(OS_POSIX) && defined(__USE_XOPEN2K)
 int pthread_mutex_timedlock(pthread_mutex_t *__restrict __mutex,
                                            const struct timespec *__restrict 
__abstime) {
     return bthread::pthread_mutex_timedlock_impl(__mutex, __abstime);
 }
 #endif // OS_POSIX __USE_XOPEN2K
-int pthread_mutex_unlock(pthread_mutex_t* __mutex) {
-    return bthread::pthread_mutex_unlock_impl(__mutex);
+
+int pthread_mutex_trylock(pthread_mutex_t* mutex) {
+    return bthread::pthread_mutex_trylock_impl(mutex);
+}
+
+int pthread_mutex_unlock(pthread_mutex_t* mutex) {
+    return bthread::pthread_mutex_unlock_impl(mutex);
 }
 #endif // NO_PTHREAD_MUTEX_HOOK
 
diff --git a/src/bthread/mutex.h b/src/bthread/mutex.h
index d05d753c..11c7eae8 100644
--- a/src/bthread/mutex.h
+++ b/src/bthread/mutex.h
@@ -35,6 +35,7 @@ extern int bthread_mutex_lock(bthread_mutex_t* mutex);
 extern int bthread_mutex_timedlock(bthread_mutex_t* __restrict mutex,
                                    const struct timespec* __restrict abstime);
 extern int bthread_mutex_unlock(bthread_mutex_t* mutex);
+extern bthread_t bthread_self(void);
 __END_DECLS
 
 namespace bthread {
@@ -76,8 +77,7 @@ namespace internal {
 #ifdef BTHREAD_USE_FAST_PTHREAD_MUTEX
 class FastPthreadMutex {
 public:
-    FastPthreadMutex() : _futex(0) {}
-    ~FastPthreadMutex() = default;
+    FastPthreadMutex();
     void lock();
     void unlock();
     bool try_lock();
@@ -85,7 +85,12 @@ public:
 private:
     DISALLOW_COPY_AND_ASSIGN(FastPthreadMutex);
     int lock_contended(const struct timespec* abstime);
+
     unsigned _futex;
+    // Note: Owner detection of the mutex comes with average execution
+    // slowdown of about 50%., so it is only used for debugging and is
+    // only available when the macro `BRPC_DEBUG_LOCK' = 1.
+    mutex_owner_t _owner;
 };
 #else
 typedef butil::Mutex FastPthreadMutex;
diff --git a/src/bthread/types.h b/src/bthread/types.h
index 0124fcbd..60f5c7fe 100644
--- a/src/bthread/types.h
+++ b/src/bthread/types.h
@@ -166,14 +166,27 @@ typedef struct {
     size_t sampling_range;
 } bthread_contention_site_t;
 
+struct mutex_owner_t {
+    bool hold;
+    uint64_t id;
+};
+
 typedef struct bthread_mutex_t {
 #if defined(__cplusplus)
-    bthread_mutex_t() : butex(NULL), csite{} {}
+    bthread_mutex_t()
+        : butex(NULL), csite{}
+        , enable_csite(false)
+        , owner{false, 0} {}
+
     DISALLOW_COPY_AND_ASSIGN(bthread_mutex_t);
 #endif
     unsigned* butex;
     bthread_contention_site_t csite;
     bool enable_csite;
+    // Note: Owner detection of the mutex comes with average execution
+    // slowdown of about 50%, so it is only used for debugging and is
+    // only available when the macro `BRPC_DEBUG_LOCK' = 1.
+    mutex_owner_t owner;
 } bthread_mutex_t;
 
 typedef struct {
diff --git a/test/bthread_mutex_unittest.cpp b/test/bthread_mutex_unittest.cpp
index b0802f9b..3163876c 100644
--- a/test/bthread_mutex_unittest.cpp
+++ b/test/bthread_mutex_unittest.cpp
@@ -230,6 +230,11 @@ TEST(MutexTest, performance) {
     butil::Mutex base_mutex;
     PerfTest(&base_mutex, (pthread_t*)NULL, thread_num, pthread_create, 
pthread_join);
     PerfTest(&base_mutex, (bthread_t*)NULL, thread_num, 
bthread_start_background, bthread_join);
+
+    bthread::FastPthreadMutex fast_mutex;
+    PerfTest(&fast_mutex, (pthread_t*)NULL, thread_num, pthread_create, 
pthread_join);
+    PerfTest(&fast_mutex, (bthread_t*)NULL, thread_num, 
bthread_start_background, bthread_join);
+
     bthread::Mutex bth_mutex;
     PerfTest(&bth_mutex, (pthread_t*)NULL, thread_num, pthread_create, 
pthread_join);
     PerfTest(&bth_mutex, (bthread_t*)NULL, thread_num, 
bthread_start_background, bthread_join);


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@brpc.apache.org
For additional commands, e-mail: dev-h...@brpc.apache.org

Reply via email to