diff --git a/src/bthread/mutex.cpp b/src/bthread/mutex.cpp index 403f6bb8a8..245b4fba7a 100644 --- a/src/bthread/mutex.cpp +++ b/src/bthread/mutex.cpp @@ -379,6 +379,31 @@ make_contention_site_invalid(bthread_contention_site_t* cs) { cs->sampling_range = 0; } +BUTIL_FORCE_INLINE void +SetBthreadMutexOwner(mutex_owner_t* owner) { + TaskGroup* g = BAIDU_GET_VOLATILE_THREAD_LOCAL(tls_task_group); + if (g != NULL && !g->is_current_main_task()) { + owner->type = OWNER_TYPE_BTHREAD; + owner->id = bthread_self(); + } else { + owner->type = OWNER_TYPE_PTHREAD; + owner->id = pthread_numeric_id(); + } +} + +BUTIL_FORCE_INLINE void +CheckBthreadMutexOwner(mutex_owner_t* owner) { + bool double_lock = + (owner->type == OWNER_TYPE_BTHREAD && owner->id == bthread_self()) || + (owner->type == OWNER_TYPE_PTHREAD && owner->id == pthread_numeric_id()); + // Double lock on the same bthread will cause deadlock. + if (double_lock) { + butil::debug::StackTrace trace(true); + LOG(ERROR) << "Detected deadlock caused by double lock:" + << std::endl << trace.ToString(); + } +} + #ifndef NO_PTHREAD_MUTEX_HOOK // Replace pthread_mutex_lock and pthread_mutex_unlock: // First call to sys_pthread_mutex_lock sets sys_pthread_mutex_lock to the @@ -777,10 +802,21 @@ const MutexInternal MUTEX_LOCKED_RAW = {{1},{0},0}; BAIDU_CASSERT(sizeof(unsigned) == sizeof(MutexInternal), sizeof_mutex_internal_must_equal_unsigned); +inline int mutex_trylock_impl(bthread_mutex_t* m) { + MutexInternal* split = (MutexInternal*)m->butex; + if (!split->locked.exchange(1, butil::memory_order_acquire)) { + // Store the owner of the mutex. + SetBthreadMutexOwner(&m->owner); + return 0; + } + return EBUSY; +} + const int MAX_SPIN_ITER = 4; inline int mutex_lock_contended_impl( bthread_mutex_t* m, const struct timespec* __restrict abstime) { + CheckBthreadMutexOwner(&m->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); @@ -813,6 +849,8 @@ inline int mutex_lock_contended_impl( queue_lifo = true; } } + // Store the owner of the mutex. + SetBthreadMutexOwner(&m->owner); return 0; } @@ -835,6 +873,8 @@ void FastPthreadMutex::lock() { if (split->locked.exchange(1, butil::memory_order_acquire)) { (void)lock_contended(); } + _owner.type = OWNER_TYPE_PTHREAD; + _owner.id = pthread_numeric_id(); ++tls_pthread_lock_count; } @@ -842,12 +882,15 @@ bool FastPthreadMutex::try_lock() { auto split = (bthread::MutexInternal*)&_futex; bool lock = !split->locked.exchange(1, butil::memory_order_acquire); if (lock) { + _owner.type = OWNER_TYPE_PTHREAD; + _owner.id = pthread_numeric_id(); ++tls_pthread_lock_count; } return lock; } void FastPthreadMutex::unlock() { + _owner.type = OWNER_TYPE_NONE; auto whole = (butil::atomic*)&_futex; const unsigned prev = whole->exchange(0, butil::memory_order_release); // CAUTION: the mutex may be destroyed, check comments before butex_create @@ -857,6 +900,15 @@ void FastPthreadMutex::unlock() { --tls_pthread_lock_count; } +void FastPthreadMutex::check_owner() { + if (_owner.type != OWNER_TYPE_PTHREAD || _owner.id != pthread_numeric_id()) { + return; + } + butil::debug::StackTrace trace(true); + LOG(ERROR) << "Detected deadlock caused by double lock:" + << std::endl << trace.ToString(); +} + } // namespace internal #endif // BTHREAD_USE_FAST_PTHREAD_MUTEX @@ -875,6 +927,7 @@ extern "C" { int bthread_mutex_init(bthread_mutex_t* __restrict m, const bthread_mutexattr_t* __restrict) { bthread::make_contention_site_invalid(&m->csite); + m->owner.type = OWNER_TYPE_NONE; m->butex = bthread::butex_create_checked(); if (!m->butex) { return ENOMEM; @@ -889,11 +942,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) { @@ -901,8 +950,7 @@ int bthread_mutex_lock_contended(bthread_mutex_t* m) { } int bthread_mutex_lock(bthread_mutex_t* m) { - bthread::MutexInternal* 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. @@ -965,6 +1013,7 @@ int bthread_mutex_unlock(bthread_mutex_t* m) { saved_csite = m->csite; bthread::make_contention_site_invalid(&m->csite); } + m->owner.type = OWNER_TYPE_NONE; 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) { diff --git a/src/bthread/mutex.h b/src/bthread/mutex.h index ad6d2e5cbd..15f579958b 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 { @@ -71,15 +72,16 @@ namespace internal { #ifdef BTHREAD_USE_FAST_PTHREAD_MUTEX class FastPthreadMutex { public: - FastPthreadMutex() : _futex(0) {} - ~FastPthreadMutex() = default; + FastPthreadMutex() : _futex(0), _owner{OWNER_TYPE_NONE, 0} {} void lock(); void unlock(); bool try_lock(); private: DISALLOW_COPY_AND_ASSIGN(FastPthreadMutex); int lock_contended(); + void check_owner(); unsigned _futex; + mutex_owner_t _owner; }; #else typedef butil::Mutex FastPthreadMutex; diff --git a/src/bthread/types.h b/src/bthread/types.h index 4b4f0565f5..bbec9af94b 100644 --- a/src/bthread/types.h +++ b/src/bthread/types.h @@ -164,9 +164,21 @@ typedef struct { size_t sampling_range; } bthread_contention_site_t; +enum mutex_owner_type { + OWNER_TYPE_NONE = 0, + OWNER_TYPE_BTHREAD, + OWNER_TYPE_PTHREAD +}; + +struct mutex_owner_t { + mutex_owner_type type; + uint64_t id; +}; + typedef struct { unsigned* butex; bthread_contention_site_t csite; + mutex_owner_t owner; } bthread_mutex_t; typedef struct {