diff options
Diffstat (limited to 'gio/tests/cancellable.c')
-rw-r--r-- | gio/tests/cancellable.c | 459 |
1 files changed, 459 insertions, 0 deletions
diff --git a/gio/tests/cancellable.c b/gio/tests/cancellable.c index 278d2752e..be7017dcb 100644 --- a/gio/tests/cancellable.c +++ b/gio/tests/cancellable.c @@ -338,6 +338,458 @@ 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; + gboolean is_resetting; + gpointer handler_id; +} ConnectingThreadData; + +static void +on_cancellable_connect_disconnect (GCancellable *cancellable, + ConnectingThreadData *data) +{ + gulong handler_id = (gulong) (guintptr) 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, (gpointer) (guintptr) 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) (guintptr) 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) (guintptr) 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) (guintptr) 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); +} + +static void +on_cancelled_reset (GCancellable *cancellable, + gpointer data) +{ + ConnectingThreadData *thread_data = data; + + g_assert_true (g_cancellable_is_cancelled (cancellable)); + g_atomic_int_set (&thread_data->is_resetting, TRUE); + g_cancellable_reset (cancellable); + g_assert_false (g_cancellable_is_cancelled (cancellable)); + g_atomic_int_set (&thread_data->is_resetting, TRUE); +} + +static void +test_cancellable_reset_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 reset 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 resetting a " + "cancellable from its callback"); + return; + } + + cancellable = g_cancellable_new (); + thread_data.cancellable = cancellable; + thread_data.callback = G_CALLBACK (on_cancelled_reset); + + g_assert_false (g_atomic_int_get (&thread_data.is_resetting)); + g_assert_cmpuint ((gulong) (guintptr) g_atomic_pointer_get (&thread_data.handler_id), ==, 0); + + thread = g_thread_new ("/cancellable/reset-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) (guintptr) 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/reset-on-cancelled-callback-hangs", + (GThreadFunc) g_cancellable_cancel, + cancellable); + + while (!g_cancellable_is_cancelled (cancellable) || + !g_atomic_int_get (&thread_data.is_resetting)) + ; + + g_assert_true (g_atomic_int_get (&thread_data.is_resetting)); + g_assert_cmpuint ((gulong) (guintptr) 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_resetting)); + + g_main_loop_quit (thread_loop); + g_assert_true (g_atomic_int_get (&thread_data.is_resetting)); + + g_thread_join (g_steal_pointer (&thread)); + g_thread_unref (cancelling_thread); + g_object_unref (cancellable); +} + +static gpointer +repeatedly_cancelling_thread (gpointer data) +{ + GCancellable *cancellable = data; + const guint iterations = 10000; + + for (guint i = 0; i < iterations; ++i) + g_cancellable_cancel (cancellable); + + return NULL; +} + +static gpointer +repeatedly_resetting_thread (gpointer data) +{ + GCancellable *cancellable = data; + const guint iterations = 10000; + + for (guint i = 0; i < iterations; ++i) + g_cancellable_reset (cancellable); + + return NULL; +} + +static void +on_racy_cancellable_cancelled (GCancellable *cancellable, + gpointer data) +{ + gboolean *callback_called = data; + + g_assert_true (g_cancellable_is_cancelled (cancellable)); + g_atomic_int_set (callback_called, TRUE); +} + +static void +test_cancellable_cancel_reset_races (void) +{ + GCancellable *cancellable; + GThread *resetting_thread = NULL; + GThread *cancelling_thread = NULL; + gboolean callback_called = FALSE; + + g_test_summary ("Tests threads racing for cancelling and resetting a GCancellable"); + + cancellable = g_cancellable_new (); + + g_cancellable_connect (cancellable, G_CALLBACK (on_racy_cancellable_cancelled), + &callback_called, NULL); + g_assert_false (callback_called); + + resetting_thread = g_thread_new ("/cancellable/cancel-reset-races/resetting", + repeatedly_resetting_thread, + cancellable); + cancelling_thread = g_thread_new ("/cancellable/cancel-reset-races/cancelling", + repeatedly_cancelling_thread, cancellable); + + g_thread_join (g_steal_pointer (&cancelling_thread)); + g_thread_join (g_steal_pointer (&resetting_thread)); + + g_assert_true (callback_called); + + g_object_unref (cancellable); +} + +static gpointer +repeatedly_connecting_thread (gpointer data) +{ + GCancellable *cancellable = data; + const guint iterations = 10000; + gboolean callback_ever_called = FALSE; + + for (guint i = 0; i < iterations; ++i) + { + gboolean callback_called = FALSE; + gboolean called; + gulong id = g_cancellable_connect (cancellable, + G_CALLBACK (on_racy_cancellable_cancelled), + &callback_called, NULL); + called = g_atomic_int_get (&callback_called); + callback_ever_called |= called; + if (g_test_verbose () && called) + g_test_message ("Reconnecting cancellation callback called"); + g_cancellable_disconnect (cancellable, id); + } + + if (!callback_ever_called) + g_test_incomplete ("We didn't really checked if callbacks is called properly"); + + return NULL; +} + +static void +test_cancellable_cancel_reset_connect_races (void) +{ + GCancellable *cancellable; + GThread *resetting_thread = NULL; + GThread *cancelling_thread = NULL; + GThread *connecting_thread = NULL; + gboolean callback_called = FALSE; + + g_test_summary ("Tests threads racing for cancelling, connecting and disconnecting " + " and resetting a GCancellable"); + + cancellable = g_cancellable_new (); + + g_cancellable_connect (cancellable, G_CALLBACK (on_racy_cancellable_cancelled), + &callback_called, NULL); + g_assert_false (callback_called); + + resetting_thread = g_thread_new ("/cancel-reset-connect-races/resetting", + repeatedly_resetting_thread, + cancellable); + cancelling_thread = g_thread_new ("/cancel-reset-connect-races/cancelling", + repeatedly_cancelling_thread, cancellable); + connecting_thread = g_thread_new ("/cancel-reset-connect-races/connecting", + repeatedly_connecting_thread, cancellable); + + g_thread_join (g_steal_pointer (&cancelling_thread)); + g_thread_join (g_steal_pointer (&resetting_thread)); + g_thread_join (g_steal_pointer (&connecting_thread)); + + g_assert_true (callback_called); + + g_object_unref (cancellable); +} + int main (int argc, char *argv[]) { @@ -345,6 +797,13 @@ 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/resets-on-cancel-callback-hangs", test_cancellable_reset_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/cancel-reset-races", test_cancellable_cancel_reset_races); + g_test_add_func ("/cancellable/cancel-reset-connect-races", test_cancellable_cancel_reset_connect_races); g_test_add_func ("/cancellable-source/threaded-dispose", test_cancellable_source_threaded_dispose); return g_test_run (); |