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:
parent
f000b82d74
commit
ab0a34012f
3 changed files with 81 additions and 6 deletions
|
|
@ -19,6 +19,7 @@ use std::collections::HashMap;
|
|||
use std::ffi::CStr;
|
||||
use std::ops::Deref;
|
||||
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::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)
|
||||
where F: FnMut(Event)
|
||||
{
|
||||
|
|
@ -192,13 +257,9 @@ impl EventLoop {
|
|||
loop {
|
||||
// Get next event
|
||||
unsafe {
|
||||
// Ensure XNextEvent won't block
|
||||
let count = (self.xconn.xlib.XPending)(self.xconn.display);
|
||||
if count == 0 {
|
||||
if !self.poll_one_event(&mut xev) {
|
||||
break;
|
||||
}
|
||||
|
||||
(self.xconn.xlib.XNextEvent)(self.xconn.display, &mut xev);
|
||||
}
|
||||
self.process_event(&mut xev, &mut callback);
|
||||
}
|
||||
|
|
@ -210,7 +271,12 @@ impl EventLoop {
|
|||
let mut xev = unsafe { mem::uninitialized() };
|
||||
|
||||
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;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue