6fcf6b
From 4f0a31d66c2a6588495b8ae682f555584dafdf45 Mon Sep 17 00:00:00 2001
6fcf6b
From: Claudio Saavedra <csaavedra@igalia.com>
6fcf6b
Date: Wed, 21 Oct 2020 13:19:42 +0300
6fcf6b
Subject: [PATCH] gmain: g_main_context_check() can skip updating polled FD
6fcf6b
 sources
6fcf6b
6fcf6b
If there is a file descriptor source that has a lower priority
6fcf6b
than the one for sources that are going to be dispatched,
6fcf6b
all subsequent file descriptor sources (internally sorted by
6fcf6b
file descriptor identifier) do not get an update in their GPollRec
6fcf6b
and later on wrong sources can be dispatched.
6fcf6b
6fcf6b
Fix this by first finding the first GPollRec that matches the current
6fcf6b
GPollFD, instead of relying on it to be the current one. At
6fcf6b
the same time, document the assumptions about the ordering of the
6fcf6b
file descriptor records and array and make explicit in the documentation
6fcf6b
that the array needs to be passed to g_main_context_check() as it was
6fcf6b
received from g_main_context_query().
6fcf6b
6fcf6b
Added a new test that reproduces the bug by creating two file
6fcf6b
descriptor sources and an idle one. Since the first
6fcf6b
file descriptor created has a lower identifier and a low priority,
6fcf6b
the second one is not dispatched even when it has the same, higher,
6fcf6b
priority as the idle source. After fixing this bug, both
6fcf6b
higher priority sources are dispatched as expected.
6fcf6b
6fcf6b
While this patch was written independently, a similar fix for this
6fcf6b
bug was first submitted by Eugene M in GNOME/glib!562. Having a
6fcf6b
second fix that basically does the same is a reassurance that we
6fcf6b
are in the right here.
6fcf6b
6fcf6b
Fixes #1592
6fcf6b
---
6fcf6b
 glib/gmain.c          | 32 ++++++++++++++++++++++--
6fcf6b
 glib/tests/mainloop.c | 57 +++++++++++++++++++++++++++++++++++++++++++
6fcf6b
 2 files changed, 87 insertions(+), 2 deletions(-)
6fcf6b
6fcf6b
diff --git a/glib/gmain.c b/glib/gmain.c
6fcf6b
index 95992253d..a59cd686c 100644
6fcf6b
--- a/glib/gmain.c
6fcf6b
+++ b/glib/gmain.c
6fcf6b
@@ -3573,7 +3573,10 @@ g_main_context_prepare (GMainContext *context,
6fcf6b
  *       store #GPollFD records that need to be polled.
6fcf6b
  * @n_fds: (in): length of @fds.
6fcf6b
  *
6fcf6b
- * Determines information necessary to poll this main loop.
6fcf6b
+ * Determines information necessary to poll this main loop. You should
6fcf6b
+ * be careful to pass the resulting @fds array and its length @n_fds
6fcf6b
+ * as is when calling g_main_context_check(), as this function relies
6fcf6b
+ * on assumptions made when the array is filled.
6fcf6b
  *
6fcf6b
  * You must have successfully acquired the context with
6fcf6b
  * g_main_context_acquire() before you may call this function.
6fcf6b
@@ -3597,6 +3600,10 @@ g_main_context_query (GMainContext *context,
6fcf6b
 
6fcf6b
   TRACE (GLIB_MAIN_CONTEXT_BEFORE_QUERY (context, max_priority));
6fcf6b
 
6fcf6b
+  /* fds is filled sequentially from poll_records. Since poll_records
6fcf6b
+   * are incrementally sorted by file descriptor identifier, fds will
6fcf6b
+   * also be incrementally sorted.
6fcf6b
+   */
6fcf6b
   n_poll = 0;
6fcf6b
   lastpollrec = NULL;
6fcf6b
   for (pollrec = context->poll_records; pollrec; pollrec = pollrec->next)
6fcf6b
@@ -3611,6 +3618,10 @@ g_main_context_query (GMainContext *context,
6fcf6b
        */
6fcf6b
       events = pollrec->fd->events & ~(G_IO_ERR|G_IO_HUP|G_IO_NVAL);
6fcf6b
 
6fcf6b
+      /* This optimization --using the same GPollFD to poll for more
6fcf6b
+       * than one poll record-- relies on the poll records being
6fcf6b
+       * incrementally sorted.
6fcf6b
+       */
6fcf6b
       if (lastpollrec && pollrec->fd->fd == lastpollrec->fd->fd)
6fcf6b
         {
6fcf6b
           if (n_poll - 1 < n_fds)
6fcf6b
@@ -3656,7 +3667,10 @@ g_main_context_query (GMainContext *context,
6fcf6b
  *       the last call to g_main_context_query()
6fcf6b
  * @n_fds: return value of g_main_context_query()
6fcf6b
  *
6fcf6b
- * Passes the results of polling back to the main loop.
6fcf6b
+ * Passes the results of polling back to the main loop. You should be
6fcf6b
+ * careful to pass @fds and its length @n_fds as received from
6fcf6b
+ * g_main_context_query(), as this functions relies on assumptions
6fcf6b
+ * on how @fds is filled.
6fcf6b
  *
6fcf6b
  * You must have successfully acquired the context with
6fcf6b
  * g_main_context_acquire() before you may call this function.
6fcf6b
@@ -3711,10 +3725,22 @@ g_main_context_check (GMainContext *context,
6fcf6b
       return FALSE;
6fcf6b
     }
6fcf6b
 
6fcf6b
+  /* The linear iteration below relies on the assumption that both
6fcf6b
+   * poll records and the fds array are incrementally sorted by file
6fcf6b
+   * descriptor identifier.
6fcf6b
+   */
6fcf6b
   pollrec = context->poll_records;
6fcf6b
   i = 0;
6fcf6b
   while (pollrec && i < n_fds)
6fcf6b
     {
6fcf6b
+      /* Make sure that fds is sorted by file descriptor identifier. */
6fcf6b
+      g_assert (i <= 0 || fds[i - 1].fd < fds[i].fd);
6fcf6b
+
6fcf6b
+      /* Skip until finding the first GPollRec matching the current GPollFD. */
6fcf6b
+      while (pollrec && pollrec->fd->fd != fds[i].fd)
6fcf6b
+        pollrec = pollrec->next;
6fcf6b
+
6fcf6b
+      /* Update all consecutive GPollRecs that match. */
6fcf6b
       while (pollrec && pollrec->fd->fd == fds[i].fd)
6fcf6b
         {
6fcf6b
           if (pollrec->priority <= max_priority)
6fcf6b
@@ -3725,6 +3751,7 @@ g_main_context_check (GMainContext *context,
6fcf6b
           pollrec = pollrec->next;
6fcf6b
         }
6fcf6b
 
6fcf6b
+      /* Iterate to next GPollFD. */
6fcf6b
       i++;
6fcf6b
     }
6fcf6b
 
6fcf6b
@@ -4320,6 +4347,7 @@ g_main_context_add_poll_unlocked (GMainContext *context,
6fcf6b
   newrec->fd = fd;
6fcf6b
   newrec->priority = priority;
6fcf6b
 
6fcf6b
+  /* Poll records are incrementally sorted by file descriptor identifier. */
6fcf6b
   prevrec = NULL;
6fcf6b
   nextrec = context->poll_records;
6fcf6b
   while (nextrec)
6fcf6b
diff --git a/glib/tests/mainloop.c b/glib/tests/mainloop.c
6fcf6b
index f5d672a63..397921f2d 100644
6fcf6b
--- a/glib/tests/mainloop.c
6fcf6b
+++ b/glib/tests/mainloop.c
6fcf6b
@@ -1511,6 +1511,62 @@ test_unix_file_poll (void)
6fcf6b
   close (fd);
6fcf6b
 }
6fcf6b
 
6fcf6b
+static void
6fcf6b
+test_unix_fd_priority (void)
6fcf6b
+{
6fcf6b
+  gint fd1, fd2;
6fcf6b
+  GMainLoop *loop;
6fcf6b
+  GSource *source;
6fcf6b
+
6fcf6b
+  gint s1 = 0;
6fcf6b
+  gboolean s2 = FALSE, s3 = FALSE;
6fcf6b
+
6fcf6b
+  g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/issues/1592");
6fcf6b
+
6fcf6b
+  loop = g_main_loop_new (NULL, FALSE);
6fcf6b
+
6fcf6b
+  source = g_idle_source_new ();
6fcf6b
+  g_source_set_callback (source, count_calls, &s1, NULL);
6fcf6b
+  g_source_set_priority (source, 0);
6fcf6b
+  g_source_attach (source, NULL);
6fcf6b
+  g_source_unref (source);
6fcf6b
+
6fcf6b
+  fd1 = open ("/dev/random", O_RDONLY);
6fcf6b
+  g_assert_cmpint (fd1, >=, 0);
6fcf6b
+  source = g_unix_fd_source_new (fd1, G_IO_IN);
6fcf6b
+  g_source_set_callback (source, (GSourceFunc) (void (*)(void)) (flag_bool), &s2, NULL);
6fcf6b
+  g_source_set_priority (source, 10);
6fcf6b
+  g_source_attach (source, NULL);
6fcf6b
+  g_source_unref (source);
6fcf6b
+
6fcf6b
+  fd2 = open ("/dev/random", O_RDONLY);
6fcf6b
+  g_assert_cmpint (fd2, >=, 0);
6fcf6b
+  source = g_unix_fd_source_new (fd2, G_IO_IN);
6fcf6b
+  g_source_set_callback (source, (GSourceFunc) (void (*)(void)) (flag_bool), &s3, NULL);
6fcf6b
+  g_source_set_priority (source, 0);
6fcf6b
+  g_source_attach (source, NULL);
6fcf6b
+  g_source_unref (source);
6fcf6b
+
6fcf6b
+  /* This tests a bug that depends on the source with the lowest FD
6fcf6b
+     identifier to have the lowest priority. Make sure that this is
6fcf6b
+     the case. */
6fcf6b
+  g_assert_cmpint (fd1, <, fd2);
6fcf6b
+
6fcf6b
+  g_assert_true (g_main_context_iteration (NULL, FALSE));
6fcf6b
+
6fcf6b
+  /* Idle source should have been dispatched. */
6fcf6b
+  g_assert_cmpint (s1, ==, 1);
6fcf6b
+  /* Low priority FD source shouldn't have been dispatched. */
6fcf6b
+  g_assert_false (s2);
6fcf6b
+  /* Default priority FD source should have been dispatched. */
6fcf6b
+  g_assert_true (s3);
6fcf6b
+
6fcf6b
+  g_main_loop_unref (loop);
6fcf6b
+
6fcf6b
+  close (fd1);
6fcf6b
+  close (fd2);
6fcf6b
+}
6fcf6b
+
6fcf6b
 #endif
6fcf6b
 
6fcf6b
 static gboolean
6fcf6b
@@ -1751,6 +1807,7 @@ main (int argc, char *argv[])
6fcf6b
   g_test_add_func ("/mainloop/source-unix-fd-api", test_source_unix_fd_api);
6fcf6b
   g_test_add_func ("/mainloop/wait", test_mainloop_wait);
6fcf6b
   g_test_add_func ("/mainloop/unix-file-poll", test_unix_file_poll);
6fcf6b
+  g_test_add_func ("/mainloop/unix-fd-priority", test_unix_fd_priority);
6fcf6b
 #endif
6fcf6b
   g_test_add_func ("/mainloop/nfds", test_nfds);
6fcf6b
 
6fcf6b
-- 
6fcf6b
2.31.1
6fcf6b