DCHECK that a Mutex is not locked when destroyed Destroying a locked Mutex is undefined behavior and is already flagged by ThreadSanitizer via `ABSL_TSAN_MUTEX_DESTROY`. Previously, non-TSan debug builds tolerated it and called `ForgetDeadlockInfo()`, which left stale entries in the holding thread's `SynchLocksHeld` list (filling all 40 slots in `Mutex.LockedMutexDestructionBug` and disabling deadlock detection on that thread for the rest of the process). In debug builds (`!defined(NDEBUG)`), `Mutex::Dtor()` now `ABSL_RAW_DCHECK`s that the `Mutex` is not held in exclusive or shared mode when destroyed. `Mutex.LockedMutexDestructionBug` and `Mutex.DeadlockIdBug` are replaced with `Mutex.LockedMutexDestructionDeathTest`. PiperOrigin-RevId: 1001021469
diff --git a/absl/synchronization/mutex.cc b/absl/synchronization/mutex.cc index d6e2aac..0286198 100644 --- a/absl/synchronization/mutex.cc +++ b/absl/synchronization/mutex.cc
@@ -756,6 +756,9 @@ #if !defined(NDEBUG) || defined(ABSL_HAVE_THREAD_SANITIZER) || \ defined(ABSL_BUILD_DLL) void Mutex::Dtor() { + ABSL_RAW_DCHECK( + (mu_.load(std::memory_order_relaxed) & (kMuWriter | kMuReader)) == 0, + "Mutex destroyed while locked"); if (kDebugMode) { this->ForgetDeadlockInfo(); }
diff --git a/absl/synchronization/mutex_test.cc b/absl/synchronization/mutex_test.cc index a96bcff..e71fa90 100644 --- a/absl/synchronization/mutex_test.cc +++ b/absl/synchronization/mutex_test.cc
@@ -856,27 +856,22 @@ } #endif // !ABSL_MUTEX_READER_LOCK_IS_EXCLUSIVE -// Test that we correctly handle the situation when a lock is -// held and then destroyed (w/o unlocking). -#ifdef ABSL_HAVE_THREAD_SANITIZER -// TSAN reports errors when locked Mutexes are destroyed. -TEST(Mutex, DISABLED_LockedMutexDestructionBug) ABSL_NO_THREAD_SAFETY_ANALYSIS { -#else -TEST(Mutex, LockedMutexDestructionBug) ABSL_NO_THREAD_SAFETY_ANALYSIS { -#endif - for (int i = 0; i != 10; i++) { - // Create, lock and destroy 10 locks. - const int kNumLocks = 10; - auto mu = std::make_unique<absl::Mutex[]>(kNumLocks); - for (int j = 0; j != kNumLocks; j++) { - if ((j % 2) == 0) { - mu[j].lock(); - } else { - mu[j].lock_shared(); - } - } - } +#if !defined(NDEBUG) && !defined(ABSL_HAVE_THREAD_SANITIZER) +TEST(Mutex, LockedMutexDestructionDeathTest) ABSL_NO_THREAD_SAFETY_ANALYSIS { + EXPECT_DEATH_IF_SUPPORTED( + { + absl::Mutex mu; + mu.lock(); + }, + "Mutex destroyed while locked"); + EXPECT_DEATH_IF_SUPPORTED( + { + absl::Mutex mu; + mu.lock_shared(); + }, + "Mutex destroyed while locked"); } +#endif // Some functions taking pointers to non-const. bool Equals42(int *p) { return *p == 42; } @@ -1318,44 +1313,6 @@ } } -#ifdef ABSL_HAVE_THREAD_SANITIZER -// TSAN reports errors when locked Mutexes are destroyed. -TEST(Mutex, DISABLED_DeadlockIdBug) ABSL_NO_THREAD_SAFETY_ANALYSIS { -#else -TEST(Mutex, DeadlockIdBug) ABSL_NO_THREAD_SAFETY_ANALYSIS { -#endif - // Test a scenario where a cached deadlock graph node id in the - // list of held locks is not invalidated when the corresponding - // mutex is deleted. - absl::SetMutexDeadlockDetectionMode(absl::OnDeadlockCycle::kAbort); - // Mutex that will be destroyed while being held - absl::Mutex *a = new absl::Mutex; - // Other mutexes needed by test - absl::Mutex b, c; - - // Hold mutex. - a->lock(); - - // Force deadlock id assignment by acquiring another lock. - b.lock(); - b.unlock(); - - // Delete the mutex. The Mutex destructor tries to remove held locks, - // but the attempt isn't foolproof. It can fail if: - // (a) Deadlock detection is currently disabled. - // (b) The destruction is from another thread. - // We exploit (a) by temporarily disabling deadlock detection. - absl::SetMutexDeadlockDetectionMode(absl::OnDeadlockCycle::kIgnore); - delete a; - absl::SetMutexDeadlockDetectionMode(absl::OnDeadlockCycle::kAbort); - - // Now acquire another lock which will force a deadlock id assignment. - // We should end up getting assigned the same deadlock id that was - // freed up when "a" was deleted, which will cause a spurious deadlock - // report if the held lock entry for "a" was not invalidated. - c.lock(); - c.unlock(); -} // -------------------------------------------------------- // Test for timeouts/deadlines on condition waits that are specified using