From 266e892a5e81ec6193fd5f4c45d15a8d916582a2 Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Sun, 25 Aug 2024 17:39:04 +0100 Subject: [PATCH 1/4] Revert "gcancellable: Mark assert-only variable as unused" This reverts commit 6c679fb37a0bfeed7beb42d1b1e3590713eac10c. Merge request !2765 has caused a thread safety regression in `GCancellableSource` disposal. It landed too late in the cycle to fix with any confidence before the 2.82 release, so the changes are being reverted for the `glib-2-82` branch and 2.82.0 release. This commit is not problematic, but is built entirely on top of the problematic MR, so has to be reverted. Fixes: #3448 Re-opens: #774, #2309, #2313 --- gio/gcancellable.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gio/gcancellable.c b/gio/gcancellable.c index 27dd06370..abf5878ed 100644 --- a/gio/gcancellable.c +++ b/gio/gcancellable.c @@ -664,7 +664,7 @@ cancellable_source_cancelled (GCancellable *cancellable, { GSource *source = user_data; GCancellableSource *cancellable_source = (GCancellableSource *) source; - gboolean callback_was_not_called G_GNUC_UNUSED; + gboolean callback_was_not_called; g_source_ref (source); g_source_set_ready_time (source, 0); From 36f08c0495840829ee0881c72a412153e80b0f42 Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Sun, 25 Aug 2024 17:39:30 +0100 Subject: [PATCH 2/4] Revert "gcancellable: Reference the object before locking when doing signal emission" This reverts commit 54d9c26b34d70be9a1e5519bf03d727334c52e0a. Merge request !2765 has caused a thread safety regression in `GCancellableSource` disposal. It landed too late in the cycle to fix with any confidence before the 2.82 release, so the changes are being reverted for the `glib-2-82` branch and 2.82.0 release. Fixes: #3448 Re-opens: #774, #2309, #2313 --- gio/gcancellable.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/gio/gcancellable.c b/gio/gcancellable.c index abf5878ed..227d56d2e 100644 --- a/gio/gcancellable.c +++ b/gio/gcancellable.c @@ -495,16 +495,11 @@ g_cancellable_cancel (GCancellable *cancellable) priv = cancellable->priv; - /* We add a reference before locking, to avoid that potential toggle - * notifications on the object might happen while we're locked. - */ - g_object_ref (cancellable); g_mutex_lock (&priv->mutex); if (!g_atomic_int_compare_and_exchange (&priv->cancelled, FALSE, TRUE)) { g_mutex_unlock (&priv->mutex); - g_object_unref (cancellable); return; } @@ -513,6 +508,7 @@ g_cancellable_cancel (GCancellable *cancellable) if (priv->wakeup) GLIB_PRIVATE_CALL (g_wakeup_signal) (priv->wakeup); + g_object_ref (cancellable); g_signal_emit (cancellable, signals[CANCELLED], 0); if (g_atomic_int_dec_and_test (&priv->cancelled_running)) From 5d66cf7cee58f1b03b7b81eca0581805cb62ea30 Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Sun, 25 Aug 2024 17:39:40 +0100 Subject: [PATCH 3/4] Revert "GCancellable: Use per-instance mutex logic instead of global critical sections" This reverts commit 3a07b2abd4006da33cdfb0916b266a21070a47d6. Merge request !2765 has caused a thread safety regression in `GCancellableSource` disposal. It landed too late in the cycle to fix with any confidence before the 2.82 release, so the changes are being reverted for the `glib-2-82` branch and 2.82.0 release. Fixes: #3448 Re-opens: #774, #2309, #2313 --- gio/gcancellable.c | 201 ++++++++++++++++++++++------------------ gio/tests/cancellable.c | 10 +- gio/tests/gmenumodel.c | 22 +++-- 3 files changed, 130 insertions(+), 103 deletions(-) diff --git a/gio/gcancellable.c b/gio/gcancellable.c index 227d56d2e..c3572910e 100644 --- a/gio/gcancellable.c +++ b/gio/gcancellable.c @@ -1,7 +1,6 @@ /* GIO - GLib Input, Output and Streaming Library * * Copyright (C) 2006-2007 Red Hat, Inc. - * Copyright (C) 2022-2024 Canonical, Ltd. * * SPDX-License-Identifier: LGPL-2.1-or-later * @@ -19,7 +18,6 @@ * Public License along with this library; if not, see . * * Author: Alexander Larsson - * Author: Marco Trevisan */ #include "config.h" @@ -47,12 +45,14 @@ enum { struct _GCancellablePrivate { - /* Atomic so that we don't require holding global mutexes for independent ops. */ + /* Atomic so that g_cancellable_is_cancelled does not require holding the mutex. */ gboolean cancelled; - int cancelled_running; + /* Access to fields below is protected by cancellable_mutex. */ + guint cancelled_running : 1; + guint cancelled_running_waiting : 1; + unsigned cancelled_emissions; + unsigned cancelled_emissions_waiting : 1; - /* Access to fields below is protected by cancellable's mutex. */ - GMutex mutex; guint fd_refcount; GWakeup *wakeup; }; @@ -62,6 +62,7 @@ static guint signals[LAST_SIGNAL] = { 0 }; G_DEFINE_TYPE_WITH_PRIVATE (GCancellable, g_cancellable, G_TYPE_OBJECT) static GPrivate current_cancellable; +static GMutex cancellable_mutex; static GCond cancellable_cond; static void @@ -69,15 +70,9 @@ g_cancellable_finalize (GObject *object) { GCancellable *cancellable = G_CANCELLABLE (object); - /* We're at finalization phase, so only one thread can be here. - * Thus there's no need to lock. In case something is locking us, then we've - * a bug, and g_mutex_clear() will make this clear aborting. - */ if (cancellable->priv->wakeup) GLIB_PRIVATE_CALL (g_wakeup_free) (cancellable->priv->wakeup); - g_mutex_clear (&cancellable->priv->mutex); - G_OBJECT_CLASS (g_cancellable_parent_class)->finalize (object); } @@ -159,8 +154,6 @@ static void g_cancellable_init (GCancellable *cancellable) { cancellable->priv = g_cancellable_get_instance_private (cancellable); - - g_mutex_init (&cancellable->priv->mutex); } /** @@ -272,17 +265,28 @@ g_cancellable_reset (GCancellable *cancellable) g_return_if_fail (G_IS_CANCELLABLE (cancellable)); + g_mutex_lock (&cancellable_mutex); + priv = cancellable->priv; - g_mutex_lock (&priv->mutex); + while (priv->cancelled_running || priv->cancelled_emissions > 0) + { + if (priv->cancelled_running) + priv->cancelled_running_waiting = TRUE; - if (g_atomic_int_compare_and_exchange (&priv->cancelled, TRUE, FALSE)) + if (priv->cancelled_emissions > 0) + priv->cancelled_emissions_waiting = TRUE; + + g_cond_wait (&cancellable_cond, &cancellable_mutex); + } + + if (g_atomic_int_exchange (&priv->cancelled, FALSE)) { if (priv->wakeup) GLIB_PRIVATE_CALL (g_wakeup_acknowledge) (priv->wakeup); } - g_mutex_unlock (&priv->mutex); + g_mutex_unlock (&cancellable_mutex); } /** @@ -400,29 +404,26 @@ g_cancellable_get_fd (GCancellable *cancellable) gboolean g_cancellable_make_pollfd (GCancellable *cancellable, GPollFD *pollfd) { - GCancellablePrivate *priv; - g_return_val_if_fail (pollfd != NULL, FALSE); if (cancellable == NULL) return FALSE; g_return_val_if_fail (G_IS_CANCELLABLE (cancellable), FALSE); - priv = cancellable->priv; + g_mutex_lock (&cancellable_mutex); - g_mutex_lock (&priv->mutex); + cancellable->priv->fd_refcount++; - if ((priv->fd_refcount++) == 0) + if (cancellable->priv->wakeup == NULL) { - priv->wakeup = GLIB_PRIVATE_CALL (g_wakeup_new) (); + cancellable->priv->wakeup = GLIB_PRIVATE_CALL (g_wakeup_new) (); - if (g_atomic_int_get (&priv->cancelled)) - GLIB_PRIVATE_CALL (g_wakeup_signal) (priv->wakeup); + if (g_atomic_int_get (&cancellable->priv->cancelled)) + GLIB_PRIVATE_CALL (g_wakeup_signal) (cancellable->priv->wakeup); } - g_assert (priv->wakeup); - GLIB_PRIVATE_CALL (g_wakeup_get_pollfd) (priv->wakeup, pollfd); + GLIB_PRIVATE_CALL (g_wakeup_get_pollfd) (cancellable->priv->wakeup, pollfd); - g_mutex_unlock (&priv->mutex); + g_mutex_unlock (&cancellable_mutex); return TRUE; } @@ -446,22 +447,26 @@ g_cancellable_make_pollfd (GCancellable *cancellable, GPollFD *pollfd) void g_cancellable_release_fd (GCancellable *cancellable) { + GCancellablePrivate *priv; + if (cancellable == NULL) return; g_return_if_fail (G_IS_CANCELLABLE (cancellable)); - g_mutex_lock (&cancellable->priv->mutex); + priv = cancellable->priv; - g_assert (cancellable->priv->fd_refcount > 0); + g_mutex_lock (&cancellable_mutex); + g_assert (priv->fd_refcount > 0); - if ((cancellable->priv->fd_refcount--) == 1) + priv->fd_refcount--; + if (priv->fd_refcount == 0) { - GLIB_PRIVATE_CALL (g_wakeup_free) (cancellable->priv->wakeup); - cancellable->priv->wakeup = NULL; + GLIB_PRIVATE_CALL (g_wakeup_free) (priv->wakeup); + priv->wakeup = NULL; } - g_mutex_unlock (&cancellable->priv->mutex); + g_mutex_unlock (&cancellable_mutex); } /** @@ -490,31 +495,37 @@ g_cancellable_cancel (GCancellable *cancellable) { GCancellablePrivate *priv; - if (cancellable == NULL || g_atomic_int_get (&cancellable->priv->cancelled)) + if (cancellable == NULL || g_cancellable_is_cancelled (cancellable)) return; priv = cancellable->priv; - g_mutex_lock (&priv->mutex); + g_mutex_lock (&cancellable_mutex); - if (!g_atomic_int_compare_and_exchange (&priv->cancelled, FALSE, TRUE)) + if (g_atomic_int_exchange (&priv->cancelled, TRUE)) { - g_mutex_unlock (&priv->mutex); + g_mutex_unlock (&cancellable_mutex); return; } - g_atomic_int_inc (&priv->cancelled_running); + priv->cancelled_running = TRUE; if (priv->wakeup) GLIB_PRIVATE_CALL (g_wakeup_signal) (priv->wakeup); + g_mutex_unlock (&cancellable_mutex); + g_object_ref (cancellable); g_signal_emit (cancellable, signals[CANCELLED], 0); - if (g_atomic_int_dec_and_test (&priv->cancelled_running)) - g_cond_broadcast (&cancellable_cond); + g_mutex_lock (&cancellable_mutex); - g_mutex_unlock (&priv->mutex); + priv->cancelled_running = FALSE; + if (priv->cancelled_running_waiting) + g_cond_broadcast (&cancellable_cond); + priv->cancelled_running_waiting = FALSE; + + g_mutex_unlock (&cancellable_mutex); g_object_unref (cancellable); } @@ -562,7 +573,7 @@ g_cancellable_connect (GCancellable *cancellable, g_return_val_if_fail (G_IS_CANCELLABLE (cancellable), 0); - g_mutex_lock (&cancellable->priv->mutex); + g_mutex_lock (&cancellable_mutex); if (g_atomic_int_get (&cancellable->priv->cancelled)) { @@ -572,10 +583,23 @@ g_cancellable_connect (GCancellable *cancellable, _callback = (void *)callback; id = 0; + cancellable->priv->cancelled_emissions++; + + g_mutex_unlock (&cancellable_mutex); + _callback (cancellable, data); if (data_destroy_func) data_destroy_func (data); + + g_mutex_lock (&cancellable_mutex); + + if (cancellable->priv->cancelled_emissions_waiting) + g_cond_broadcast (&cancellable_cond); + + cancellable->priv->cancelled_emissions--; + + g_mutex_unlock (&cancellable_mutex); } else { @@ -583,9 +607,10 @@ g_cancellable_connect (GCancellable *cancellable, callback, data, (GClosureNotify) data_destroy_func, G_CONNECT_DEFAULT); + + g_mutex_unlock (&cancellable_mutex); } - g_mutex_unlock (&cancellable->priv->mutex); return id; } @@ -621,26 +646,33 @@ g_cancellable_disconnect (GCancellable *cancellable, if (handler_id == 0 || cancellable == NULL) return; + g_mutex_lock (&cancellable_mutex); + priv = cancellable->priv; - g_mutex_lock (&priv->mutex); + while (priv->cancelled_running || priv->cancelled_emissions) + { + if (priv->cancelled_running) + priv->cancelled_running_waiting = TRUE; - while (g_atomic_int_get (&priv->cancelled_running) != 0) - g_cond_wait (&cancellable_cond, &priv->mutex); + if (priv->cancelled_emissions) + priv->cancelled_emissions_waiting = TRUE; - g_mutex_unlock (&priv->mutex); + g_cond_wait (&cancellable_cond, &cancellable_mutex); + } g_signal_handler_disconnect (cancellable, handler_id); + + g_mutex_unlock (&cancellable_mutex); } typedef struct { GSource source; - /* Atomic: */ GCancellable *cancellable; gulong cancelled_handler; - /* Atomic: */ - gboolean cancelled_callback_called; + /* Protected by cancellable_mutex: */ + gboolean resurrected_during_cancellation; } GCancellableSource; /* @@ -660,35 +692,27 @@ cancellable_source_cancelled (GCancellable *cancellable, { GSource *source = user_data; GCancellableSource *cancellable_source = (GCancellableSource *) source; - gboolean callback_was_not_called; + + g_mutex_lock (&cancellable_mutex); + + /* Drop the reference added in cancellable_source_dispose(); see the comment there. + * The reference must be dropped after unlocking @cancellable_mutex since + * it could be the final reference, and the dispose function takes + * @cancellable_mutex. */ + if (cancellable_source->resurrected_during_cancellation) + { + cancellable_source->resurrected_during_cancellation = FALSE; + g_mutex_unlock (&cancellable_mutex); + g_source_unref (source); + return; + } g_source_ref (source); + g_mutex_unlock (&cancellable_mutex); g_source_set_ready_time (source, 0); - - callback_was_not_called = g_atomic_int_compare_and_exchange ( - &cancellable_source->cancelled_callback_called, FALSE, TRUE); - g_assert (callback_was_not_called); - g_source_unref (source); } -static gboolean -cancellable_source_prepare (GSource *source, - gint *timeout) -{ - GCancellableSource *cancellable_source = (GCancellableSource *) source; - GCancellable *cancellable; - - if (timeout) - *timeout = -1; - - cancellable = g_atomic_pointer_get (&cancellable_source->cancellable); - if (cancellable && !g_atomic_int_get (&cancellable->priv->cancelled_running)) - g_atomic_int_set (&cancellable_source->cancelled_callback_called, FALSE); - - return FALSE; -} - static gboolean cancellable_source_dispatch (GSource *source, GSourceFunc callback, @@ -705,13 +729,12 @@ static void cancellable_source_dispose (GSource *source) { GCancellableSource *cancellable_source = (GCancellableSource *)source; - GCancellable *cancellable; - cancellable = g_atomic_pointer_exchange (&cancellable_source->cancellable, NULL); + g_mutex_lock (&cancellable_mutex); - if (cancellable) + if (cancellable_source->cancellable) { - if (g_atomic_int_get (&cancellable->priv->cancelled_running)) + if (cancellable_source->cancellable->priv->cancelled_running) { /* There can be a race here: if thread A has called * g_cancellable_cancel() and has got as far as committing to call @@ -723,21 +746,21 @@ cancellable_source_dispose (GSource *source) * will then be left in a state where it’s committed to using a * dangling GCancellableSource pointer. * - * Eliminate that race by waiting to ensure that our cancelled - * callback has been called, keeping a temporary ref, so that - * there's no risk that we're unreffing something that is still - * going to be used. + * Eliminate that race by resurrecting the #GSource temporarily, and + * then dropping that reference in cancellable_source_cancelled(), + * which should be guaranteed to fire because we’re inside a + * @cancelled_running block. */ - g_source_ref (source); - while (!g_atomic_int_get (&cancellable_source->cancelled_callback_called)) - ; - g_source_unref (source); + cancellable_source->resurrected_during_cancellation = TRUE; } - g_clear_signal_handler (&cancellable_source->cancelled_handler, cancellable); - g_object_unref (cancellable); + g_clear_signal_handler (&cancellable_source->cancelled_handler, + cancellable_source->cancellable); + g_clear_object (&cancellable_source->cancellable); } + + g_mutex_unlock (&cancellable_mutex); } static gboolean @@ -766,7 +789,7 @@ cancellable_source_closure_callback (GCancellable *cancellable, static GSourceFuncs cancellable_source_funcs = { - cancellable_source_prepare, + NULL, NULL, cancellable_source_dispatch, NULL, diff --git a/gio/tests/cancellable.c b/gio/tests/cancellable.c index 5379812d7..aaf992792 100644 --- a/gio/tests/cancellable.c +++ b/gio/tests/cancellable.c @@ -261,6 +261,11 @@ threaded_dispose_thread_cb (gpointer user_data) static void test_cancellable_source_threaded_dispose (void) { +#ifdef _GLIB_ADDRESS_SANITIZER + g_test_incomplete ("FIXME: Leaks lots of GCancellableSource objects, see glib#2309"); + (void) cancelled_cb; + (void) threaded_dispose_thread_cb; +#else ThreadedDisposeData data; GThread *thread = NULL; guint i; @@ -270,10 +275,6 @@ test_cancellable_source_threaded_dispose (void) "(in one thread) and cancelling the GCancellable it refers " "to (in another thread)"); g_test_bug ("https://gitlab.gnome.org/GNOME/glib/issues/1841"); -#ifdef _GLIB_ADDRESS_SANITIZER - g_test_message ("We also ensure that no GCancellableSource are leaked"); - g_test_bug ("https://gitlab.gnome.org/GNOME/glib/issues/2309"); -#endif /* Create a new thread and wait until it’s ready to execute. Each iteration of * the test will pass it a new #GCancellableSource. */ @@ -334,6 +335,7 @@ test_cancellable_source_threaded_dispose (void) g_cond_clear (&data.cond); g_ptr_array_unref (cancellables_pending_unref); +#endif } static void diff --git a/gio/tests/gmenumodel.c b/gio/tests/gmenumodel.c index 75f5d85f9..22d1b4d61 100644 --- a/gio/tests/gmenumodel.c +++ b/gio/tests/gmenumodel.c @@ -1007,16 +1007,17 @@ test_dbus_roundtrip (void) static void test_dbus_peer_roundtrip (void) { - PeerConnection peer; - #ifdef _GLIB_ADDRESS_SANITIZER - g_test_message ("Ensure that no GCancellableSource are leaked"); - g_test_bug ("https://gitlab.gnome.org/GNOME/glib/issues/2313"); -#endif + g_test_incomplete ("FIXME: Leaks a GCancellableSource, see glib#2313"); + (void) peer_connection_up; + (void) peer_connection_down; +#else + PeerConnection peer; peer_connection_up (&peer); do_roundtrip (peer.server_connection, peer.client_connection); peer_connection_down (&peer); +#endif } static gint items_changed_count; @@ -1145,16 +1146,17 @@ test_dbus_subscriptions (void) static void test_dbus_peer_subscriptions (void) { - PeerConnection peer; - #ifdef _GLIB_ADDRESS_SANITIZER - g_test_message ("Ensure that no GCancellableSource are leaked"); - g_test_bug ("https://gitlab.gnome.org/GNOME/glib/issues/2313"); -#endif + g_test_incomplete ("FIXME: Leaks a GCancellableSource, see glib#2313"); + (void) peer_connection_up; + (void) peer_connection_down; +#else + PeerConnection peer; peer_connection_up (&peer); do_subscriptions (peer.server_connection, peer.client_connection); peer_connection_down (&peer); +#endif } static void From f9668b92e4ca2b3623b7b4af8ba78df546f7eadc Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Sun, 25 Aug 2024 17:39:50 +0100 Subject: [PATCH 4/4] Revert "gio/tests: Ensure that a GCancellableSource can be used muliple times" This reverts commit 0a8cb10f224ff2dfed14f6500e77cee68ae7d4c4. Merge request !2765 has caused a thread safety regression in `GCancellableSource` disposal. It landed too late in the cycle to fix with any confidence before the 2.82 release, so the changes are being reverted for the `glib-2-82` branch and 2.82.0 release. Fixes: #3448 Re-opens: #774, #2309, #2313 --- gio/gcancellable.c | 8 ++---- gio/tests/cancellable.c | 64 ----------------------------------------- 2 files changed, 3 insertions(+), 69 deletions(-) diff --git a/gio/gcancellable.c b/gio/gcancellable.c index c3572910e..41ffab322 100644 --- a/gio/gcancellable.c +++ b/gio/gcancellable.c @@ -541,11 +541,9 @@ g_cancellable_cancel (GCancellable *cancellable) * signal. Also handles the race condition that may happen * if the cancellable is cancelled right before connecting. * - * @callback is called exactly once each time @cancellable is cancelled, - * either directly at the time of the connect if @cancellable is already - * cancelled, or when @cancellable is cancelled in some thread. - * In case the cancellable is reset via [method@Gio.Cancellable.reset] - * then the callback can be called again if the @cancellable is cancelled. + * @callback is called at most once, either directly at the + * time of the connect if @cancellable is already cancelled, + * or when @cancellable is cancelled in some thread. * * @data_destroy_func will be called when the handler is * disconnected, or immediately if the cancellable is already diff --git a/gio/tests/cancellable.c b/gio/tests/cancellable.c index aaf992792..b7be395bc 100644 --- a/gio/tests/cancellable.c +++ b/gio/tests/cancellable.c @@ -810,69 +810,6 @@ test_cancellable_cancel_reset_connect_races (void) g_object_unref (cancellable); } -static gboolean -source_cancelled_counter_cb (GCancellable *cancellable, - gpointer user_data) -{ - guint *n_calls = user_data; - - *n_calls = *n_calls + 1; - - return G_SOURCE_CONTINUE; -} - -static void -do_nothing (G_GNUC_UNUSED void *user_data) -{ - /* An empty timeout/idle once callback function */ -} - -static void -test_cancellable_source_can_be_fired_multiple_times (void) -{ - GCancellable *cancellable; - GSource *source; - guint n_calls = 0; - - g_test_summary ("Test a cancellable source callback can be called multiple times"); - g_test_bug ("https://gitlab.gnome.org/GNOME/glib/issues/774"); - - cancellable = g_cancellable_new (); - source = g_cancellable_source_new (cancellable); - - g_source_set_callback (source, G_SOURCE_FUNC (source_cancelled_counter_cb), - &n_calls, NULL); - g_source_attach (source, NULL); - - g_cancellable_cancel (cancellable); - g_assert_cmpuint (n_calls, ==, 0); - - while (g_main_context_pending (NULL)) - g_main_context_iteration (NULL, TRUE); - - g_assert_cmpuint (n_calls, ==, 1); - - g_cancellable_cancel (cancellable); - - g_timeout_add_once (100, do_nothing, NULL); - while (g_main_context_pending (NULL)) - g_main_context_iteration (NULL, TRUE); - - g_assert_cmpuint (n_calls, ==, 1); - - g_cancellable_reset (cancellable); - g_cancellable_cancel (cancellable); - g_assert_cmpuint (n_calls, ==, 1); - - while (g_main_context_pending (NULL)) - g_main_context_iteration (NULL, TRUE); - - g_assert_cmpuint (n_calls, ==, 2); - - g_source_unref (source); - g_object_unref (cancellable); -} - int main (int argc, char *argv[]) { @@ -888,7 +825,6 @@ main (int argc, char *argv[]) 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); - g_test_add_func ("/cancellable-source/can-be-fired-multiple-times", test_cancellable_source_can_be_fired_multiple_times); return g_test_run (); }