diff --git a/glib/gmain.c b/glib/gmain.c index 6d56ab3e8..ded5dccbf 100644 --- a/glib/gmain.c +++ b/glib/gmain.c @@ -69,6 +69,10 @@ #include #include +#ifdef HAVE_POLL_H +#include +#endif + #ifdef HAVE_PIDFD #include #include @@ -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) */ } /** diff --git a/meson.build b/meson.build index b82e2bf3b..c741a0c32 100644 --- a/meson.build +++ b/meson.build @@ -1008,6 +1008,19 @@ if cc.links('''#include glib_conf.set('HAVE_EVENTFD', 1) endif +# Check for ppoll(2) +if cc.links('''#define _GNU_SOURCE + #include + #include + 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 #include