Merge branch 'wip/chergert/gpoll-for-gmain' into 'main'

Use ppoll() when possible for more precise timeouts

See merge request GNOME/glib!3958
This commit is contained in:
Philip Withnall 2024-03-22 00:17:21 +00:00
commit 1c5f5a6914
2 changed files with 137 additions and 56 deletions

View File

@ -69,6 +69,10 @@
#include <errno.h>
#include <string.h>
#ifdef HAVE_POLL_H
#include <poll.h>
#endif
#ifdef HAVE_PIDFD
#include <sys/syscall.h>
#include <sys/wait.h>
@ -189,7 +193,7 @@ struct _GMainContext
GHashTable *sources; /* guint -> GSource */
GPtrArray *pending_dispatches;
gint timeout; /* Timeout for current iteration */
gint64 timeout_usec; /* Timeout for current iteration */
guint next_id;
GQueue source_lists;
@ -322,7 +326,7 @@ static gboolean g_main_context_prepare_unlocked (GMainContext *context,
gint *priority);
static gint g_main_context_query_unlocked (GMainContext *context,
gint max_priority,
gint *timeout,
gint64 *timeout_usec,
GPollFD *fds,
gint n_fds);
static gboolean g_main_context_check_unlocked (GMainContext *context,
@ -331,7 +335,7 @@ static gboolean g_main_context_check_unlocked (GMainContext *context,
gint n_fds);
static void g_main_context_dispatch_unlocked (GMainContext *context);
static void g_main_context_poll_unlocked (GMainContext *context,
int timeout,
gint64 timeout_usec,
int priority,
GPollFD *fds,
int n_fds);
@ -3633,6 +3637,45 @@ g_main_context_prepare (GMainContext *context,
return ready;
}
static inline int
round_timeout_to_msec (gint64 timeout_usec)
{
/* We need to round to milliseconds from our internal microseconds for
* various external API and GPollFunc which requires milliseconds.
*
* However, we want to ensure a few invariants for this.
*
* Return == -1 if we have no timeout specified
* Return == 0 if we don't want to block at all
* Return > 0 if we have any timeout to avoid spinning the CPU
*
* This does cause jitter if the microsecond timeout is < 1000 usec
* because that is beyond our precision. However, using ppoll() instead
* of poll() (when available) avoids this jitter.
*/
if (timeout_usec == 0)
return 0;
if (timeout_usec > 0)
{
guint64 timeout_msec = (timeout_usec + 999) / 1000;
return (int) MIN (timeout_msec, G_MAXINT);
}
return -1;
}
static inline gint64
extend_timeout_to_usec (int timeout_msec)
{
if (timeout_msec >= 0)
return (gint64) timeout_msec * 1000;
return -1;
}
static gboolean
g_main_context_prepare_unlocked (GMainContext *context,
gint *priority)
@ -3676,12 +3719,12 @@ g_main_context_prepare_unlocked (GMainContext *context,
/* Prepare all sources */
context->timeout = -1;
context->timeout_usec = -1;
g_source_iter_init (&iter, context, TRUE);
while (g_source_iter_next (&iter, &source))
{
gint source_timeout = -1;
gint64 source_timeout_usec = -1;
if (SOURCE_DESTROYED (source) || SOURCE_BLOCKED (source))
continue;
@ -3699,14 +3742,17 @@ g_main_context_prepare_unlocked (GMainContext *context,
if (prepare)
{
gint64 begin_time_nsec G_GNUC_UNUSED;
int source_timeout_msec = -1;
context->in_check_or_prepare++;
UNLOCK_CONTEXT (context);
begin_time_nsec = G_TRACE_CURRENT_TIME;
result = (* prepare) (source, &source_timeout);
TRACE (GLIB_MAIN_AFTER_PREPARE (source, prepare, source_timeout));
result = (*prepare) (source, &source_timeout_msec);
TRACE (GLIB_MAIN_AFTER_PREPARE (source, prepare, source_timeout_msec));
source_timeout_usec = extend_timeout_to_usec (source_timeout_msec);
g_trace_mark (begin_time_nsec, G_TRACE_CURRENT_TIME - begin_time_nsec,
"GLib", "GSource.prepare",
@ -3730,18 +3776,13 @@ g_main_context_prepare_unlocked (GMainContext *context,
if (source->priv->ready_time <= context->time)
{
source_timeout = 0;
source_timeout_usec = 0;
result = TRUE;
}
else
else if (source_timeout_usec < 0 ||
(source->priv->ready_time < context->time + source_timeout_usec))
{
gint64 timeout;
/* rounding down will lead to spinning, so always round up */
timeout = (source->priv->ready_time - context->time + 999) / 1000;
if (source_timeout < 0 || timeout < source_timeout)
source_timeout = MIN (timeout, G_MAXINT);
source_timeout_usec = MAX (0, source->priv->ready_time - context->time);
}
}
@ -3761,16 +3802,16 @@ g_main_context_prepare_unlocked (GMainContext *context,
{
n_ready++;
current_priority = source->priority;
context->timeout = 0;
}
if (source_timeout >= 0)
{
if (context->timeout < 0)
context->timeout = source_timeout;
else
context->timeout = MIN (context->timeout, source_timeout);
context->timeout_usec = 0;
}
if (source_timeout_usec >= 0)
{
if (context->timeout_usec < 0)
context->timeout_usec = source_timeout_usec;
else
context->timeout_usec = MIN (context->timeout_usec, source_timeout_usec);
}
}
g_source_iter_clear (&iter);
@ -3807,28 +3848,32 @@ g_main_context_prepare_unlocked (GMainContext *context,
gint
g_main_context_query (GMainContext *context,
gint max_priority,
gint *timeout,
gint *timeout_msec,
GPollFD *fds,
gint n_fds)
{
gint64 timeout_usec;
gint n_poll;
if (context == NULL)
context = g_main_context_default ();
LOCK_CONTEXT (context);
n_poll = g_main_context_query_unlocked (context, max_priority, timeout, fds, n_fds);
n_poll = g_main_context_query_unlocked (context, max_priority, &timeout_usec, fds, n_fds);
UNLOCK_CONTEXT (context);
if (timeout_msec != NULL)
*timeout_msec = round_timeout_to_msec (timeout_usec);
return n_poll;
}
static gint
g_main_context_query_unlocked (GMainContext *context,
gint max_priority,
gint *timeout,
gint64 *timeout_usec,
GPollFD *fds,
gint n_fds)
{
@ -3881,15 +3926,15 @@ g_main_context_query_unlocked (GMainContext *context,
}
context->poll_changed = FALSE;
if (timeout)
if (timeout_usec)
{
*timeout = context->timeout;
if (*timeout != 0)
*timeout_usec = context->timeout_usec;
if (*timeout_usec != 0)
context->time_is_fresh = FALSE;
}
TRACE (GLIB_MAIN_CONTEXT_AFTER_QUERY (context, context->timeout,
TRACE (GLIB_MAIN_CONTEXT_AFTER_QUERY (context, context->timeout_usec,
fds, n_poll));
return n_poll;
@ -4163,7 +4208,7 @@ g_main_context_iterate_unlocked (GMainContext *context,
GThread *self)
{
gint max_priority = 0;
gint timeout;
gint64 timeout_usec;
gboolean some_ready;
gint nfds, allocated_nfds;
GPollFD *fds = NULL;
@ -4196,10 +4241,10 @@ g_main_context_iterate_unlocked (GMainContext *context,
fds = context->cached_poll_array;
g_main_context_prepare_unlocked (context, &max_priority);
while ((nfds = g_main_context_query_unlocked (
context, max_priority, &timeout, fds,
allocated_nfds)) > allocated_nfds)
context, max_priority, &timeout_usec, fds,
allocated_nfds)) > allocated_nfds)
{
g_free (fds);
context->cached_poll_array_size = allocated_nfds = nfds;
@ -4207,10 +4252,10 @@ g_main_context_iterate_unlocked (GMainContext *context,
}
if (!block)
timeout = 0;
g_main_context_poll_unlocked (context, timeout, max_priority, fds, nfds);
timeout_usec = 0;
g_main_context_poll_unlocked (context, timeout_usec, max_priority, fds, nfds);
some_ready = g_main_context_check_unlocked (context, max_priority, fds, nfds);
if (dispatch)
@ -4489,7 +4534,7 @@ g_main_loop_get_context (GMainLoop *loop)
/* HOLDS: context's lock */
static void
g_main_context_poll_unlocked (GMainContext *context,
int timeout,
gint64 timeout_usec,
int priority,
GPollFD *fds,
int n_fds)
@ -4502,7 +4547,7 @@ g_main_context_poll_unlocked (GMainContext *context,
GPollFunc poll_func;
if (n_fds || timeout != 0)
if (n_fds || timeout_usec != 0)
{
int ret, errsv;
@ -4510,16 +4555,39 @@ g_main_context_poll_unlocked (GMainContext *context,
poll_timer = NULL;
if (_g_main_poll_debug)
{
g_print ("polling context=%p n=%d timeout=%d\n",
context, n_fds, timeout);
poll_timer = g_timer_new ();
g_print ("polling context=%p n=%d timeout_usec=%"G_GINT64_FORMAT"\n",
context, n_fds, timeout_usec);
poll_timer = g_timer_new ();
}
#endif
poll_func = context->poll_func;
UNLOCK_CONTEXT (context);
ret = (*poll_func) (fds, n_fds, timeout);
LOCK_CONTEXT (context);
#if defined(HAVE_PPOLL) && defined(HAVE_POLL)
if (poll_func == g_poll)
{
struct timespec spec;
struct timespec *spec_p = NULL;
if (timeout_usec > -1)
{
spec.tv_sec = timeout_usec / G_USEC_PER_SEC;
spec.tv_nsec = (timeout_usec % G_USEC_PER_SEC) * 1000L;
spec_p = &spec;
}
UNLOCK_CONTEXT (context);
ret = ppoll ((struct pollfd *) fds, n_fds, spec_p, NULL);
LOCK_CONTEXT (context);
}
else
#endif
{
int timeout_msec = round_timeout_to_msec (timeout_usec);
UNLOCK_CONTEXT (context);
ret = (*poll_func) (fds, n_fds, timeout_msec);
LOCK_CONTEXT (context);
}
errsv = errno;
if (ret < 0 && errsv != EINTR)
@ -4535,11 +4603,11 @@ g_main_context_poll_unlocked (GMainContext *context,
#ifdef G_MAIN_POLL_DEBUG
if (_g_main_poll_debug)
{
g_print ("g_main_poll(%d) timeout: %d - elapsed %12.10f seconds",
n_fds,
timeout,
g_timer_elapsed (poll_timer, NULL));
g_timer_destroy (poll_timer);
g_print ("g_main_poll(%d) timeout_usec: %"G_GINT64_FORMAT" - elapsed %12.10f seconds",
n_fds,
timeout_usec,
g_timer_elapsed (poll_timer, NULL));
g_timer_destroy (poll_timer);
pollrec = context->poll_records;
while (pollrec != NULL)
@ -4573,7 +4641,7 @@ g_main_context_poll_unlocked (GMainContext *context,
g_print ("\n");
}
#endif
} /* if (n_fds || timeout != 0) */
} /* if (n_fds || timeout_usec != 0) */
}
/**

View File

@ -1008,6 +1008,19 @@ if cc.links('''#include <sys/eventfd.h>
glib_conf.set('HAVE_EVENTFD', 1)
endif
# Check for ppoll(2)
if cc.links('''#define _GNU_SOURCE
#include <poll.h>
#include <stddef.h>
int main (int argc, char ** argv) {
struct pollfd fds[1] = {{0}};
struct timespec ts = {0};
ppoll (fds, 1, NULL, NULL);
return 0;
}''', name : 'ppoll(2) system call')
glib_conf.set('HAVE_PPOLL', 1)
endif
# Check for pidfd_open(2)
if cc.links('''#include <sys/syscall.h>
#include <sys/wait.h>