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.
This commit is contained in:
Emmanuele Bassi 2022-05-24 17:07:04 +01:00 committed by Philip Withnall
parent 587b1559ae
commit 12571a0821
4 changed files with 210 additions and 0 deletions

View File

@ -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
<SUBSECTION>
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

View File

@ -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.

View File

@ -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);

View File

@ -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);