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 <errno.h>
#include <string.h> #include <string.h>
#ifdef HAVE_POLL_H
#include <poll.h>
#endif
#ifdef HAVE_PIDFD #ifdef HAVE_PIDFD
#include <sys/syscall.h> #include <sys/syscall.h>
#include <sys/wait.h> #include <sys/wait.h>
@ -189,7 +193,7 @@ struct _GMainContext
GHashTable *sources; /* guint -> GSource */ GHashTable *sources; /* guint -> GSource */
GPtrArray *pending_dispatches; GPtrArray *pending_dispatches;
gint timeout; /* Timeout for current iteration */ gint64 timeout_usec; /* Timeout for current iteration */
guint next_id; guint next_id;
GQueue source_lists; GQueue source_lists;
@ -322,7 +326,7 @@ static gboolean g_main_context_prepare_unlocked (GMainContext *context,
gint *priority); gint *priority);
static gint g_main_context_query_unlocked (GMainContext *context, static gint g_main_context_query_unlocked (GMainContext *context,
gint max_priority, gint max_priority,
gint *timeout, gint64 *timeout_usec,
GPollFD *fds, GPollFD *fds,
gint n_fds); gint n_fds);
static gboolean g_main_context_check_unlocked (GMainContext *context, 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); gint n_fds);
static void g_main_context_dispatch_unlocked (GMainContext *context); static void g_main_context_dispatch_unlocked (GMainContext *context);
static void g_main_context_poll_unlocked (GMainContext *context, static void g_main_context_poll_unlocked (GMainContext *context,
int timeout, gint64 timeout_usec,
int priority, int priority,
GPollFD *fds, GPollFD *fds,
int n_fds); int n_fds);
@ -3633,6 +3637,45 @@ g_main_context_prepare (GMainContext *context,
return ready; 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 static gboolean
g_main_context_prepare_unlocked (GMainContext *context, g_main_context_prepare_unlocked (GMainContext *context,
gint *priority) gint *priority)
@ -3676,12 +3719,12 @@ g_main_context_prepare_unlocked (GMainContext *context,
/* Prepare all sources */ /* Prepare all sources */
context->timeout = -1; context->timeout_usec = -1;
g_source_iter_init (&iter, context, TRUE); g_source_iter_init (&iter, context, TRUE);
while (g_source_iter_next (&iter, &source)) while (g_source_iter_next (&iter, &source))
{ {
gint source_timeout = -1; gint64 source_timeout_usec = -1;
if (SOURCE_DESTROYED (source) || SOURCE_BLOCKED (source)) if (SOURCE_DESTROYED (source) || SOURCE_BLOCKED (source))
continue; continue;
@ -3699,14 +3742,17 @@ g_main_context_prepare_unlocked (GMainContext *context,
if (prepare) if (prepare)
{ {
gint64 begin_time_nsec G_GNUC_UNUSED; gint64 begin_time_nsec G_GNUC_UNUSED;
int source_timeout_msec = -1;
context->in_check_or_prepare++; context->in_check_or_prepare++;
UNLOCK_CONTEXT (context); UNLOCK_CONTEXT (context);
begin_time_nsec = G_TRACE_CURRENT_TIME; begin_time_nsec = G_TRACE_CURRENT_TIME;
result = (* prepare) (source, &source_timeout); result = (*prepare) (source, &source_timeout_msec);
TRACE (GLIB_MAIN_AFTER_PREPARE (source, prepare, source_timeout)); 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, g_trace_mark (begin_time_nsec, G_TRACE_CURRENT_TIME - begin_time_nsec,
"GLib", "GSource.prepare", "GLib", "GSource.prepare",
@ -3730,18 +3776,13 @@ g_main_context_prepare_unlocked (GMainContext *context,
if (source->priv->ready_time <= context->time) if (source->priv->ready_time <= context->time)
{ {
source_timeout = 0; source_timeout_usec = 0;
result = TRUE; result = TRUE;
} }
else else if (source_timeout_usec < 0 ||
(source->priv->ready_time < context->time + source_timeout_usec))
{ {
gint64 timeout; source_timeout_usec = MAX (0, source->priv->ready_time - context->time);
/* 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);
} }
} }
@ -3761,15 +3802,15 @@ g_main_context_prepare_unlocked (GMainContext *context,
{ {
n_ready++; n_ready++;
current_priority = source->priority; current_priority = source->priority;
context->timeout = 0; context->timeout_usec = 0;
} }
if (source_timeout >= 0) if (source_timeout_usec >= 0)
{ {
if (context->timeout < 0) if (context->timeout_usec < 0)
context->timeout = source_timeout; context->timeout_usec = source_timeout_usec;
else else
context->timeout = MIN (context->timeout, source_timeout); context->timeout_usec = MIN (context->timeout_usec, source_timeout_usec);
} }
} }
g_source_iter_clear (&iter); g_source_iter_clear (&iter);
@ -3807,10 +3848,11 @@ g_main_context_prepare_unlocked (GMainContext *context,
gint gint
g_main_context_query (GMainContext *context, g_main_context_query (GMainContext *context,
gint max_priority, gint max_priority,
gint *timeout, gint *timeout_msec,
GPollFD *fds, GPollFD *fds,
gint n_fds) gint n_fds)
{ {
gint64 timeout_usec;
gint n_poll; gint n_poll;
if (context == NULL) if (context == NULL)
@ -3818,17 +3860,20 @@ g_main_context_query (GMainContext *context,
LOCK_CONTEXT (context); 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); UNLOCK_CONTEXT (context);
if (timeout_msec != NULL)
*timeout_msec = round_timeout_to_msec (timeout_usec);
return n_poll; return n_poll;
} }
static gint static gint
g_main_context_query_unlocked (GMainContext *context, g_main_context_query_unlocked (GMainContext *context,
gint max_priority, gint max_priority,
gint *timeout, gint64 *timeout_usec,
GPollFD *fds, GPollFD *fds,
gint n_fds) gint n_fds)
{ {
@ -3882,14 +3927,14 @@ g_main_context_query_unlocked (GMainContext *context,
context->poll_changed = FALSE; context->poll_changed = FALSE;
if (timeout) if (timeout_usec)
{ {
*timeout = context->timeout; *timeout_usec = context->timeout_usec;
if (*timeout != 0) if (*timeout_usec != 0)
context->time_is_fresh = FALSE; 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)); fds, n_poll));
return n_poll; return n_poll;
@ -4163,7 +4208,7 @@ g_main_context_iterate_unlocked (GMainContext *context,
GThread *self) GThread *self)
{ {
gint max_priority = 0; gint max_priority = 0;
gint timeout; gint64 timeout_usec;
gboolean some_ready; gboolean some_ready;
gint nfds, allocated_nfds; gint nfds, allocated_nfds;
GPollFD *fds = NULL; GPollFD *fds = NULL;
@ -4198,7 +4243,7 @@ g_main_context_iterate_unlocked (GMainContext *context,
g_main_context_prepare_unlocked (context, &max_priority); g_main_context_prepare_unlocked (context, &max_priority);
while ((nfds = g_main_context_query_unlocked ( while ((nfds = g_main_context_query_unlocked (
context, max_priority, &timeout, fds, context, max_priority, &timeout_usec, fds,
allocated_nfds)) > allocated_nfds) allocated_nfds)) > allocated_nfds)
{ {
g_free (fds); g_free (fds);
@ -4207,9 +4252,9 @@ g_main_context_iterate_unlocked (GMainContext *context,
} }
if (!block) if (!block)
timeout = 0; timeout_usec = 0;
g_main_context_poll_unlocked (context, timeout, max_priority, fds, nfds); g_main_context_poll_unlocked (context, timeout_usec, max_priority, fds, nfds);
some_ready = g_main_context_check_unlocked (context, max_priority, fds, nfds); some_ready = g_main_context_check_unlocked (context, max_priority, fds, nfds);
@ -4489,7 +4534,7 @@ g_main_loop_get_context (GMainLoop *loop)
/* HOLDS: context's lock */ /* HOLDS: context's lock */
static void static void
g_main_context_poll_unlocked (GMainContext *context, g_main_context_poll_unlocked (GMainContext *context,
int timeout, gint64 timeout_usec,
int priority, int priority,
GPollFD *fds, GPollFD *fds,
int n_fds) int n_fds)
@ -4502,7 +4547,7 @@ g_main_context_poll_unlocked (GMainContext *context,
GPollFunc poll_func; GPollFunc poll_func;
if (n_fds || timeout != 0) if (n_fds || timeout_usec != 0)
{ {
int ret, errsv; int ret, errsv;
@ -4510,16 +4555,39 @@ g_main_context_poll_unlocked (GMainContext *context,
poll_timer = NULL; poll_timer = NULL;
if (_g_main_poll_debug) if (_g_main_poll_debug)
{ {
g_print ("polling context=%p n=%d timeout=%d\n", g_print ("polling context=%p n=%d timeout_usec=%"G_GINT64_FORMAT"\n",
context, n_fds, timeout); context, n_fds, timeout_usec);
poll_timer = g_timer_new (); poll_timer = g_timer_new ();
} }
#endif #endif
poll_func = context->poll_func; poll_func = context->poll_func;
#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); UNLOCK_CONTEXT (context);
ret = (*poll_func) (fds, n_fds, timeout); ret = ppoll ((struct pollfd *) fds, n_fds, spec_p, NULL);
LOCK_CONTEXT (context); 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; errsv = errno;
if (ret < 0 && errsv != EINTR) if (ret < 0 && errsv != EINTR)
@ -4535,9 +4603,9 @@ g_main_context_poll_unlocked (GMainContext *context,
#ifdef G_MAIN_POLL_DEBUG #ifdef G_MAIN_POLL_DEBUG
if (_g_main_poll_debug) if (_g_main_poll_debug)
{ {
g_print ("g_main_poll(%d) timeout: %d - elapsed %12.10f seconds", g_print ("g_main_poll(%d) timeout_usec: %"G_GINT64_FORMAT" - elapsed %12.10f seconds",
n_fds, n_fds,
timeout, timeout_usec,
g_timer_elapsed (poll_timer, NULL)); g_timer_elapsed (poll_timer, NULL));
g_timer_destroy (poll_timer); g_timer_destroy (poll_timer);
pollrec = context->poll_records; pollrec = context->poll_records;
@ -4573,7 +4641,7 @@ g_main_context_poll_unlocked (GMainContext *context,
g_print ("\n"); g_print ("\n");
} }
#endif #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) glib_conf.set('HAVE_EVENTFD', 1)
endif 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) # Check for pidfd_open(2)
if cc.links('''#include <sys/syscall.h> if cc.links('''#include <sys/syscall.h>
#include <sys/wait.h> #include <sys/wait.h>