diff options
-rw-r--r-- | gobject/gobject.c | 10 | ||||
-rw-r--r-- | gobject/tests/threadtests.c | 87 |
2 files changed, 97 insertions, 0 deletions
diff --git a/gobject/gobject.c b/gobject/gobject.c index 2c62b5aae..9ddab43f0 100644 --- a/gobject/gobject.c +++ b/gobject/gobject.c @@ -3276,6 +3276,16 @@ toggle_refs_notify (GObject *object, ToggleRefStack tstack, *tstackptr; 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); tstack = *tstackptr; G_UNLOCK (toggle_refs_mutex); diff --git a/gobject/tests/threadtests.c b/gobject/tests/threadtests.c index b09b79f9b..3b485eb52 100644 --- a/gobject/tests/threadtests.c +++ b/gobject/tests/threadtests.c @@ -422,6 +422,91 @@ test_threaded_weak_ref_finalization (void) 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 main (int argc, 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/on-finalization", test_threaded_weak_ref_finalization); + g_test_add_func ("/GObject/threaded-toggle-notify", + test_threaded_toggle_notify); return g_test_run(); } |