[glib: 6/9] Add tests for GBinding thread-safety




commit cc15c933b34621b102b79b7a9aaafacc69ff7896
Author: Sebastian Dröge <sebastian centricular com>
Date:   Tue Nov 24 15:01:02 2020 +0200

    Add tests for GBinding thread-safety

 gobject/tests/binding.c | 178 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 178 insertions(+)
---
diff --git a/gobject/tests/binding.c b/gobject/tests/binding.c
index be6d751c1..d79d09ec5 100644
--- a/gobject/tests/binding.c
+++ b/gobject/tests/binding.c
@@ -886,6 +886,182 @@ binding_interface (void)
   g_object_unref (target);
 }
 
+typedef struct {
+  GThread *thread;
+  GBinding *binding;
+  GMutex *lock;
+  GCond *cond;
+  gboolean *wait;
+  gint *count; /* (atomic) */
+} ConcurrentUnbindData;
+
+static gpointer
+concurrent_unbind_func (gpointer data)
+{
+  ConcurrentUnbindData *unbind_data = data;
+
+  g_mutex_lock (unbind_data->lock);
+  g_atomic_int_inc (unbind_data->count);
+  while (*unbind_data->wait)
+    g_cond_wait (unbind_data->cond, unbind_data->lock);
+  g_mutex_unlock (unbind_data->lock);
+  g_binding_unbind (unbind_data->binding);
+  g_object_unref (unbind_data->binding);
+
+  return NULL;
+}
+
+static void
+binding_concurrent_unbind (void)
+{
+  guint i, j;
+
+  g_test_summary ("Test that unbinding from multiple threads concurrently works correctly");
+
+  for (i = 0; i < 50; i++)
+    {
+      BindingSource *source = g_object_new (binding_source_get_type (), NULL);
+      BindingTarget *target = g_object_new (binding_target_get_type (), NULL);
+      GBinding *binding;
+      GQueue threads = G_QUEUE_INIT;
+      GMutex lock;
+      GCond cond;
+      gboolean wait = TRUE;
+      gint count = 0; /* (atomic) */
+      ConcurrentUnbindData *data;
+
+      g_mutex_init (&lock);
+      g_cond_init (&cond);
+
+      binding = g_object_bind_property (source, "foo",
+                                        target, "bar",
+                                        G_BINDING_BIDIRECTIONAL);
+      g_object_ref (binding);
+
+      for (j = 0; j < 10; j++)
+        {
+          data = g_new0 (ConcurrentUnbindData, 1);
+
+          data->binding = g_object_ref (binding);
+          data->lock = &lock;
+          data->cond = &cond;
+          data->wait = &wait;
+          data->count = &count;
+
+          data->thread = g_thread_new ("binding-concurrent", concurrent_unbind_func, data);
+          g_queue_push_tail (&threads, data);
+        }
+
+      /* wait until all threads are started */
+      while (g_atomic_int_get (&count) < 10)
+        g_thread_yield ();
+
+      g_mutex_lock (&lock);
+      wait = FALSE;
+      g_cond_broadcast (&cond);
+      g_mutex_unlock (&lock);
+
+      while ((data = g_queue_pop_head (&threads)))
+        {
+          g_thread_join (data->thread);
+          g_free (data);
+        }
+
+      g_mutex_clear (&lock);
+      g_cond_clear (&cond);
+
+      g_object_unref (binding);
+      g_object_unref (source);
+      g_object_unref (target);
+    }
+}
+
+typedef struct {
+  GObject *object;
+  GMutex *lock;
+  GCond *cond;
+  gint *count; /* (atomic) */
+  gboolean *wait;
+} ConcurrentFinalizeData;
+
+static gpointer
+concurrent_finalize_func (gpointer data)
+{
+  ConcurrentFinalizeData *finalize_data = data;
+
+  g_mutex_lock (finalize_data->lock);
+  g_atomic_int_inc (finalize_data->count);
+  while (*finalize_data->wait)
+    g_cond_wait (finalize_data->cond, finalize_data->lock);
+  g_mutex_unlock (finalize_data->lock);
+  g_object_unref (finalize_data->object);
+  g_free (finalize_data);
+
+  return NULL;
+}
+
+static void
+binding_concurrent_finalizing (void)
+{
+  guint i;
+
+  g_test_summary ("Test that finalizing source/target from multiple threads concurrently works correctly");
+
+  for (i = 0; i < 50; i++)
+    {
+      BindingSource *source = g_object_new (binding_source_get_type (), NULL);
+      BindingTarget *target = g_object_new (binding_target_get_type (), NULL);
+      GBinding *binding;
+      GMutex lock;
+      GCond cond;
+      gboolean wait = TRUE;
+      ConcurrentFinalizeData *data;
+      GThread *source_thread, *target_thread;
+      gint count = 0; /* (atomic) */
+
+      g_mutex_init (&lock);
+      g_cond_init (&cond);
+
+      binding = g_object_bind_property (source, "foo",
+                                        target, "bar",
+                                        G_BINDING_BIDIRECTIONAL);
+      g_object_ref (binding);
+
+      data = g_new0 (ConcurrentFinalizeData, 1);
+      data->object = (GObject *) source;
+      data->wait = &wait;
+      data->lock = &lock;
+      data->cond = &cond;
+      data->count = &count;
+      source_thread = g_thread_new ("binding-concurrent", concurrent_finalize_func, data);
+
+      data = g_new0 (ConcurrentFinalizeData, 1);
+      data->object = (GObject *) target;
+      data->wait = &wait;
+      data->lock = &lock;
+      data->cond = &cond;
+      data->count = &count;
+      target_thread = g_thread_new ("binding-concurrent", concurrent_finalize_func, data);
+
+      /* wait until all threads are started */
+      while (g_atomic_int_get (&count) < 2)
+        g_thread_yield ();
+
+      g_mutex_lock (&lock);
+      wait = FALSE;
+      g_cond_broadcast (&cond);
+      g_mutex_unlock (&lock);
+
+      g_thread_join (source_thread);
+      g_thread_join (target_thread);
+
+      g_mutex_clear (&lock);
+      g_cond_clear (&cond);
+
+      g_object_unref (binding);
+    }
+}
+
 int
 main (int argc, char *argv[])
 {
@@ -908,6 +1084,8 @@ main (int argc, char *argv[])
   g_test_add_func ("/binding/unbind-multiple", binding_unbind_multiple);
   g_test_add_func ("/binding/fail", binding_fail);
   g_test_add_func ("/binding/interface", binding_interface);
+  g_test_add_func ("/binding/concurrent-unbind", binding_concurrent_unbind);
+  g_test_add_func ("/binding/concurrent-finalizing", binding_concurrent_finalizing);
 
   return g_test_run ();
 }


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]