Add g_output_stream_write_all_async()
[platform/upstream/glib.git] / gio / tests / gdbus-threading.c
index 1de395c..c62b223 100644 (file)
@@ -13,9 +13,7 @@
  * Lesser General Public License for more details.
  *
  * You should have received a copy of the GNU Lesser General
- * Public License along with this library; if not, write to the
- * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
- * Boston, MA 02111-1307, USA.
+ * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
  *
  * Author: David Zeuthen <davidz@redhat.com>
  */
@@ -29,9 +27,6 @@
 /* all tests rely on a global connection */
 static GDBusConnection *c = NULL;
 
-/* all tests rely on a shared mainloop */
-static GMainLoop *loop = NULL;
-
 /* ---------------------------------------------------------------------------------------------------- */
 /* Ensure that signal and method replies are delivered in the right thread */
 /* ---------------------------------------------------------------------------------------------------- */
@@ -136,7 +131,7 @@ test_delivery_in_thread_func (gpointer _data)
                           "/org/freedesktop/DBus", /* object path */
                           "org.freedesktop.DBus",  /* interface name */
                           "GetId",                 /* method name */
-                          NULL,
+                          NULL, NULL,
                           G_DBUS_CALL_FLAGS_NONE,
                           -1,
                           NULL,
@@ -156,7 +151,7 @@ test_delivery_in_thread_func (gpointer _data)
                           "/org/freedesktop/DBus", /* object path */
                           "org.freedesktop.DBus",  /* interface name */
                           "GetId",                 /* method name */
-                          NULL,
+                          NULL, NULL,
                           G_DBUS_CALL_FLAGS_NONE,
                           -1,
                           ca,
@@ -174,7 +169,7 @@ test_delivery_in_thread_func (gpointer _data)
                           "/org/freedesktop/DBus", /* object path */
                           "org.freedesktop.DBus",  /* interface name */
                           "GetId",                 /* method name */
-                          NULL,
+                          NULL, NULL,
                           G_DBUS_CALL_FLAGS_NONE,
                           -1,
                           ca,
@@ -197,6 +192,7 @@ test_delivery_in_thread_func (gpointer _data)
                                                         "NameOwnerChanged",      /* member */
                                                         "/org/freedesktop/DBus", /* path */
                                                         NULL,
+                                                        G_DBUS_SIGNAL_FLAGS_NONE,
                                                         signal_handler,
                                                         &data,
                                                         NULL);
@@ -220,27 +216,17 @@ test_delivery_in_thread_func (gpointer _data)
   g_main_loop_unref (thread_loop);
   g_main_context_unref (thread_context);
 
-  g_main_loop_quit (loop);
-
   return NULL;
 }
 
 static void
 test_delivery_in_thread (void)
 {
-  GError *error;
   GThread *thread;
 
-  error = NULL;
-  thread = g_thread_create (test_delivery_in_thread_func,
-                            NULL,
-                            TRUE,
-                            &error);
-  g_assert_no_error (error);
-  g_assert (thread != NULL);
-
-  /* run the event loop - it is needed to dispatch D-Bus messages */
-  g_main_loop_run (loop);
+  thread = g_thread_new ("deliver",
+                         test_delivery_in_thread_func,
+                         NULL);
 
   g_thread_join (thread);
 }
@@ -255,8 +241,6 @@ typedef struct {
 
   GMainLoop *thread_loop;
   GThread *thread;
-
-  gboolean done;
 } SyncThreadData;
 
 static void
@@ -311,7 +295,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
@@ -328,7 +313,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);
@@ -341,18 +327,11 @@ test_sleep_in_thread_func (gpointer _data)
   g_main_loop_unref (data->thread_loop);
   g_main_context_unref (thread_context);
 
-  data->done = TRUE;
-  g_main_loop_quit (loop);
-
   return NULL;
 }
 
 static void
-on_proxy_appeared (GDBusConnection *connection,
-                   const gchar     *name,
-                   const gchar     *name_owner,
-                   GDBusProxy      *proxy,
-                   gpointer         user_data)
+test_method_calls_on_proxy (GDBusProxy *proxy)
 {
   guint n;
 
@@ -364,6 +343,11 @@ on_proxy_appeared (GDBusConnection *connection,
    * a number of times. We do this so each set of calls add up to 4000
    * milliseconds.
    *
+   * The dbus test server that this code calls into uses glib timeouts
+   * to do the sleeping which have only a granularity of 1ms.  It is
+   * therefore possible to lose as much as 40ms; the test could finish
+   * in slightly less than 4 seconds.
+   *
    * We run this test twice - first with async calls in each thread, then
    * again with sync calls
    */
@@ -377,12 +361,10 @@ on_proxy_appeared (GDBusConnection *connection,
       SyncThreadData data1;
       SyncThreadData data2;
       SyncThreadData data3;
-      GError *error;
       GTimeVal start_time;
       GTimeVal end_time;
       guint elapsed_msec;
 
-      error = NULL;
       do_async = (n == 0);
 
       g_get_current_time (&start_time);
@@ -391,41 +373,25 @@ on_proxy_appeared (GDBusConnection *connection,
       data1.msec = 40;
       data1.num = 100;
       data1.async = do_async;
-      data1.done = FALSE;
-      thread1 = g_thread_create (test_sleep_in_thread_func,
-                                 &data1,
-                                 TRUE,
-                                 &error);
-      g_assert_no_error (error);
-      g_assert (thread1 != NULL);
+      thread1 = g_thread_new ("sleep",
+                              test_sleep_in_thread_func,
+                              &data1);
 
       data2.proxy = proxy;
       data2.msec = 20;
       data2.num = 200;
       data2.async = do_async;
-      data2.done = FALSE;
-      thread2 = g_thread_create (test_sleep_in_thread_func,
-                                 &data2,
-                                 TRUE,
-                                 &error);
-      g_assert_no_error (error);
-      g_assert (thread2 != NULL);
+      thread2 = g_thread_new ("sleep2",
+                              test_sleep_in_thread_func,
+                              &data2);
 
       data3.proxy = proxy;
       data3.msec = 100;
       data3.num = 40;
       data3.async = do_async;
-      data3.done = FALSE;
-      thread3 = g_thread_create (test_sleep_in_thread_func,
-                                 &data3,
-                                 TRUE,
-                                 &error);
-      g_assert_no_error (error);
-      g_assert (thread3 != NULL);
-
-      /* we handle messages in the main loop - threads will quit it when they are done */
-      while (!(data1.done && data2.done && data3.done))
-        g_main_loop_run (loop);
+      thread3 = g_thread_new ("sleep3",
+                              test_sleep_in_thread_func,
+                              &data3);
 
       g_thread_join (thread1);
       g_thread_join (thread2);
@@ -438,43 +404,177 @@ on_proxy_appeared (GDBusConnection *connection,
 
       //g_debug ("Elapsed time for %s = %d msec", n == 0 ? "async" : "sync", elapsed_msec);
 
-      /* elapsed_msec should be 4000 msec + change for overhead */
-      g_assert_cmpint (elapsed_msec, >=, 4000);
-      g_assert_cmpint (elapsed_msec,  <, 5000);
+      /* elapsed_msec should be 4000 msec +/- change for overhead/inaccuracy */
+      g_assert_cmpint (elapsed_msec, >=, 3950);
+      g_assert_cmpint (elapsed_msec,  <, 8000);
 
-      g_print (" ");
+      if (!g_test_quiet ())
+        g_print (" ");
     }
+}
 
-  g_main_loop_quit (loop);
+static void
+test_method_calls_in_thread (void)
+{
+  GDBusProxy *proxy;
+  GDBusConnection *connection;
+  GError *error;
+  gchar *name_owner;
+
+  error = NULL;
+  connection = g_bus_get_sync (G_BUS_TYPE_SESSION,
+                               NULL,
+                               &error);
+  g_assert_no_error (error);
+  error = NULL;
+  proxy = g_dbus_proxy_new_sync (connection,
+                                 G_DBUS_PROXY_FLAGS_NONE,
+                                 NULL,                      /* GDBusInterfaceInfo */
+                                 "com.example.TestService", /* name */
+                                 "/com/example/TestObject", /* object path */
+                                 "com.example.Frob",        /* interface */
+                                 NULL, /* GCancellable */
+                                 &error);
+  g_assert_no_error (error);
+
+  name_owner = g_dbus_proxy_get_name_owner (proxy);
+  g_assert_cmpstr (name_owner, !=, NULL);
+  g_free (name_owner);
+
+  test_method_calls_on_proxy (proxy);
+
+  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
-on_proxy_vanished (GDBusConnection *connection,
-                   const gchar     *name,
-                   gpointer         user_data)
+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_method_calls_in_thread (void)
+test_threaded_singleton (void)
 {
-  guint watcher_id;
-
-  watcher_id = g_bus_watch_proxy (G_BUS_TYPE_SESSION,
-                                  "com.example.TestService",
-                                  G_BUS_NAME_WATCHER_FLAGS_NONE,
-                                  "/com/example/TestObject",
-                                  "com.example.Frob",
-                                  G_TYPE_DBUS_PROXY,
-                                  G_DBUS_PROXY_FLAGS_NONE,
-                                  on_proxy_appeared,
-                                  on_proxy_vanished,
-                                  NULL,
-                                  NULL);
-
-  g_main_loop_run (loop);
-
-  g_bus_unwatch_proxy (watcher_id);
+  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);
 }
 
 /* ---------------------------------------------------------------------------------------------------- */
@@ -485,29 +585,16 @@ main (int   argc,
 {
   GError *error;
   gint ret;
+  gchar *path;
 
-  g_type_init ();
-  g_thread_init (NULL);
   g_test_init (&argc, &argv, NULL);
 
-  /* all the tests rely on a shared main loop */
-  loop = g_main_loop_new (NULL, FALSE);
-
-  /* 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 ("./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);
@@ -520,6 +607,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();