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