Rollback "Mutex: Remove destructor in release build"

PiperOrigin-RevId: 577180526
Change-Id: Iec53709456805ca8dc5327669cc0f6c95825d0e9
diff --git a/absl/synchronization/mutex.cc b/absl/synchronization/mutex.cc
index c733ff4..4703267 100644
--- a/absl/synchronization/mutex.cc
+++ b/absl/synchronization/mutex.cc
@@ -202,23 +202,31 @@
 // Ensure that "(*pv & bits) == bits" by doing an atomic update of "*pv" to
 // "*pv | bits" if necessary.  Wait until (*pv & wait_until_clear)==0
 // before making any change.
-// Returns true if bits were previously unset and set by the call.
 // This is used to set flags in mutex and condition variable words.
-static bool AtomicSetBits(std::atomic<intptr_t>* pv, intptr_t bits,
+static void AtomicSetBits(std::atomic<intptr_t>* pv, intptr_t bits,
                           intptr_t wait_until_clear) {
-  for (;;) {
-    intptr_t v = pv->load(std::memory_order_relaxed);
-    if ((v & bits) == bits) {
-      return false;
-    }
-    if ((v & wait_until_clear) != 0) {
-      continue;
-    }
-    if (pv->compare_exchange_weak(v, v | bits, std::memory_order_release,
-                                  std::memory_order_relaxed)) {
-      return true;
-    }
-  }
+  intptr_t v;
+  do {
+    v = pv->load(std::memory_order_relaxed);
+  } while ((v & bits) != bits &&
+           ((v & wait_until_clear) != 0 ||
+            !pv->compare_exchange_weak(v, v | bits, std::memory_order_release,
+                                       std::memory_order_relaxed)));
+}
+
+// Ensure that "(*pv & bits) == 0" by doing an atomic update of "*pv" to
+// "*pv & ~bits" if necessary.  Wait until (*pv & wait_until_clear)==0
+// before making any change.
+// This is used to unset flags in mutex and condition variable words.
+static void AtomicClearBits(std::atomic<intptr_t>* pv, intptr_t bits,
+                            intptr_t wait_until_clear) {
+  intptr_t v;
+  do {
+    v = pv->load(std::memory_order_relaxed);
+  } while ((v & bits) != 0 &&
+           ((v & wait_until_clear) != 0 ||
+            !pv->compare_exchange_weak(v, v & ~bits, std::memory_order_release,
+                                       std::memory_order_relaxed)));
 }
 
 //------------------------------------------------------------------
@@ -329,49 +337,12 @@
                                     const char* name, intptr_t bits,
                                     intptr_t lockbit) {
   uint32_t h = reinterpret_cast<uintptr_t>(addr) % kNSynchEvent;
+  SynchEvent* e;
+  // first look for existing SynchEvent struct..
   synch_event_mu.Lock();
-  // When a Mutex/CondVar is destroyed, we don't remove the associated
-  // SynchEvent to keep destructors empty in release builds for performance
-  // reasons. If the current call is the first to set bits (kMuEvent/kCVEvent),
-  // we don't look up the existing even because (if it exists, it must be for
-  // the previous Mutex/CondVar that existed at the same address).
-  // The leaking events must not be a problem for tests, which should create
-  // bounded amount of events. And debug logging is not supposed to be enabled
-  // in production. However, if it's accidentally enabled, or briefly enabled
-  // for some debugging, we don't want to crash the program. Instead we drop
-  // all events, if we accumulated too many of them. Size of a single event
-  // is ~48 bytes, so 100K events is ~5 MB.
-  // Additionally we could delete the old event for the same address,
-  // but it would require a better hashmap (if we accumulate too many events,
-  // linked lists will grow and traversing them will be very slow).
-  constexpr size_t kMaxSynchEventCount = 100 << 10;
-  // Total number of live synch events.
-  static size_t synch_event_count ABSL_GUARDED_BY(synch_event_mu);
-  if (++synch_event_count > kMaxSynchEventCount) {
-    synch_event_count = 0;
-    ABSL_RAW_LOG(ERROR,
-                 "Accumulated %zu Mutex debug objects. If you see this"
-                 " in production, it may mean that the production code"
-                 " accidentally calls "
-                 "Mutex/CondVar::EnableDebugLog/EnableInvariantDebugging.",
-                 kMaxSynchEventCount);
-    for (auto*& head : synch_event) {
-      for (auto* e = head; e != nullptr;) {
-        SynchEvent* next = e->next;
-        if (--(e->refcount) == 0) {
-          base_internal::LowLevelAlloc::Free(e);
-        }
-        e = next;
-      }
-      head = nullptr;
-    }
-  }
-  SynchEvent* e = nullptr;
-  if (!AtomicSetBits(addr, bits, lockbit)) {
-    for (e = synch_event[h];
-         e != nullptr && e->masked_addr != base_internal::HidePtr(addr);
-         e = e->next) {
-    }
+  for (e = synch_event[h];
+       e != nullptr && e->masked_addr != base_internal::HidePtr(addr);
+       e = e->next) {
   }
   if (e == nullptr) {  // no SynchEvent struct found; make one.
     if (name == nullptr) {
@@ -387,6 +358,7 @@
     e->log = false;
     strcpy(e->name, name);  // NOLINT(runtime/printf)
     e->next = synch_event[h];
+    AtomicSetBits(addr, bits, lockbit);
     synch_event[h] = e;
   } else {
     e->refcount++;  // for return value
@@ -395,6 +367,11 @@
   return e;
 }
 
+// Deallocate the SynchEvent *e, whose refcount has fallen to zero.
+static void DeleteSynchEvent(SynchEvent* e) {
+  base_internal::LowLevelAlloc::Free(e);
+}
+
 // Decrement the reference count of *e, or do nothing if e==null.
 static void UnrefSynchEvent(SynchEvent* e) {
   if (e != nullptr) {
@@ -402,11 +379,36 @@
     bool del = (--(e->refcount) == 0);
     synch_event_mu.Unlock();
     if (del) {
-      base_internal::LowLevelAlloc::Free(e);
+      DeleteSynchEvent(e);
     }
   }
 }
 
+// Forget the mapping from the object (Mutex or CondVar) at address addr
+// to SynchEvent object, and clear "bits" in its word (waiting until lockbit
+// is clear before doing so).
+static void ForgetSynchEvent(std::atomic<intptr_t>* addr, intptr_t bits,
+                             intptr_t lockbit) {
+  uint32_t h = reinterpret_cast<uintptr_t>(addr) % kNSynchEvent;
+  SynchEvent** pe;
+  SynchEvent* e;
+  synch_event_mu.Lock();
+  for (pe = &synch_event[h];
+       (e = *pe) != nullptr && e->masked_addr != base_internal::HidePtr(addr);
+       pe = &e->next) {
+  }
+  bool del = false;
+  if (e != nullptr) {
+    *pe = e->next;
+    del = (--(e->refcount) == 0);
+  }
+  AtomicClearBits(addr, bits, lockbit);
+  synch_event_mu.Unlock();
+  if (del) {
+    DeleteSynchEvent(e);
+  }
+}
+
 // Return a refcounted reference to the SynchEvent of the object at address
 // "addr", if any.  The pointer returned is valid until the UnrefSynchEvent() is
 // called.
@@ -730,35 +732,25 @@
 }
 #endif
 
-#if defined(__APPLE__) || defined(ABSL_BUILD_DLL)
-// When building a dll symbol export lists may reference the destructor
-// and want it to be an exported symbol rather than an inline function.
-// Some apple builds also do dynamic library build but don't say it explicitly.
-Mutex::~Mutex() { Dtor(); }
-#endif
+static bool DebugOnlyIsExiting() {
+  return false;
+}
 
-#if !defined(NDEBUG) || defined(ABSL_HAVE_THREAD_SANITIZER)
-void Mutex::Dtor() {
+Mutex::~Mutex() {
+  intptr_t v = mu_.load(std::memory_order_relaxed);
+  if ((v & kMuEvent) != 0 && !DebugOnlyIsExiting()) {
+    ForgetSynchEvent(&this->mu_, kMuEvent, kMuSpin);
+  }
   if (kDebugMode) {
     this->ForgetDeadlockInfo();
   }
   ABSL_TSAN_MUTEX_DESTROY(this, __tsan_mutex_not_static);
 }
-#endif
 
 void Mutex::EnableDebugLog(const char* name) {
   SynchEvent* e = EnsureSynchEvent(&this->mu_, name, kMuEvent, kMuSpin);
   e->log = true;
   UnrefSynchEvent(e);
-  // This prevents "error: undefined symbol: absl::Mutex::~Mutex()"
-  // in a release build (NDEBUG defined) when a test does "#undef NDEBUG"
-  // to use assert macro. In such case, the test does not get the dtor
-  // definition because it's supposed to be outline when NDEBUG is not defined,
-  // and this source file does not define one either because NDEBUG is defined.
-  // Since it's not possible to take address of a destructor, we move the
-  // actual destructor code into the separate Dtor function and force the
-  // compiler to emit this function even if it's inline by taking its address.
-  ABSL_ATTRIBUTE_UNUSED volatile auto dtor = &Mutex::Dtor;
 }
 
 void EnableMutexInvariantDebugging(bool enabled) {
@@ -2478,6 +2470,12 @@
   UnrefSynchEvent(e);
 }
 
+CondVar::~CondVar() {
+  if ((cv_.load(std::memory_order_relaxed) & kCvEvent) != 0) {
+    ForgetSynchEvent(&this->cv_, kCvEvent, kCvSpin);
+  }
+}
+
 // Remove thread s from the list of waiters on this condition variable.
 void CondVar::Remove(PerThreadSynch* s) {
   SchedulingGuard::ScopedDisable disable_rescheduling;
diff --git a/absl/synchronization/mutex.h b/absl/synchronization/mutex.h
index d8264c0..95726f6 100644
--- a/absl/synchronization/mutex.h
+++ b/absl/synchronization/mutex.h
@@ -536,7 +536,6 @@
   void Block(base_internal::PerThreadSynch* s);
   // Wake a thread; return successor.
   base_internal::PerThreadSynch* Wakeup(base_internal::PerThreadSynch* w);
-  void Dtor();
 
   friend class CondVar;   // for access to Trans()/Fer().
   void Trans(MuHow how);  // used for CondVar->Mutex transfer
@@ -910,6 +909,7 @@
   // A `CondVar` allocated on the heap or on the stack can use the this
   // constructor.
   CondVar();
+  ~CondVar();
 
   // CondVar::Wait()
   //
@@ -1061,15 +1061,6 @@
 
 inline constexpr Mutex::Mutex(absl::ConstInitType) : mu_(0) {}
 
-#if !defined(__APPLE__) && !defined(ABSL_BUILD_DLL)
-inline Mutex::~Mutex() { Dtor(); }
-#endif
-
-#if defined(NDEBUG) && !defined(ABSL_HAVE_THREAD_SANITIZER)
-// Use default (empty) destructor in release build for performance reasons.
-inline void Mutex::Dtor() {}
-#endif
-
 inline CondVar::CondVar() : cv_(0) {}
 
 // static
diff --git a/absl/synchronization/mutex_test.cc b/absl/synchronization/mutex_test.cc
index 35b4333..6c38c07 100644
--- a/absl/synchronization/mutex_test.cc
+++ b/absl/synchronization/mutex_test.cc
@@ -1709,33 +1709,6 @@
   logged_cv.SignalAll();
 }
 
-TEST(Mutex, LoggingAddressReuse) {
-  // Repeatedly re-create a Mutex with debug logging at the same address.
-  alignas(absl::Mutex) char storage[sizeof(absl::Mutex)];
-  auto invariant =
-      +[](void *alive) { EXPECT_TRUE(*static_cast<bool *>(alive)); };
-  constexpr size_t kIters = 10;
-  bool alive[kIters] = {};
-  for (size_t i = 0; i < kIters; ++i) {
-    absl::Mutex *mu = new (storage) absl::Mutex;
-    alive[i] = true;
-    mu->EnableDebugLog("Mutex");
-    mu->EnableInvariantDebugging(invariant, &alive[i]);
-    mu->Lock();
-    mu->Unlock();
-    mu->~Mutex();
-    alive[i] = false;
-  }
-}
-
-TEST(Mutex, LoggingBankrupcy) {
-  // Test the case with too many live Mutexes with debug logging.
-  std::vector<absl::Mutex> mus(1 << 20);
-  for (auto &mu : mus) {
-    mu.EnableDebugLog("Mutex");
-  }
-}
-
 // --------------------------------------------------------
 
 // Generate the vector of thread counts for tests parameterized on thread count.