|
|
|
@@ -338,6 +338,396 @@ 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;
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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) 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) 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) 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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
|
main (int argc, char *argv[])
|
|
|
|
|
{
|
|
|
|
@@ -345,6 +735,12 @@ 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-source/threaded-dispose", test_cancellable_source_threaded_dispose);
|
|
|
|
|
|
|
|
|
|
return g_test_run ();
|
|
|
|
|