Merge branch 'safer-toggle-notify' into 'main'

gobject: Ensure an object has toggle references before notifying it

Closes #2394

See merge request GNOME/glib!2072
This commit is contained in:
Philip Withnall 2021-09-21 10:38:28 +00:00
commit b2b7feda8d
2 changed files with 97 additions and 0 deletions

View File

@ -3276,6 +3276,16 @@ toggle_refs_notify (GObject *object,
ToggleRefStack tstack, *tstackptr; ToggleRefStack tstack, *tstackptr;
G_LOCK (toggle_refs_mutex); G_LOCK (toggle_refs_mutex);
/* If another thread removed the toggle reference on the object, while
* we were waiting here, there's nothing to notify.
* So let's check again if the object has toggle reference and in case return.
*/
if (!OBJECT_HAS_TOGGLE_REF (object))
{
G_UNLOCK (toggle_refs_mutex);
return;
}
tstackptr = g_datalist_id_get_data (&object->qdata, quark_toggle_refs); tstackptr = g_datalist_id_get_data (&object->qdata, quark_toggle_refs);
tstack = *tstackptr; tstack = *tstackptr;
G_UNLOCK (toggle_refs_mutex); G_UNLOCK (toggle_refs_mutex);

View File

@ -422,6 +422,91 @@ test_threaded_weak_ref_finalization (void)
g_assert_null (g_weak_ref_get (&weak)); g_assert_null (g_weak_ref_get (&weak));
} }
typedef struct
{
GObject *object;
int done; /* (atomic) */
int toggles; /* (atomic) */
} ToggleNotifyThreadData;
static gpointer
on_reffer_thread (gpointer user_data)
{
ToggleNotifyThreadData *thread_data = user_data;
while (!g_atomic_int_get (&thread_data->done))
{
g_object_ref (thread_data->object);
g_object_unref (thread_data->object);
}
return NULL;
}
static void
on_toggle_notify (gpointer data,
GObject *object,
gboolean is_last_ref)
{
/* Anything could be put here, but we don't care for this test.
* Actually having this empty made the bug to happen more frequently (being
* timing related).
*/
}
static gpointer
on_toggler_thread (gpointer user_data)
{
ToggleNotifyThreadData *thread_data = user_data;
while (!g_atomic_int_get (&thread_data->done))
{
g_object_ref (thread_data->object);
g_object_remove_toggle_ref (thread_data->object, on_toggle_notify, thread_data);
g_object_add_toggle_ref (thread_data->object, on_toggle_notify, thread_data);
g_object_unref (thread_data->object);
g_atomic_int_add (&thread_data->toggles, 1);
}
return NULL;
}
static void
test_threaded_toggle_notify (void)
{
GObject *object = g_object_new (G_TYPE_OBJECT, NULL);
ToggleNotifyThreadData data = { object, FALSE, 0 };
GThread *threads[3];
gsize i;
g_test_bug ("https://gitlab.gnome.org/GNOME/glib/issues/2394");
g_test_summary ("Test that toggle reference notifications can be changed "
"safely from another (the main) thread without causing the "
"notifying thread to abort");
g_object_add_toggle_ref (object, on_toggle_notify, &data);
g_object_unref (object);
g_assert_cmpint (object->ref_count, ==, 1);
threads[0] = g_thread_new ("on_reffer_thread", on_reffer_thread, &data);
threads[1] = g_thread_new ("on_another_reffer_thread", on_reffer_thread, &data);
threads[2] = g_thread_new ("on_main_toggler_thread", on_toggler_thread, &data);
/* We need to wait here for the threads to run for a bit in order to make the
* race to happen, so we wait for an high number of toggle changes to be met
* so that we can be consistent on each platform.
*/
while (g_atomic_int_get (&data.toggles) < 1000000)
;
g_atomic_int_set (&data.done, TRUE);
for (i = 0; i < G_N_ELEMENTS (threads); i++)
g_thread_join (threads[i]);
g_assert_cmpint (object->ref_count, ==, 1);
g_clear_object (&object);
}
int int
main (int argc, main (int argc,
char *argv[]) char *argv[])
@ -433,6 +518,8 @@ main (int argc,
g_test_add_func ("/GObject/threaded-weak-ref", test_threaded_weak_ref); g_test_add_func ("/GObject/threaded-weak-ref", test_threaded_weak_ref);
g_test_add_func ("/GObject/threaded-weak-ref/on-finalization", g_test_add_func ("/GObject/threaded-weak-ref/on-finalization",
test_threaded_weak_ref_finalization); test_threaded_weak_ref_finalization);
g_test_add_func ("/GObject/threaded-toggle-notify",
test_threaded_toggle_notify);
return g_test_run(); return g_test_run();
} }