[glib: 1/10] gio/tests: Add tests for cancellable pollfd and cancellation callbacks




commit 62192925b6ea3597043d7771be58ba7a21a8407b
Author: Marco Trevisan (TreviƱo) <mail 3v1n0 net>
Date:   Mon Jun 20 20:53:26 2022 +0200

    gio/tests: Add tests for cancellable pollfd and cancellation callbacks

 gio/tests/cancellable.c | 244 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 244 insertions(+)
---
diff --git a/gio/tests/cancellable.c b/gio/tests/cancellable.c
index 278d2752e0..c51d13f297 100644
--- a/gio/tests/cancellable.c
+++ b/gio/tests/cancellable.c
@@ -338,6 +338,246 @@ test_cancellable_source_threaded_dispose (void)
 #endif
 }
 
+static void
+test_cancellable_poll_fd (void)
+{
+  GCancellable *cancellable;
+  GPollFD pollfd = {.fd = -1};
+  int fd = -1;
+
+#ifdef G_OS_WIN32
+  g_test_skip ("Platform not supported");
+  return;
+#endif
+
+  cancellable = g_cancellable_new ();
+
+  g_assert_true (g_cancellable_make_pollfd (cancellable, &pollfd));
+  g_assert_cmpint (pollfd.fd, >, 0);
+
+  fd = g_cancellable_get_fd (cancellable);
+  g_assert_cmpint (fd, >, 0);
+
+  g_cancellable_release_fd (cancellable);
+  g_cancellable_release_fd (cancellable);
+
+  g_object_unref (cancellable);
+}
+
+static void
+test_cancellable_cancelled_poll_fd (void)
+{
+  GCancellable *cancellable;
+  GPollFD pollfd;
+
+#ifdef G_OS_WIN32
+  g_test_skip ("Platform not supported");
+  return;
+#endif
+
+  g_test_summary ("Tests that cancellation wakes up a pollable FD on creation");
+
+  cancellable = g_cancellable_new ();
+  g_assert_true (g_cancellable_make_pollfd (cancellable, &pollfd));
+  g_cancellable_cancel (cancellable);
+
+  g_poll (&pollfd, 1, -1);
+
+  g_cancellable_release_fd (cancellable);
+  g_object_unref (cancellable);
+}
+
+typedef struct {
+  GCancellable *cancellable;
+  gboolean polling_started; /* Atomic */
+} CancellablePollThreadData;
+
+static gpointer
+cancel_cancellable_thread (gpointer user_data)
+{
+  CancellablePollThreadData *thread_data = user_data;
+
+  while (!g_atomic_int_get (&thread_data->polling_started))
+    ;
+
+  /* Let's just wait a moment before cancelling, this is not really needed
+   * but we do it to simulate that the thread is actually doing something.
+   */
+  g_usleep (G_USEC_PER_SEC / 10);
+  g_cancellable_cancel (thread_data->cancellable);
+
+  return NULL;
+}
+
+static gpointer
+polling_cancelled_cancellable_thread (gpointer user_data)
+{
+  CancellablePollThreadData *thread_data = user_data;
+  GPollFD pollfd;
+
+  g_assert_true (g_cancellable_make_pollfd (thread_data->cancellable, &pollfd));
+  g_atomic_int_set (&thread_data->polling_started, TRUE);
+
+  g_poll (&pollfd, 1, -1);
+
+  g_cancellable_release_fd (thread_data->cancellable);
+
+  return NULL;
+}
+
+static void
+test_cancellable_cancelled_poll_fd_threaded (void)
+{
+  GCancellable *cancellable;
+  CancellablePollThreadData thread_data = {0};
+  GThread *polling_thread = NULL;
+  GThread *cancelling_thread = NULL;
+  GPollFD pollfd;
+
+#ifdef G_OS_WIN32
+  g_test_skip ("Platform not supported");
+  return;
+#endif
+
+  g_test_summary ("Tests that a cancellation wakes up a pollable FD");
+
+  cancellable = g_cancellable_new ();
+  g_assert_true (g_cancellable_make_pollfd (cancellable, &pollfd));
+
+  thread_data.cancellable = cancellable;
+
+  polling_thread = g_thread_new ("/cancellable/poll-fd-cancelled-threaded/polling",
+                                 polling_cancelled_cancellable_thread,
+                                 &thread_data);
+  cancelling_thread = g_thread_new ("/cancellable/poll-fd-cancelled-threaded/cancelling",
+                                    cancel_cancellable_thread, &thread_data);
+
+  g_poll (&pollfd, 1, -1);
+  g_assert_true (g_cancellable_is_cancelled (cancellable));
+  g_cancellable_release_fd (cancellable);
+
+  g_thread_join (g_steal_pointer (&cancelling_thread));
+  g_thread_join (g_steal_pointer (&polling_thread));
+
+  g_object_unref (cancellable);
+}
+
+typedef struct {
+  GMainLoop *loop;
+  GCancellable *cancellable;
+  GCallback callback;
+  gboolean is_disconnecting;
+  gulong handler_id;
+} ConnectingThreadData;
+
+static void
+on_cancellable_connect_disconnect (GCancellable *cancellable,
+                                   ConnectingThreadData *data)
+{
+  gulong handler_id = (gulong) g_atomic_pointer_exchange (&data->handler_id, 0);
+  g_atomic_int_set (&data->is_disconnecting, TRUE);
+  g_cancellable_disconnect (cancellable, handler_id);
+  g_atomic_int_set (&data->is_disconnecting, FALSE);
+}
+
+static gpointer
+connecting_thread (gpointer user_data)
+{
+  GMainContext *context;
+  ConnectingThreadData *data = user_data;
+  gulong handler_id;
+  GMainLoop *loop;
+
+  handler_id =
+    g_cancellable_connect (data->cancellable, data->callback, data, NULL);
+
+  context = g_main_context_new ();
+  g_main_context_push_thread_default (context);
+  loop = g_main_loop_new (context, FALSE);
+
+  g_atomic_pointer_set (&data->handler_id, handler_id);
+  g_atomic_pointer_set (&data->loop, loop);
+  g_main_loop_run (loop);
+
+  g_main_context_pop_thread_default (context);
+  g_main_context_unref (context);
+  g_main_loop_unref (loop);
+
+  return NULL;
+}
+
+static void
+test_cancellable_disconnect_on_cancelled_callback_hangs (void)
+{
+  GCancellable *cancellable;
+  GThread *thread = NULL;
+  GThread *cancelling_thread = NULL;
+  ConnectingThreadData thread_data = {0};
+  GMainLoop *thread_loop;
+  gpointer waited;
+
+  /* While this is not convenient, it's done to ensure that we don't have a
+   * race when trying to cancelling a cancellable that is about to be cancelled
+   * in another thread
+   */
+  g_test_summary ("Tests that trying to disconnect a cancellable from the "
+                  "cancelled signal callback will result in a deadlock "
+                  "as per #GCancellable::cancelled");
+
+  if (!g_test_undefined ())
+    {
+      g_test_skip ("Skipping testing disallowed behaviour of disconnecting from "
+                  "a cancellable from its cancelled callback");
+      return;
+    }
+
+  cancellable = g_cancellable_new ();
+  thread_data.cancellable = cancellable;
+  thread_data.callback = G_CALLBACK (on_cancellable_connect_disconnect);
+
+  g_assert_false (g_atomic_int_get (&thread_data.is_disconnecting));
+  g_assert_cmpuint ((gulong) g_atomic_pointer_get (&thread_data.handler_id), ==, 0);
+
+  thread = g_thread_new ("/cancellable/disconnect-on-cancelled-callback-hangs",
+                         connecting_thread, &thread_data);
+
+  while (!g_atomic_pointer_get (&thread_data.loop))
+    ;
+
+  thread_loop = thread_data.loop;
+  g_assert_cmpuint ((gulong) g_atomic_pointer_get (&thread_data.handler_id), !=, 0);
+
+  /* FIXME: This thread will hang (at least that's what this test wants to
+   *        ensure), but we can't stop it from the caller, unless we'll expose
+   *        pthread_cancel (and similar) to GLib.
+   *        So it will keep hanging till the test process is alive.
+   */
+  cancelling_thread = g_thread_new ("/cancellable/disconnect-on-cancelled-callback-hangs",
+                                    (GThreadFunc) g_cancellable_cancel,
+                                    cancellable);
+
+  while (!g_cancellable_is_cancelled (cancellable) ||
+         !g_atomic_int_get (&thread_data.is_disconnecting))
+    ;
+
+  g_assert_true (g_atomic_int_get (&thread_data.is_disconnecting));
+  g_assert_cmpuint ((gulong) g_atomic_pointer_get (&thread_data.handler_id), ==, 0);
+
+  waited = &waited;
+  g_timeout_add_once (100, (GSourceOnceFunc) g_nullify_pointer, &waited);
+  while (waited != NULL)
+    g_main_context_iteration (NULL, TRUE);
+
+  g_assert_true (g_atomic_int_get (&thread_data.is_disconnecting));
+
+  g_main_loop_quit (thread_loop);
+  g_assert_true (g_atomic_int_get (&thread_data.is_disconnecting));
+
+  g_thread_join (g_steal_pointer (&thread));
+  g_thread_unref (cancelling_thread);
+  g_object_unref (cancellable);
+}
+
 int
 main (int argc, char *argv[])
 {
@@ -345,6 +585,10 @@ main (int argc, char *argv[])
 
   g_test_add_func ("/cancellable/multiple-concurrent", test_cancel_multiple_concurrent);
   g_test_add_func ("/cancellable/null", test_cancel_null);
+  g_test_add_func ("/cancellable/disconnect-on-cancelled-callback-hangs", 
test_cancellable_disconnect_on_cancelled_callback_hangs);
+  g_test_add_func ("/cancellable/poll-fd", test_cancellable_poll_fd);
+  g_test_add_func ("/cancellable/poll-fd-cancelled", test_cancellable_cancelled_poll_fd);
+  g_test_add_func ("/cancellable/poll-fd-cancelled-threaded", test_cancellable_cancelled_poll_fd_threaded);
   g_test_add_func ("/cancellable-source/threaded-dispose", test_cancellable_source_threaded_dispose);
 
   return g_test_run ();


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