gdbus-connection: Work around race in connection tests
[platform/upstream/glib.git] / gio / tests / gdbus-threading.c
index 1736e60..1c1f1df 100644 (file)
@@ -297,7 +297,8 @@ test_sleep_in_thread_func (gpointer _data)
                              (GAsyncReadyCallback) sleep_cb,
                              data);
           g_main_loop_run (data->thread_loop);
-          g_print ("A");
+          if (!g_test_quiet ())
+            g_print ("A");
           //g_debug ("done invoking async (%p)", g_thread_self ());
         }
       else
@@ -314,7 +315,8 @@ test_sleep_in_thread_func (gpointer _data)
                                            -1,
                                            NULL,
                                            &error);
-          g_print ("S");
+          if (!g_test_quiet ())
+            g_print ("S");
           //g_debug ("done invoking sync (%p)", g_thread_self ());
           g_assert_no_error (error);
           g_assert (result != NULL);
@@ -406,9 +408,10 @@ test_method_calls_on_proxy (GDBusProxy *proxy)
 
       /* elapsed_msec should be 4000 msec +/- change for overhead/inaccuracy */
       g_assert_cmpint (elapsed_msec, >=, 3950);
-      g_assert_cmpint (elapsed_msec,  <, 6000);
+      g_assert_cmpint (elapsed_msec,  <, 8000);
 
-      g_print (" ");
+      if (!g_test_quiet ())
+        g_print (" ");
     }
 }
 
@@ -444,6 +447,136 @@ test_method_calls_in_thread (void)
 
   g_object_unref (proxy);
   g_object_unref (connection);
+
+  if (!g_test_quiet ())
+    g_print ("\n");
+}
+
+#define SLEEP_MIN_USEC 1
+#define SLEEP_MAX_USEC 10
+
+/* Can run in any thread */
+static void
+ensure_connection_works (GDBusConnection *conn)
+{
+  GVariant *v;
+  GError *error = NULL;
+
+  v = g_dbus_connection_call_sync (conn, "org.freedesktop.DBus",
+      "/org/freedesktop/DBus", "org.freedesktop.DBus", "GetId", NULL, NULL, 0, -1,
+      NULL, &error);
+  g_assert_no_error (error);
+  g_assert (v != NULL);
+  g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE ("(s)")));
+  g_variant_unref (v);
+}
+
+/*
+ * get_sync_in_thread:
+ * @data: (type guint): delay in microseconds
+ *
+ * Sleep for a short time, then get a session bus connection and call
+ * a method on it.
+ *
+ * Runs in a non-main thread.
+ *
+ * Returns: (transfer full): the connection
+ */
+static gpointer
+get_sync_in_thread (gpointer data)
+{
+  guint delay = GPOINTER_TO_UINT (data);
+  GError *error = NULL;
+  GDBusConnection *conn;
+
+  g_usleep (delay);
+
+  conn = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
+  g_assert_no_error (error);
+
+  ensure_connection_works (conn);
+
+  return conn;
+}
+
+static void
+test_threaded_singleton (void)
+{
+  guint i, n;
+  guint unref_wins = 0;
+  guint get_wins = 0;
+
+  if (g_test_thorough ())
+    n = 100000;
+  else
+    n = 5000;
+
+  for (i = 0; i < n; i++)
+    {
+      GThread *thread;
+      guint j;
+      guint unref_delay, get_delay;
+      GDBusConnection *new_conn;
+
+      /* We want to be the last ref, so let it finish setting up */
+      for (j = 0; j < 100; j++)
+        {
+          guint r = g_atomic_int_get (&G_OBJECT (c)->ref_count);
+
+          if (r == 1)
+            break;
+
+          g_debug ("run %u: refcount is %u, sleeping", i, r);
+          g_usleep (1000);
+        }
+
+      if (j == 100)
+        g_error ("connection had too many refs");
+
+      if (g_test_verbose () && (i % (n/50)) == 0)
+        g_print ("%u%%\n", ((i * 100) / n));
+
+      /* Delay for a random time on each side of the race, to perturb the
+       * timing. Ideally, we want each side to win half the races; these
+       * timings are about right on smcv's laptop.
+       */
+      unref_delay = g_random_int_range (SLEEP_MIN_USEC, SLEEP_MAX_USEC);
+      get_delay = g_random_int_range (SLEEP_MIN_USEC / 2, SLEEP_MAX_USEC / 2);
+
+      /* One half of the race is to call g_bus_get_sync... */
+      thread = g_thread_new ("get_sync_in_thread", get_sync_in_thread,
+          GUINT_TO_POINTER (get_delay));
+
+      /* ... and the other half is to unref the shared connection, which must
+       * have exactly one ref at this point
+       */
+      g_usleep (unref_delay);
+      g_object_unref (c);
+
+      /* Wait for the thread to run; see what it got */
+      new_conn = g_thread_join (thread);
+
+      /* If the thread won the race, it will have kept the same connection,
+       * and it'll have one ref
+       */
+      if (new_conn == c)
+        {
+          get_wins++;
+        }
+      else
+        {
+          unref_wins++;
+          /* c is invalid now, but new_conn is suitable for the
+           * next round
+           */
+          c = new_conn;
+        }
+
+      ensure_connection_works (c);
+    }
+
+  if (g_test_verbose ())
+    g_print ("Unref won %u races; Get won %u races\n", unref_wins, get_wins);
 }
 
 /* ---------------------------------------------------------------------------------------------------- */
@@ -454,25 +587,16 @@ main (int   argc,
 {
   GError *error;
   gint ret;
+  gchar *path;
 
-  g_type_init ();
   g_test_init (&argc, &argv, NULL);
 
-  /* all the tests use a session bus with a well-known address that we can bring up and down
-   * using session_bus_up() and session_bus_down().
-   */
-  g_unsetenv ("DISPLAY");
-  g_setenv ("DBUS_SESSION_BUS_ADDRESS", session_bus_get_temporary_address (), TRUE);
-
   session_bus_up ();
 
-  /* TODO: wait a bit for the bus to come up.. ideally session_bus_up() won't return
-   * until one can connect to the bus but that's not how things work right now
-   */
-  usleep (500 * 1000);
-
   /* this is safe; testserver will exit once the bus goes away */
-  g_assert (g_spawn_command_line_async (SRCDIR "/gdbus-testserver.py", NULL));
+  path = g_test_build_filename (G_TEST_BUILT, "gdbus-testserver", NULL);
+  g_assert (g_spawn_command_line_async (path, NULL));
+  g_free (path);
 
   /* wait for the service to come up */
   usleep (500 * 1000);
@@ -485,6 +609,7 @@ main (int   argc,
 
   g_test_add_func ("/gdbus/delivery-in-thread", test_delivery_in_thread);
   g_test_add_func ("/gdbus/method-calls-in-thread", test_method_calls_in_thread);
+  g_test_add_func ("/gdbus/threaded-singleton", test_threaded_singleton);
 
   ret = g_test_run();