From 12571a08212e96fef079704453091a0fc317a2a9 Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Tue, 24 May 2022 17:07:04 +0100 Subject: [PATCH 1/3] Add one-shot idle and timeout functions Many idle and timeout sources are installed as "one shot": called once and immediately removed. While it's easy to write a simple callback that returns G_SOURCE_REMOVE, it would also be useful to have some sort of "visual" marker when reading the code; a way to immediately see that a callback (which may be defined elsewhere in the code) is meant to be invoked just once. Includes additional unit tests by Philip Withnall. --- docs/reference/glib/glib-sections.txt | 3 + glib/gmain.c | 118 ++++++++++++++++++++++++++ glib/gmain.h | 20 +++++ glib/tests/mainloop.c | 69 +++++++++++++++ 4 files changed, 210 insertions(+) diff --git a/docs/reference/glib/glib-sections.txt b/docs/reference/glib/glib-sections.txt index bacf38684..038a97ede 100644 --- a/docs/reference/glib/glib-sections.txt +++ b/docs/reference/glib/glib-sections.txt @@ -904,6 +904,7 @@ g_main_context_pop_thread_default g_timeout_source_new g_timeout_source_new_seconds g_timeout_add +g_timeout_add_once g_timeout_add_full g_timeout_add_seconds g_timeout_add_seconds_full @@ -911,6 +912,7 @@ g_timeout_add_seconds_full g_idle_source_new g_idle_add +g_idle_add_once g_idle_add_full g_idle_remove_by_data @@ -954,6 +956,7 @@ g_source_get_context g_source_set_callback GSourceFunc G_SOURCE_FUNC +GSourceOnceFunc g_source_set_callback_indirect g_source_set_ready_time g_source_get_ready_time diff --git a/glib/gmain.c b/glib/gmain.c index 74568b8a9..dd533c91d 100644 --- a/glib/gmain.c +++ b/glib/gmain.c @@ -5157,6 +5157,85 @@ g_timeout_add (guint32 interval, interval, function, data, NULL); } +typedef struct { + GSourceOnceFunc function; + gpointer data; +} OnceData; + +static void +once_data_free (gpointer data) +{ + g_free (data); +} + +static gboolean +once_function (gpointer data) +{ + OnceData *once_data = data; + + once_data->function (once_data->data); + + return G_SOURCE_REMOVE; +} + +/** + * g_timeout_add_once: + * @interval: the time after which the function will be called, in + * milliseconds (1/1000ths of a second) + * @function: function to call + * @data: data to pass to @function + * + * Sets a function to be called after @interval milliseconds have elapsed, + * with the default priority, %G_PRIORITY_DEFAULT. + * + * The given @function is called once. + * + * Note that timeout functions may be delayed, due to the processing of other + * event sources. Thus they should not be relied on for precise timing. + * + * See [memory management of sources][mainloop-memory-management] for details + * on how to handle the return value and memory management of @data. + * + * If you want to have a timer in the "seconds" range and do not care + * about the exact time of the first call of the timer, use the + * g_timeout_add_seconds() function; this function allows for more + * optimizations and more efficient system power usage. + * + * This internally creates a main loop source using g_timeout_source_new() + * and attaches it to the global #GMainContext using g_source_attach(), so + * the callback will be invoked in whichever thread is running that main + * context. You can do these steps manually if you need greater control or to + * use a custom main context. + * + * It is safe to call this function from any thread. + * + * The interval given is in terms of monotonic time, not wall clock + * time. See g_get_monotonic_time(). + * + * Returns: the ID (greater than 0) of the event source + * + * Since: 2.74 + */ +guint +g_timeout_add_once (guint32 interval, + GSourceOnceFunc function, + gpointer data) +{ + OnceData *once_data; + + g_return_val_if_fail (function != NULL, 0); + + once_data = g_new (OnceData, 1); + once_data->function = function; + once_data->data = data; + + return g_timeout_add_full (G_PRIORITY_DEFAULT, + interval, + once_function, + once_data, + once_data_free); +} + /** * g_timeout_add_seconds_full: (rename-to g_timeout_add_seconds) * @priority: the priority of the timeout source. Typically this will be in @@ -6048,6 +6127,45 @@ g_idle_add (GSourceFunc function, return g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, function, data, NULL); } +/** + * g_idle_add_once: + * @function: function to call + * @data: data to pass to @function + * + * Adds a function to be called whenever there are no higher priority + * events pending to the default main loop. The function is given the + * default idle priority, %G_PRIORITY_DEFAULT_IDLE. + * + * The function will only be called once. + * + * See [memory management of sources][mainloop-memory-management] for details + * on how to handle the return value and memory management of @data. + * + * This internally creates a main loop source using g_idle_source_new() + * and attaches it to the global #GMainContext using g_source_attach(), so + * the callback will be invoked in whichever thread is running that main + * context. You can do these steps manually if you need greater control or to + * use a custom main context. + * + * Returns: the ID (greater than 0) of the event source + * + * Since: 2.74 + */ +guint +g_idle_add_once (GSourceOnceFunc function, + gpointer data) +{ + OnceData *once_data; + + g_return_val_if_fail (function != NULL, 0); + + once_data = g_new (OnceData, 1); + once_data->function = function; + once_data->data = data; + + return g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, once_function, once_data, once_data_free); +} + /** * g_idle_remove_by_data: * @data: the data for the idle source's callback. diff --git a/glib/gmain.h b/glib/gmain.h index 2cfa043cc..aebf5cb23 100644 --- a/glib/gmain.h +++ b/glib/gmain.h @@ -194,6 +194,19 @@ typedef struct _GSourceFuncs GSourceFuncs; */ typedef gboolean (*GSourceFunc) (gpointer user_data); +/** + * GSourceOnceFunc: + * @user_data: data passed to the function, set when the source was + * created + * + * A source function that is only called once. + * + * See: g_idle_add_once(), g_timeout_add_once() + * + * Since: 2.74 + */ +typedef void (* GSourceOnceFunc) (gpointer user_data); + /** * G_SOURCE_FUNC: * @f: a function pointer. @@ -772,6 +785,10 @@ GLIB_AVAILABLE_IN_ALL guint g_timeout_add (guint interval, GSourceFunc function, gpointer data); +GLIB_AVAILABLE_IN_2_74 +guint g_timeout_add_once (guint interval, + GSourceOnceFunc function, + gpointer data); GLIB_AVAILABLE_IN_ALL guint g_timeout_add_seconds_full (gint priority, guint interval, @@ -800,6 +817,9 @@ guint g_idle_add_full (gint priority, GSourceFunc function, gpointer data, GDestroyNotify notify); +GLIB_AVAILABLE_IN_2_74 +guint g_idle_add_once (GSourceOnceFunc function, + gpointer data); GLIB_AVAILABLE_IN_ALL gboolean g_idle_remove_by_data (gpointer data); diff --git a/glib/tests/mainloop.c b/glib/tests/mainloop.c index 38eee475c..fd3cac1b9 100644 --- a/glib/tests/mainloop.c +++ b/glib/tests/mainloop.c @@ -2309,6 +2309,72 @@ test_maincontext_source_finalization_from_dispatch (gconstpointer user_data) g_main_context_unref (c); } +static void +once_cb (gpointer user_data) +{ + guint *counter = user_data; + + *counter = *counter + 1; +} + +static void +test_maincontext_idle_once (void) +{ + guint counter = 0; + guint source_id; + GSource *source; + + g_test_summary ("Test g_idle_add_once() works"); + + source_id = g_idle_add_once (once_cb, &counter); + source = g_main_context_find_source_by_id (NULL, source_id); + g_assert_nonnull (source); + g_source_ref (source); + + /* Iterating the main context should dispatch the source. */ + g_assert_cmpuint (counter, ==, 0); + g_main_context_iteration (NULL, FALSE); + g_assert_cmpuint (counter, ==, 1); + + /* Iterating it again should not dispatch the source again. */ + g_main_context_iteration (NULL, FALSE); + g_assert_cmpuint (counter, ==, 1); + g_assert_true (g_source_is_destroyed (source)); + + g_clear_pointer (&source, g_source_unref); +} + +static void +test_maincontext_timeout_once (void) +{ + guint counter = 0, check_counter = 0; + guint source_id; + GSource *source; + + g_test_summary ("Test g_timeout_add_once() works"); + + source_id = g_timeout_add_once (10 /* ms */, once_cb, &counter); + source = g_main_context_find_source_by_id (NULL, source_id); + g_assert_nonnull (source); + g_source_ref (source); + + /* Iterating the main context should dispatch the source, though we have to block. */ + g_assert_cmpuint (counter, ==, 0); + g_main_context_iteration (NULL, TRUE); + g_assert_cmpuint (counter, ==, 1); + + /* Iterating it again should not dispatch the source again. We add a second + * timeout and block until that is dispatched. Given the ordering guarantees, + * we should then know whether the first one would have re-dispatched by then. */ + g_timeout_add_once (30 /* ms */, once_cb, &check_counter); + g_main_context_iteration (NULL, TRUE); + g_assert_cmpuint (check_counter, ==, 1); + g_assert_cmpuint (counter, ==, 1); + g_assert_true (g_source_is_destroyed (source)); + + g_clear_pointer (&source, g_source_unref); +} + static void test_steal_fd (void) { @@ -2363,6 +2429,9 @@ main (int argc, char *argv[]) g_test_add_data_func (name, GINT_TO_POINTER (i), test_maincontext_source_finalization_from_dispatch); g_free (name); } + g_test_add_func ("/maincontext/idle-once", test_maincontext_idle_once); + g_test_add_func ("/maincontext/timeout-once", test_maincontext_timeout_once); + g_test_add_func ("/mainloop/basic", test_mainloop_basic); g_test_add_func ("/mainloop/timeouts", test_timeouts); g_test_add_func ("/mainloop/priorities", test_priorities); From 50c316ef36412ff6b3e55f381879645dd02ee3d9 Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Tue, 24 May 2022 17:10:32 +0100 Subject: [PATCH 2/3] Port various timeout/idle callbacks to the once API So we excercise it in our test coverage. --- glib/tests/mapping.c | 5 ++--- glib/tests/timeout.c | 8 +++----- glib/tests/utils.c | 7 +++---- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/glib/tests/mapping.c b/glib/tests/mapping.c index 2ad63d296..ea3495cd0 100644 --- a/glib/tests/mapping.c +++ b/glib/tests/mapping.c @@ -85,13 +85,12 @@ map_or_die (const gchar *filename, return map; } -static gboolean +static void signal_parent (gpointer data) { #ifndef G_OS_WIN32 kill (parent_pid, SIGUSR1); #endif - return G_SOURCE_REMOVE; } static void @@ -113,7 +112,7 @@ child_main (void) #endif loop = g_main_loop_new (NULL, FALSE); g_idle_add (check_stop, loop); - g_idle_add (signal_parent, NULL); + g_idle_add_once (signal_parent, NULL); g_main_loop_run (loop); g_test_message ("test_child_private: received parent signal"); diff --git a/glib/tests/timeout.c b/glib/tests/timeout.c index 9e4d047a6..acbb8f3e1 100644 --- a/glib/tests/timeout.c +++ b/glib/tests/timeout.c @@ -5,12 +5,10 @@ static GMainLoop *loop; -static gboolean +static void stop_waiting (gpointer data) { g_main_loop_quit (loop); - - return G_SOURCE_REMOVE; } static gboolean @@ -44,7 +42,7 @@ test_seconds (void) g_test_bug ("https://bugzilla.gnome.org/show_bug.cgi?id=642052"); loop = g_main_loop_new (NULL, FALSE); - g_timeout_add (2100, stop_waiting, NULL); + g_timeout_add_once (2100, stop_waiting, NULL); id = g_timeout_add_seconds (21475, unreachable_callback, NULL); g_main_loop_run (loop); @@ -81,7 +79,7 @@ test_weeks_overflow (void) g_test_bug ("https://gitlab.gnome.org/GNOME/glib/issues/1600"); loop = g_main_loop_new (NULL, FALSE); - g_timeout_add (2100, stop_waiting, NULL); + g_timeout_add_once (2100, stop_waiting, NULL); interval_seconds = 1 + G_MAXUINT / 1000; id = g_timeout_add_seconds (interval_seconds, unreachable_callback, NULL); diff --git a/glib/tests/utils.c b/glib/tests/utils.c index 11fed555a..6318e1916 100644 --- a/glib/tests/utils.c +++ b/glib/tests/utils.c @@ -767,11 +767,10 @@ test_os_info (void) g_free (contents); } -static gboolean +static void source_test (gpointer data) { g_assert_not_reached (); - return G_SOURCE_REMOVE; } static void @@ -779,13 +778,13 @@ test_clear_source (void) { guint id; - id = g_idle_add (source_test, NULL); + id = g_idle_add_once (source_test, NULL); g_assert_cmpuint (id, >, 0); g_clear_handle_id (&id, g_source_remove); g_assert_cmpuint (id, ==, 0); - id = g_timeout_add (100, source_test, NULL); + id = g_timeout_add_once (100, source_test, NULL); g_assert_cmpuint (id, >, 0); g_clear_handle_id (&id, g_source_remove); From c1f94cd1a55c5b031bfc008fe45ea4bf0ebcbbeb Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Fri, 27 May 2022 12:41:29 +0100 Subject: [PATCH 3/3] gmain: Minor documentation updates to idle-once and timeout-once funcs As suggested on !2684. Signed-off-by: Philip Withnall --- glib/gmain.c | 39 +++++++-------------------------------- glib/gmain.h | 3 ++- 2 files changed, 9 insertions(+), 33 deletions(-) diff --git a/glib/gmain.c b/glib/gmain.c index dd533c91d..cd92ed580 100644 --- a/glib/gmain.c +++ b/glib/gmain.c @@ -5184,34 +5184,15 @@ once_function (gpointer data) * milliseconds (1/1000ths of a second) * @function: function to call * @data: data to pass to @function - * + * * Sets a function to be called after @interval milliseconds have elapsed, * with the default priority, %G_PRIORITY_DEFAULT. * - * The given @function is called once. + * The given @function is called once and then the source will be automatically + * removed from the main context. * - * Note that timeout functions may be delayed, due to the processing of other - * event sources. Thus they should not be relied on for precise timing. + * This function otherwise behaves like g_timeout_add(). * - * See [memory management of sources][mainloop-memory-management] for details - * on how to handle the return value and memory management of @data. - * - * If you want to have a timer in the "seconds" range and do not care - * about the exact time of the first call of the timer, use the - * g_timeout_add_seconds() function; this function allows for more - * optimizations and more efficient system power usage. - * - * This internally creates a main loop source using g_timeout_source_new() - * and attaches it to the global #GMainContext using g_source_attach(), so - * the callback will be invoked in whichever thread is running that main - * context. You can do these steps manually if you need greater control or to - * use a custom main context. - * - * It is safe to call this function from any thread. - * - * The interval given is in terms of monotonic time, not wall clock - * time. See g_get_monotonic_time(). - * * Returns: the ID (greater than 0) of the event source * * Since: 2.74 @@ -6136,16 +6117,10 @@ g_idle_add (GSourceFunc function, * events pending to the default main loop. The function is given the * default idle priority, %G_PRIORITY_DEFAULT_IDLE. * - * The function will only be called once. + * The function will only be called once and then the source will be + * automatically removed from the main context. * - * See [memory management of sources][mainloop-memory-management] for details - * on how to handle the return value and memory management of @data. - * - * This internally creates a main loop source using g_idle_source_new() - * and attaches it to the global #GMainContext using g_source_attach(), so - * the callback will be invoked in whichever thread is running that main - * context. You can do these steps manually if you need greater control or to - * use a custom main context. + * This function otherwise behaves like g_idle_add(). * * Returns: the ID (greater than 0) of the event source * diff --git a/glib/gmain.h b/glib/gmain.h index aebf5cb23..ae3cc3ec5 100644 --- a/glib/gmain.h +++ b/glib/gmain.h @@ -199,7 +199,8 @@ typedef gboolean (*GSourceFunc) (gpointer user_data); * @user_data: data passed to the function, set when the source was * created * - * A source function that is only called once. + * A source function that is only called once before being removed from the main + * context automatically. * * See: g_idle_add_once(), g_timeout_add_once() *