Skip to content

gdk: Fix for nested mainloops on MacOS preserving the same poll fds pointer

Marco Mastropaolo requested to merge xanathar/gtk:gtk-3-24 into gtk-3-24

This fixes/patches a problem that might happen in somewhat uncommon conditions when using nested main loops on MacOS.

Symptom

Code running nested main loops on MacOS might crash with the following message:

Gdk:ERROR:../gdk/quartz/gdkeventloop-quartz.c:585:select_thread_collect_poll: assertion failed: (ufds[i].fd == current_pollfds[i].fd)
Bail out! Gdk:ERROR:../gdk/quartz/gdkeventloop-quartz.c:585:select_thread_collect_poll: assertion failed: (ufds[i].fd == current_pollfds[i].fd)
[1]    23888 abort      /Applications/...

Reason

Apparently, gdk replaces the polling function that is used by glib to poll file descriptors, in order to work around the problem of having to wait for events which are exposed by Quartz in ways different from fds.

The new poll function (poll_func) calls select_thread_start_poll to start polling on an array of fds on a separate thread, then "synchronously" checks for Quartz events using [NSApp nextEventMatchingMask ...]. After that, it calls select_thread_collect_poll to check if the separate fds polling thread reported any result. There are a couple of caveats for calls to select_thread_collect_poll, the most relevant of which is that (quoted from comment) "the GPollFD array passed in must be identical to the one passed to select_thread_start_poll()".

Now, poll_func has a check where it stores in a static GPollFD *last_ufds; variable the pointer to the array of the fds being polled, and skips the call to select_thread_collect_poll if the last_ufds != ufds (that is, if the last call to select_thread_start_poll is not the current active one because of a nested main loop). This however is not sufficient, because glib might call the poll_func with the SAME pointer but different values inside the array.

Solution

As a solution to this specific problem, I replaced the check on last_ufds with a static (static guint64 execution_count = 0;) that tracks the number of times this function is called and skips the call to select_thread_collect_poll if it's not the latest execution in time order.

This is obviously far from ideal as a solution, but I think it's on-par with the current code that tracks the latest value of the parameter in a static (static GPollFD *last_ufds;), and solves the problem where the pointer remains the same but the contents don't.

In the end, given the way the poll thread is implemented with its own copy of the fds being polled I'm not sure it can be solved without a similar workaround, barring major impacting changes.

Edited by Marco Mastropaolo

Merge request reports