X11: Fix empty event race condition with a pipe

There is a seemingly unavoidable race condition when waiting for data on
the X11 display connection, as long as any other thread is also making
Xlib calls.  The event data we are waiting for could be read by the
other thread as part of looking for the reply to its request, before our
poll has begun.

This commit replaces the X11 event sent by glfwPostEmptyEvent with
writing to an unnamed pipe.  The race condition remains if other Xlib
calls are made on other threads, but glfwPostEmptyEvent should now be
race-free.

This commit is based on work by pcwalton, OlivierSohn, kovidgoyal and
joaodasilva.

Closes #2033
Related to #379
Related to #1281
Related to #1285
This commit is contained in:
Camilla Löwy
2022-02-18 14:29:43 +01:00
committed by Camilla Löwy
parent 363d471441
commit cd22e28495
6 changed files with 88 additions and 9 deletions
+35 -9
View File
@@ -114,8 +114,12 @@ static GLFWbool waitForX11Event(double* timeout)
//
static GLFWbool waitForAnyEvent(double* timeout)
{
nfds_t count = 1;
struct pollfd fds[2] = { { ConnectionNumber(_glfw.x11.display), POLLIN } };
nfds_t count = 2;
struct pollfd fds[3] =
{
{ ConnectionNumber(_glfw.x11.display), POLLIN },
{ _glfw.x11.emptyEventPipe[0], POLLIN }
};
#if defined(__linux__)
if (_glfw.joysticksInitialized)
@@ -137,6 +141,32 @@ static GLFWbool waitForAnyEvent(double* timeout)
return GLFW_TRUE;
}
// Writes a byte to the empty event pipe
//
static void writeEmptyEvent(void)
{
for (;;)
{
const char byte = 0;
const int result = write(_glfw.x11.emptyEventPipe[1], &byte, 1);
if (result == 1 || (result == -1 && errno != EINTR))
break;
}
}
// Drains available data from the empty event pipe
//
static void drainEmptyEvents(void)
{
for (;;)
{
char dummy[64];
const int result = read(_glfw.x11.emptyEventPipe[0], dummy, sizeof(dummy));
if (result == -1 && errno != EINTR)
break;
}
}
// Waits until a VisibilityNotify event arrives for the specified window or the
// timeout period elapses (ICCCM section 4.2.2)
//
@@ -2778,6 +2808,8 @@ GLFWbool _glfwRawMouseMotionSupportedX11(void)
void _glfwPollEventsX11(void)
{
drainEmptyEvents();
#if defined(__linux__)
if (_glfw.joysticksInitialized)
_glfwDetectJoystickConnectionLinux();
@@ -2823,13 +2855,7 @@ void _glfwWaitEventsTimeoutX11(double timeout)
void _glfwPostEmptyEventX11(void)
{
XEvent event = { ClientMessage };
event.xclient.window = _glfw.x11.helperWindowHandle;
event.xclient.format = 32; // Data is 32-bit longs
event.xclient.message_type = _glfw.x11.NULL_;
XSendEvent(_glfw.x11.display, _glfw.x11.helperWindowHandle, False, 0, &event);
XFlush(_glfw.x11.display);
writeEmptyEvent();
}
void _glfwGetCursorPosX11(_GLFWwindow* window, double* xpos, double* ypos)