gdk: Fix for nested mainloops on MacOS preserving the same poll fds pointer
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.