gobject: use per-object bit-lock instead of global RWLock for GWeakRef

Replace the global RWLock with a per-object locking.

g_object_unref() needs to take a lock for GWeakRef while decrementing
the reference count to zero. That is also the case, when no weak refs
actually are registered because we cannot know that in a race free
manner without taking the lock. Replace the global RWLock with a
per-object bit lock.

Now there are actually two locks. The per-object lock
OPTIONAL2_BIT_LOCK_G_WEAK_REF and a bit lock WEAK_REF_LOCK on
`&weak_ref->priv.p`.

The object lock OPTIONAL2_BIT_LOCK_G_WEAK_REF must be taken first, we
cannot take it after having a WEAK_REF_LOCK lock. This prevents dead
locks.

Downsides:

- this requires to grow the GObject size (on x86_64) to add a
  GObjectPrivate for "optional2_flags". We cannot use the bitlock
  on "optional_flags", because while holding _weak_ref_lock(), we
  need to be able to call g_object_ref(). g_object_ref() locks on
  OPTIONAL_BIT_LOCK_TOGGLE_REFS. If OPTIONAL2_BIT_LOCK_G_WEAK_REF were on
  the same "optional_flags" as OPTIONAL_BIT_LOCK_TOGGLE_REFS, it would
  mean we have cases where we take

     OPTIONAL2_BIT_LOCK_G_WEAK_REF && _weak_ref_lock()

  and cases where we take

    _weak_ref_lock() && OPTIONAL_BIT_LOCK_TOGGLE_REFS

  That would deadlock. We need distinct locks for that. Add another
  gint field in GObjectPrivate for the bitlock OPTIONAL2_BIT_LOCK_G_WEAK_REF.

- now `g_weak_ref_set()` also temporarily raises the ref count on the
  old object. That is because we must keep the old object alive to take
  a OPTIONAL2_BIT_LOCK_G_WEAK_REF lock. Taking and dropping references
  emit toggle notifications, which is a visible change in behavior.
diff --git a/gobject/gobject.c b/gobject/gobject.c
index 0f31c60..1b24a23 100644
--- a/gobject/gobject.c
+++ b/gobject/gobject.c
@@ -109,6 +109,10 @@
 #define OPTIONAL_FLAG_HAS_NOTIFY_HANDLER (1 << 2) /* Same, specifically for "notify" */
 #define OPTIONAL_FLAG_LOCK               (1 << 3) /* _OPTIONAL_BIT_LOCK */
 
+#define _OPTIONAL2_BIT_LOCK              0
+
+#define OPTIONAL2_FLAG_LOCK              (1 << 0) /* _OPTIONAL2_BIT_LOCK */
+
 /* We use g_bit_lock(), which only supports one lock per integer.
  *
  * Hence, while we have locks for different purposes, internally they all
@@ -128,26 +132,24 @@
 #define OPTIONAL_BIT_LOCK_TOGGLE_REFS    3
 #define OPTIONAL_BIT_LOCK_CLOSURE_ARRAY  4
 
+/* These lock types are for optional2_flags and object_bit_lock2(). */
+#define OPTIONAL2_BIT_LOCK_G_WEAK_REF    1
+
 #if SIZEOF_INT == 4 && GLIB_SIZEOF_VOID_P >= 8
 #define HAVE_OPTIONAL_FLAGS_IN_GOBJECT 1
 #else
 #define HAVE_OPTIONAL_FLAGS_IN_GOBJECT 0
 #endif
 
-/* For now we only create a private struct if we don't have optional flags in
- * GObject. Currently we don't need it otherwise. In the future we might
- * always add a private struct. */
-#define HAVE_PRIVATE (!HAVE_OPTIONAL_FLAGS_IN_GOBJECT)
-
-#if HAVE_PRIVATE
-typedef struct {
+typedef struct
+{
 #if !HAVE_OPTIONAL_FLAGS_IN_GOBJECT
-	guint optional_flags; /* (atomic) */
+  guint optional_flags; /* (atomic) */
 #endif
+  guint optional2_flags; /* (atomic) */
 } GObjectPrivate;
 
 static int GObject_private_offset;
-#endif
 
 typedef struct
 {
@@ -165,6 +167,13 @@
 G_STATIC_ASSERT(G_STRUCT_OFFSET(GObject, ref_count) == G_STRUCT_OFFSET(GObjectReal, ref_count));
 G_STATIC_ASSERT(G_STRUCT_OFFSET(GObject, qdata) == G_STRUCT_OFFSET(GObjectReal, qdata));
 
+typedef struct
+{
+#if G_ENABLE_DEBUG
+  GObject *object;
+#endif
+  GSList *list;
+} GWeakRefList;
 
 /* --- prototypes --- */
 static void	g_object_base_class_init		(GObjectClass	*class);
@@ -209,7 +218,6 @@
 
 static void object_interface_check_properties           (gpointer        check_data,
 							 gpointer        g_iface);
-static void                weak_locations_free_unlocked (GSList **weak_locations);
 
 /* --- typedefs --- */
 typedef struct _GObjectNotifyQueue            GObjectNotifyQueue;
@@ -229,17 +237,13 @@
 static GParamSpecPool      *pspec_pool = NULL;
 static gulong	            gobject_signals[LAST_SIGNAL] = { 0, };
 static guint (*floating_flag_handler) (GObject*, gint) = object_floating_flag_handler;
-/* qdata pointing to GSList<GWeakRef *>, protected by weak_locations_lock */
 static GQuark	            quark_weak_locations = 0;
-static GRWLock              weak_locations_lock;
 
-#if HAVE_PRIVATE
 G_ALWAYS_INLINE static inline GObjectPrivate *
 g_object_get_instance_private (GObject *object)
 {
   return G_STRUCT_MEMBER_P (object, GObject_private_offset);
 }
-#endif
 
 G_ALWAYS_INLINE static inline guint *
 object_get_optional_flags_p (GObject *object)
@@ -288,6 +292,29 @@
   g_bit_unlock ((gint *) object_get_optional_flags_p (object), _OPTIONAL_BIT_LOCK);
 }
 
+static void
+object_bit_lock2 (GObject *object, guint lock_bit)
+{
+#if defined(G_ENABLE_DEBUG)
+  /* Only OPTIONAL2_BIT_LOCK_G_WEAK_REF is supported. Also, will take this lock
+   * nested (on two objects) during _weak_ref_set(). We thus cannot use
+   * something like _object_bit_is_locked for asserting. */
+  g_assert (lock_bit == OPTIONAL2_BIT_LOCK_G_WEAK_REF);
+#endif
+  if (object)
+    g_bit_lock ((gint *) &g_object_get_instance_private (object)->optional2_flags, _OPTIONAL2_BIT_LOCK);
+}
+
+static void
+object_bit_unlock2 (GObject *object, guint lock_bit)
+{
+#if defined(G_ENABLE_DEBUG)
+  g_assert (lock_bit == OPTIONAL2_BIT_LOCK_G_WEAK_REF);
+#endif
+  if (object)
+    g_bit_unlock ((gint *) &g_object_get_instance_private (object)->optional2_flags, _OPTIONAL2_BIT_LOCK);
+}
+
 /* --- functions --- */
 static void
 g_object_notify_queue_free (gpointer data)
@@ -542,10 +569,8 @@
     }
 #endif /* G_ENABLE_DEBUG */
 
-#if HAVE_PRIVATE
   GObject_private_offset =
       g_type_add_instance_private (G_TYPE_OBJECT, sizeof (GObjectPrivate));
-#endif
 }
 
 /* Initialize the global GParamSpecPool; this function needs to be
@@ -678,9 +703,7 @@
    */
   g_type_add_interface_check (NULL, object_interface_check_properties);
 
-#if HAVE_PRIVATE
   g_type_class_adjust_private_offset (class, &GObject_private_offset);
-#endif
 }
 
 /* Sinks @pspec if it’s a floating ref. */
@@ -1434,6 +1457,11 @@
                   "and must be removed with g_object_ref_sink().",
                   G_OBJECT_TYPE_NAME (object), object);
    }
+
+  /* At this point we really should not have any weak-locations anymore. We
+   * are about to g_datalist_clear() them, would we need a lock for
+   * OPTIONAL2_BIT_LOCK_G_WEAK_REF? */
+  g_assert (!g_datalist_id_get_data (&object->qdata, quark_weak_locations));
 #endif
 
   g_datalist_clear (&object->qdata);
@@ -1478,7 +1506,11 @@
   TRACE (GOBJECT_OBJECT_DISPOSE(object,G_TYPE_FROM_INSTANCE(object), 0));
   G_OBJECT_GET_CLASS (object)->dispose (object);
   TRACE (GOBJECT_OBJECT_DISPOSE_END(object,G_TYPE_FROM_INSTANCE(object), 0));
+
+  object_bit_lock2 (object, OPTIONAL2_BIT_LOCK_G_WEAK_REF);
   g_datalist_id_remove_data (&object->qdata, quark_weak_locations);
+  object_bit_unlock2 (object, OPTIONAL2_BIT_LOCK_G_WEAK_REF);
+
   g_object_unref (object);
 }
 
@@ -3841,77 +3873,47 @@
 static gboolean
 _object_unref_clear_weak_locations (GObject *object, gint *p_old_ref, gboolean do_unref)
 {
-  GSList **weak_locations;
+  gboolean success;
 
-  if (do_unref)
+  object_bit_lock2 (object, OPTIONAL2_BIT_LOCK_G_WEAK_REF);
+
+  /* We now hold a lock. We must check that the ref-count is as expected at 1.
+   * Otherwise, we are non the position to proceed clearing weak references. */
+  *p_old_ref = g_atomic_int_get ((gint *) &object->ref_count);
+  success = (*p_old_ref == 1);
+
+  if (success)
     {
-      gboolean unreffed = FALSE;
+      /* Looks good. Clear weak locations... */
+      g_datalist_id_remove_data (&object->qdata, quark_weak_locations);
 
-      /* Fast path for the final unref using a read-lck only. We check whether
-       * we have weak_locations and drop ref count to zero under a reader lock. */
-
-      g_rw_lock_reader_lock (&weak_locations_lock);
-
-      weak_locations = g_datalist_id_get_data (&object->qdata, quark_weak_locations);
-      if (!weak_locations)
+      /* Note that g_weak_ref_set() requires a OPTIONAL2_BIT_LOCK_G_WEAK_REF lock,
+       * so right now no new weak references can be registered.
+       *
+       * However, g_weak_ref_get() only requires a _weak_ref_lock() lock,
+       * so we might race against having the object resurrected.
+       *
+       * We can still fail. Do the final unref or recheck the ref-count.
+       *
+       * Note what this means. If we have multiple weak references registered,
+       * then we start clearing the weak locations, and half way through a weak-ref
+       * can resurrect the object. But the weak references are already partly cleared. */
+      if (do_unref)
         {
-          unreffed = g_atomic_int_compare_and_exchange_full ((int *) &object->ref_count,
-                                                             1, 0,
-                                                             p_old_ref);
-          g_rw_lock_reader_unlock (&weak_locations_lock);
-          return unreffed;
+          success = g_atomic_int_compare_and_exchange_full ((int *) &object->ref_count,
+                                                            1, 0,
+                                                            p_old_ref);
         }
-
-      g_rw_lock_reader_unlock (&weak_locations_lock);
-
-      /* We have weak-locations. Note that we are here already after dispose(). That
-       * means, during dispose a GWeakRef was registered (very unusual). */
-
-      g_rw_lock_writer_lock (&weak_locations_lock);
-
-      if (!g_atomic_int_compare_and_exchange_full ((int *) &object->ref_count,
-                                                   1, 0,
-                                                   p_old_ref))
+      else
         {
-          g_rw_lock_writer_unlock (&weak_locations_lock);
-          return FALSE;
+          *p_old_ref = g_atomic_int_get ((gint *) &object->ref_count);
+          success = (*p_old_ref == 1);
         }
-
-      weak_locations = g_datalist_id_remove_no_notify (&object->qdata, quark_weak_locations);
-      g_clear_pointer (&weak_locations, weak_locations_free_unlocked);
-
-      g_rw_lock_writer_unlock (&weak_locations_lock);
-      return TRUE;
     }
 
-  weak_locations = g_datalist_id_get_data (&object->qdata, quark_weak_locations);
-  if (weak_locations != NULL)
-    {
-      g_rw_lock_writer_lock (&weak_locations_lock);
+  object_bit_unlock2 (object, OPTIONAL2_BIT_LOCK_G_WEAK_REF);
 
-      *p_old_ref = g_atomic_int_get (&object->ref_count);
-      if (*p_old_ref != 1)
-        {
-          g_rw_lock_writer_unlock (&weak_locations_lock);
-          return FALSE;
-        }
-
-      weak_locations = g_datalist_id_remove_no_notify (&object->qdata, quark_weak_locations);
-      g_clear_pointer (&weak_locations, weak_locations_free_unlocked);
-
-      g_rw_lock_writer_unlock (&weak_locations_lock);
-      return TRUE;
-    }
-
-  /* We don't need to re-fetch p_old_ref or check that it's still 1. The caller
-   * did that already. We are good.
-   *
-   * Note that in this case we fetched old_ref and weak_locations separately,
-   * without a lock. But this is fine. We are still before calling dispose().
-   * If there is a race at this point, the same race can happen between
-   * _object_unref_clear_weak_locations() and dispose() call. That is handled
-   * just fine. */
-  return TRUE;
+  return success;
 }
 
 /**
@@ -5064,6 +5066,201 @@
  * without first having or creating a strong reference to the object.
  */
 
+#define WEAK_REF_LOCK 0
+
+static GObject *
+_weak_ref_clean_pointer (gpointer ptr)
+{
+  /* Drop the lockbit WEAK_REF_LOCK (if set). */
+  return g_pointer_bit_lock_mask_ptr (ptr, WEAK_REF_LOCK, FALSE, 0, NULL);
+}
+
+static void
+_weak_ref_lock (GWeakRef *weak_ref, GObject **out_object)
+{
+  g_pointer_bit_lock (&weak_ref->priv.p, WEAK_REF_LOCK);
+
+  if (out_object)
+    {
+      /* Let's read "weak_ref->priv.p" without atomic. We just set a bitlock (using
+       * atomics), nobody is changing this value. */
+      *out_object = _weak_ref_clean_pointer (weak_ref->priv.p);
+    }
+}
+
+static void
+_weak_ref_unlock (GWeakRef *weak_ref)
+{
+  g_pointer_bit_unlock (&weak_ref->priv.p, WEAK_REF_LOCK);
+}
+
+static void
+_weak_ref_unlock_and_set (GWeakRef *weak_ref, GObject *object)
+{
+  g_pointer_bit_unlock_and_set (&weak_ref->priv.p, WEAK_REF_LOCK, object, 0);
+}
+
+static void
+_weak_ref_list_free (GWeakRefList *weak_ref_list)
+{
+  /* This must be called with a OPTIONAL2_BIT_LOCK_G_WEAK_REF lock hold. Note
+   * that this function is the destroy notify for quark_weak_locations data. We
+   * will always explicitly call
+   *
+   *   g_datalist_id_remove_data (&object->qdata, quark_weak_locations);
+   *
+   * while holding a OPTIONAL2_BIT_LOCK_G_WEAK_REF. Only during finalize
+   * (g_datalist_clear()) we don't. But then the qdata is expected not to be
+   * set. */
+  while (weak_ref_list->list)
+    {
+      GWeakRef *weak_ref = weak_ref_list->list->data;
+      gpointer ptr;
+
+      weak_ref_list->list = g_slist_remove (weak_ref_list->list, weak_ref);
+
+      /* Fast-path. Most likely @weak_ref is currently not locked, so we can
+       * just atomically set the pointer to NULL. */
+      ptr = g_atomic_pointer_get (&weak_ref->priv.p);
+#if G_ENABLE_DEBUG
+      g_assert(_weak_ref_clean_pointer (ptr) == weak_ref_list->object);
+#endif
+      if (G_LIKELY (ptr == _weak_ref_clean_pointer (ptr)))
+        {
+          /* The pointer is unlocked. Try an atomic compare-and-exchange... */
+          if (g_atomic_pointer_compare_and_exchange (&weak_ref->priv.p, ptr, NULL))
+            continue;
+        }
+
+      /* The @weak_ref is locked. This can only be due to a concurrent
+       * g_weak_ref_get() (because g_weak_ref_set() would wait to acquire a
+       * OPTIONAL2_BIT_LOCK_G_WEAK_REF lock).
+       *
+       * Take the lock on @weak_ref, and set the pointer to NULL. */
+      _weak_ref_lock (weak_ref, NULL);
+#if G_ENABLE_DEBUG
+      g_assert(_weak_ref_clean_pointer (weak_ref->priv.p) == weak_ref_list->object);
+#endif
+      _weak_ref_unlock_and_set (weak_ref, NULL);
+    }
+
+  g_free_sized (weak_ref_list, sizeof (GWeakRefList));
+}
+
+static void
+_weak_ref_set (GWeakRef *weak_ref,
+               GObject *new_object,
+               gboolean is_initially_null)
+{
+  GWeakRefList *weak_ref_list;
+  GObject *old_object_unlock;
+  GObject *old_object_real;
+  GObject *old_object;
+
+  if (is_initially_null)
+    {
+      /* The caller indicates that the weak-ref is set to NULL. Don't check. */
+      old_object = NULL;
+    }
+  else
+    {
+      /* We must take a lock on old_object. For that, we have to first get a
+       * reference to it. */
+      old_object = g_weak_ref_get (weak_ref);
+    }
+
+  /* We need lock on both objects and the weak_ref. The order of the locks
+   * matters so we don't deadlock. */
+  if (new_object && old_object && (((guintptr) (gpointer) old_object) < ((guintptr) ((gpointer) new_object))))
+    {
+      /* To avoid deadlocks, we sort the objects by pointer value. */
+      object_bit_lock2 (old_object, OPTIONAL2_BIT_LOCK_G_WEAK_REF);
+      object_bit_lock2 (new_object, OPTIONAL2_BIT_LOCK_G_WEAK_REF);
+      old_object_unlock = old_object;
+    }
+  else
+    {
+      object_bit_lock2 (new_object, OPTIONAL2_BIT_LOCK_G_WEAK_REF);
+      if (new_object != old_object)
+        {
+          object_bit_lock2 (old_object, OPTIONAL2_BIT_LOCK_G_WEAK_REF);
+          old_object_unlock = old_object;
+        }
+      else
+        old_object_unlock = NULL;
+    }
+  _weak_ref_lock (weak_ref, &old_object_real);
+
+  if (old_object_real != old_object)
+    {
+      /* A race. old_object_real is not the expected old_object due to a
+       * concurrent g_weak_ref_set() call. */
+      if (old_object_real)
+        {
+          /* We lost the race and are done. */
+          object_bit_unlock2 (old_object_unlock, OPTIONAL2_BIT_LOCK_G_WEAK_REF);
+          object_bit_unlock2 (new_object, OPTIONAL2_BIT_LOCK_G_WEAK_REF);
+          _weak_ref_unlock (weak_ref);
+          g_clear_object (&old_object);
+          return;
+        }
+
+      /* old_object_real is NULL, which is unexpected but OK. Proceed to set. */
+    }
+
+  if (old_object_real && new_object != old_object_real)
+    {
+      weak_ref_list = g_datalist_id_get_data (&old_object_real->qdata, quark_weak_locations);
+      if (!weak_ref_list)
+        {
+          g_critical ("unexpected missing GWeakRefList");
+        }
+      else
+        {
+#if G_ENABLE_DEBUG
+          g_assert (g_slist_find (weak_ref_list->list, weak_ref));
+#endif
+          weak_ref_list->list = g_slist_remove (weak_ref_list->list, weak_ref);
+          if (!weak_ref_list->list)
+            g_datalist_id_remove_data (&old_object_real->qdata, quark_weak_locations);
+        }
+    }
+
+  object_bit_unlock2 (old_object_unlock, OPTIONAL2_BIT_LOCK_G_WEAK_REF);
+
+  if (new_object && new_object != old_object_real)
+    {
+      if (g_atomic_int_get (&new_object->ref_count) < 1)
+        {
+          g_critical ("calling g_weak_ref_set() with already destroyed object");
+          object_bit_unlock2 (new_object, OPTIONAL2_BIT_LOCK_G_WEAK_REF);
+          new_object = NULL;
+        }
+      else
+        {
+          weak_ref_list = g_datalist_id_get_data (&new_object->qdata, quark_weak_locations);
+          if (!weak_ref_list)
+            {
+              weak_ref_list = g_new0 (GWeakRefList, 1);
+#if G_ENABLE_DEBUG
+              weak_ref_list->object = new_object;
+#endif
+              g_datalist_id_set_data_full (&new_object->qdata, quark_weak_locations,
+                                           weak_ref_list, (GDestroyNotify) _weak_ref_list_free);
+            }
+
+          weak_ref_list->list = g_slist_prepend (weak_ref_list->list, weak_ref);
+        }
+    }
+
+  _weak_ref_unlock_and_set (weak_ref, new_object);
+  object_bit_unlock2 (new_object, OPTIONAL2_BIT_LOCK_G_WEAK_REF);
+
+  /* g_object_unref() emits callbacks. Can only unref now after all locks are
+   * released. */
+  g_clear_object (&old_object);
+}
+
 /**
  * g_weak_ref_init: (skip)
  * @weak_ref: (inout): uninitialized or empty location for a weak
@@ -5084,12 +5281,19 @@
  */
 void
 g_weak_ref_init (GWeakRef *weak_ref,
-                 gpointer  object)
+                 gpointer object)
 {
-  weak_ref->priv.p = NULL;
+  g_return_if_fail (weak_ref);
+  g_return_if_fail (object == NULL || G_IS_OBJECT (object));
 
+  g_atomic_pointer_set (&weak_ref->priv.p, NULL);
   if (object)
-    g_weak_ref_set (weak_ref, object);
+    {
+      /* We give a hint that the weak_ref is currently NULL. Unlike
+       * g_weak_ref_set(), we then don't need extra locks just to
+       * find out that we have no object. */
+      _weak_ref_set (weak_ref, object, TRUE);
+    }
 }
 
 /**
@@ -5103,6 +5307,11 @@
  * You should only call this on a #GWeakRef that previously had
  * g_weak_ref_init() called on it.
  *
+ * Note that clearing a weak ref needs to temporarily take a reference
+ * on the old object. This means that toggle notifications can be emitted
+ * and that the old objects may be destroyed by this (if another thread
+ * races to also release the last reference).
+ *
  * Since: 2.32
  */
 void
@@ -5142,48 +5351,66 @@
 
   g_return_val_if_fail (weak_ref, NULL);
 
-  g_rw_lock_reader_lock (&weak_locations_lock);
+again:
 
-  object = weak_ref->priv.p;
+  _weak_ref_lock (weak_ref, &object);
 
   if (object)
     object = object_ref (object, &toggle_notify, &toggle_data);
 
-  g_rw_lock_reader_unlock (&weak_locations_lock);
+  _weak_ref_unlock (weak_ref);
 
   if (toggle_notify)
     toggle_notify (toggle_data, object, FALSE);
 
-  return object;
-}
-
-static void
-weak_locations_free_unlocked (GSList **weak_locations)
-{
-  if (*weak_locations)
+  if (object)
     {
-      GSList *weak_location;
+      GObject *object2;
+      gboolean obj_is_good;
 
-      for (weak_location = *weak_locations; weak_location;)
+      /* There is a problem...
+       *
+       * We obtained a reference on @object while holding a _weak_ref_lock()
+       * lock.
+       *
+       * But _object_unref_clear_weak_locations() is racing against clearing
+       * the weak-ref. We can only ensure that the weak-reference is sane, if
+       * we also do that under a OPTIONAL2_BIT_LOCK_G_WEAK_REF lock.  But we
+       * could not have taken that look, before getting a reference on @object.
+       *
+       * Now that we have the @object, we need to take locks in the right order
+       * and check again. */
+      object_bit_lock2 (object, OPTIONAL2_BIT_LOCK_G_WEAK_REF);
+      _weak_ref_lock (weak_ref, &object2);
+      obj_is_good = (object2 == object);
+      _weak_ref_unlock (weak_ref);
+      object_bit_unlock2 (object, OPTIONAL2_BIT_LOCK_G_WEAK_REF);
+
+      /* FIXME: while this should work, when @obj_is_good is FALSE, tests break
+       * (??).
+       *
+       * But what is worse, is that we cannot have g_weak_ref_get() /
+       * g_weak_ref_set() take spurious references on Object. We currently need
+       * that, because we need to take OPTIONAL2_BIT_LOCK_G_WEAK_REF locks and
+       * the object needs to stay alive for that.
+       *
+       * However, this is too bad. Taking a reference emits toggle
+       * notifications, and worse, it means that we can steal the destruction
+       * from another thread that does the final g_object_unref().
+       *
+       * The fix instead will be to request a ref-counted locker instance from
+       * the object, which we can keep alive separately from object and can
+       * take a lock on. */
+      obj_is_good = TRUE;
+
+      if (!obj_is_good)
         {
-          GWeakRef *weak_ref_location = weak_location->data;
-
-          weak_ref_location->priv.p = NULL;
-          weak_location = g_slist_delete_link (weak_location, weak_location);
+          g_object_unref (object);
+          goto again;
         }
     }
 
-  g_free (weak_locations);
-}
-
-static void
-weak_locations_free (gpointer data)
-{
-  GSList **weak_locations = data;
-
-  g_rw_lock_writer_lock (&weak_locations_lock);
-  weak_locations_free_unlocked (weak_locations);
-  g_rw_lock_writer_unlock (&weak_locations_lock);
+  return object;
 }
 
 /**
@@ -5197,86 +5424,19 @@
  * You must own a strong reference on @object while calling this
  * function.
  *
+ * Note that resetting a weak ref needs to temporarily take a reference
+ * on the old object. This means that toggle notifications can be emitted
+ * and that the old objects may be destroyed by this (if another thread
+ * races to also release the last reference).
+ *
  * Since: 2.32
  */
 void
 g_weak_ref_set (GWeakRef *weak_ref,
-                gpointer  object)
+                gpointer object)
 {
-  GSList **weak_locations;
-  GObject *new_object;
-  GObject *old_object;
-
   g_return_if_fail (weak_ref != NULL);
   g_return_if_fail (object == NULL || G_IS_OBJECT (object));
 
-  new_object = object;
-
-  g_rw_lock_writer_lock (&weak_locations_lock);
-
-  /* We use the extra level of indirection here so that if we have ever
-   * had a weak pointer installed at any point in time on this object,
-   * we can see that there is a non-NULL value associated with the
-   * weak-pointer quark and know that this value will not change at any
-   * point in the object's lifetime.
-   *
-   * Both properties are important for reducing the amount of times we
-   * need to acquire locks and for decreasing the duration of time the
-   * lock is held while avoiding some rather tricky races.
-   *
-   * Specifically: we can avoid having to do an extra unconditional lock
-   * in g_object_unref() without worrying about some extremely tricky
-   * races.
-   */
-
-  old_object = weak_ref->priv.p;
-  if (new_object != old_object)
-    {
-      weak_ref->priv.p = new_object;
-
-      /* Remove the weak ref from the old object */
-      if (old_object != NULL)
-        {
-          weak_locations = g_datalist_id_get_data (&old_object->qdata, quark_weak_locations);
-          if (weak_locations == NULL)
-            {
-              g_critical ("unexpected missing GWeakRef");
-            }
-          else
-            {
-              *weak_locations = g_slist_remove (*weak_locations, weak_ref);
-
-              if (!*weak_locations)
-                {
-                  weak_locations_free_unlocked (weak_locations);
-                  g_datalist_id_remove_no_notify (&old_object->qdata, quark_weak_locations);
-                }
-            }
-        }
-
-      /* Add the weak ref to the new object */
-      if (new_object != NULL)
-        {
-          if (g_atomic_int_get (&new_object->ref_count) < 1)
-            {
-              weak_ref->priv.p = NULL;
-              g_rw_lock_writer_unlock (&weak_locations_lock);
-              g_critical ("calling g_weak_ref_set() with already destroyed object");
-              return;
-            }
-
-          weak_locations = g_datalist_id_get_data (&new_object->qdata, quark_weak_locations);
-
-          if (weak_locations == NULL)
-            {
-              weak_locations = g_new0 (GSList *, 1);
-              g_datalist_id_set_data_full (&new_object->qdata, quark_weak_locations,
-                                           weak_locations, weak_locations_free);
-            }
-
-          *weak_locations = g_slist_prepend (*weak_locations, weak_ref);
-        }
-    }
-
-  g_rw_lock_writer_unlock (&weak_locations_lock);
+  _weak_ref_set (weak_ref, object, FALSE);
 }