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