mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2025-08-30 21:04:13 +02:00
The weak notification APIs g_object_weak_ref() and g_object_weak_unref() are not thread-safe. This patch adds thread-safe alternatives: g_object_weak_ref_full() and g_object_weak_unref_full(). The problem arises when other threads call g_object_run_dispose() or g_object_unref(), making g_object_weak_unref() unsafe. The caller cannot know whether the weak notification was successfully removed or might still be invoked. For example, g_object_weak_unref() will assert if no matching notification is found. This is inherrently racy. Beyond that problem, weak notifications often involve user data that must be freed -- either by the callback or after g_object_weak_unref(). Since you can't know which path executed, this can lead to races and double-free errors. The new g_object_weak_unref_full() returns a boolean to indicate whether the notification was removed or will still be invoked, allowing safe cleanup. This return value and acting upon it is the main solution for thread-safety. Note that g_object_unref() only starts disposing after ensuring there are no more GWeakRefs and only the single caller's strong reference remains. So you might think that no other thread could acquire a strong reference and race by calling g_object_weak_unref(). While this makes such a race less likely, it is not eliminated. If there are multiple weak notifications or closures, one can pass a reference to another thread that calls g_object_weak_unref() and enables the race. Also, with g_object_run_dispose(), there is nothing preventing another thread from racing against g_object_weak_unref(). g_object_weak_ref_full() and g_object_weak_unref_full() also support a `synchronize=TRUE` flag. This ensures the callback runs while holding a per-callback mutex, allowing g_object_weak_unref_full() to wait until the callback has either already run or will never run. Calling user callbacks while holding a lock can risk deadlocks, but the risk is limited because the lock is specific to that notification. Finally, GDestroyNotify callbacks are supported. While mostly a convenience, they are also invoked outside the lock, which enables more complex cleanup without the risk of deadlock. Contrary to common wisdom, combining weak notifications with GWeakRef does not solve this problem. Also, it forces to acquire strong references, which emits toggle notifications. When carefully using g_object_weak_ref_full(), the caller of g_object_weak_unref_full() can safely use a pointer to the object, without ever increasing the reference count. A unit test shows how that is done. This improves correctness and safety for weak references in multithreaded contexts. The main overhead of this change is that WeakRefTuple grew from 2 pointer sizes to 4. Every weak notification will have such a entry, so it takes now more memory to track the registration. Otherwise, there is no relevant overhead compared to before. Obviously, a "synchronized" notification is more expensive, which is why it requires an opt-in during g_object_weak_ref_full().