x11: thread safe replacement for XNextEvent (#782)

XNextEvent will block for input while holding the global Xlib mutex.

This will cause a deadlock in even the most trivial multi-threaded
application because OpenGL functions will need to hold the Xlib mutex
too.

Add EventsLoop::poll_one_event and EventsLoop::wait_for_input to provide
thread-safe functions to poll and wait events from the X11 event queue
using unix select(2) and XCheckIfEvent.

This is a somewhat ugly workaround to an ugly problem.

Fixes #779
This commit is contained in:
Riku Salminen 2019-02-25 01:02:55 +02:00 committed by Victor Berger
parent f000b82d74
commit ab0a34012f
3 changed files with 81 additions and 6 deletions

View file

@ -42,6 +42,7 @@
- On Wayland, add `set_wayland_theme()` to control client decoration color theme - On Wayland, add `set_wayland_theme()` to control client decoration color theme
- Added serde serialization to `os::unix::XWindowType`. - Added serde serialization to `os::unix::XWindowType`.
- **Breaking:** Remove the `icon_loading` feature and the associated `image` dependency. - **Breaking:** Remove the `icon_loading` feature and the associated `image` dependency.
- On X11, make event loop thread safe by replacing XNextEvent with select(2) and XCheckIfEvent
- On Windows, fix malformed function pointer typecast that could invoke undefined behavior. - On Windows, fix malformed function pointer typecast that could invoke undefined behavior.
- Refactored Windows state/flag-setting code. - Refactored Windows state/flag-setting code.
- On Windows, hiding the cursor no longer hides the cursor for all Winit windows - just the one `hide_cursor` was called on. - On Windows, hiding the cursor no longer hides the cursor for all Winit windows - just the one `hide_cursor` was called on.

View file

@ -19,6 +19,7 @@ use std::collections::HashMap;
use std::ffi::CStr; use std::ffi::CStr;
use std::ops::Deref; use std::ops::Deref;
use std::os::raw::*; use std::os::raw::*;
use libc::{select, fd_set, FD_SET, FD_ZERO, FD_ISSET, EINTR, EINVAL, ENOMEM, EBADF, __errno_location};
use std::sync::{Arc, mpsc, Weak}; use std::sync::{Arc, mpsc, Weak};
use std::sync::atomic::{self, AtomicBool}; use std::sync::atomic::{self, AtomicBool};
@ -185,6 +186,70 @@ impl EventLoop {
} }
} }
unsafe fn poll_one_event(&mut self, event_ptr : *mut ffi::XEvent) -> bool {
// This function is used to poll and remove a single event
// from the Xlib event queue in a non-blocking, atomic way.
// XCheckIfEvent is non-blocking and removes events from queue.
// XNextEvent can't be used because it blocks while holding the
// global Xlib mutex.
// XPeekEvent does not remove events from the queue.
unsafe extern "C" fn predicate(
_display: *mut ffi::Display,
_event: *mut ffi::XEvent,
_arg : *mut c_char) -> c_int {
// This predicate always returns "true" (1) to accept all events
1
}
let result = (self.xconn.xlib.XCheckIfEvent)(
self.xconn.display,
event_ptr,
Some(predicate),
std::ptr::null_mut());
result != 0
}
unsafe fn wait_for_input(&mut self) {
// XNextEvent can not be used in multi-threaded applications
// because it is blocking for input while holding the global
// Xlib mutex.
// To work around this issue, first flush the X11 display, then
// use select(2) to wait for input to arrive
loop {
// First use XFlush to flush any buffered x11 requests
(self.xconn.xlib.XFlush)(self.xconn.display);
// Then use select(2) to wait for input data
let mut fds : fd_set = mem::uninitialized();
FD_ZERO(&mut fds);
FD_SET(self.xconn.x11_fd, &mut fds);
let err = select(
self.xconn.x11_fd + 1,
&mut fds, // read fds
std::ptr::null_mut(), // write fds
std::ptr::null_mut(), // except fds (could be used to detect errors)
std::ptr::null_mut()); // timeout
if err < 0 {
let errno_ptr = __errno_location();
let errno = *errno_ptr;
if errno == EINTR {
// try again if errno is EINTR
continue;
}
assert!(errno == EBADF || errno == EINVAL || errno == ENOMEM);
panic!("select(2) returned fatal error condition");
}
if FD_ISSET(self.xconn.x11_fd, &mut fds) {
break;
}
}
}
pub fn poll_events<F>(&mut self, mut callback: F) pub fn poll_events<F>(&mut self, mut callback: F)
where F: FnMut(Event) where F: FnMut(Event)
{ {
@ -192,13 +257,9 @@ impl EventLoop {
loop { loop {
// Get next event // Get next event
unsafe { unsafe {
// Ensure XNextEvent won't block if !self.poll_one_event(&mut xev) {
let count = (self.xconn.xlib.XPending)(self.xconn.display);
if count == 0 {
break; break;
} }
(self.xconn.xlib.XNextEvent)(self.xconn.display, &mut xev);
} }
self.process_event(&mut xev, &mut callback); self.process_event(&mut xev, &mut callback);
} }
@ -210,7 +271,12 @@ impl EventLoop {
let mut xev = unsafe { mem::uninitialized() }; let mut xev = unsafe { mem::uninitialized() };
loop { loop {
unsafe { (self.xconn.xlib.XNextEvent)(self.xconn.display, &mut xev) }; // Blocks as necessary unsafe {
while !self.poll_one_event(&mut xev) {
// block until input is available
self.wait_for_input();
}
};
let mut control_flow = ControlFlow::Continue; let mut control_flow = ControlFlow::Continue;

View file

@ -1,6 +1,7 @@
use std::ptr; use std::ptr;
use std::fmt; use std::fmt;
use std::error::Error; use std::error::Error;
use std::os::raw::c_int;
use libc; use libc;
use parking_lot::Mutex; use parking_lot::Mutex;
@ -18,6 +19,7 @@ pub struct XConnection {
pub xinput2: ffi::XInput2, pub xinput2: ffi::XInput2,
pub xlib_xcb: ffi::Xlib_xcb, pub xlib_xcb: ffi::Xlib_xcb,
pub display: *mut ffi::Display, pub display: *mut ffi::Display,
pub x11_fd: c_int,
pub latest_error: Mutex<Option<XError>>, pub latest_error: Mutex<Option<XError>>,
} }
@ -48,6 +50,11 @@ impl XConnection {
display display
}; };
// Get X11 socket file descriptor
let fd = unsafe {
(xlib.XConnectionNumber)(display)
};
Ok(XConnection { Ok(XConnection {
xlib, xlib,
xrandr, xrandr,
@ -56,6 +63,7 @@ impl XConnection {
xinput2, xinput2,
xlib_xcb, xlib_xcb,
display, display,
x11_fd: fd,
latest_error: Mutex::new(None), latest_error: Mutex::new(None),
}) })
} }