x11rb/event_loop_integration.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
//! # Integrating x11rb with an Event Loop
//!
//! To integrate x11rb with an event loop,
//! [`std::os::unix::io::AsRawFd`](https://doc.rust-lang.org/std/os/unix/io/trait.AsRawFd.html) is
//! implemented by [`RustConnection`](../rust_connection/struct.RustConnection.html)'s
//! [`DefaultStream`](../rust_connection/struct.DefaultStream.html#impl-AsRawFd) and
//! [`XCBConnection`](../xcb_ffi/struct.XCBConnection.html#impl-AsRawFd). This allows to integrate
//! with an event loop that also handles timeouts or network I/O. See
//! [`xclock_utc`](https://github.com/psychon/x11rb/blob/master/x11rb/examples/xclock_utc.rs) for an
//! example.
//!
//! The general form of such an integration could be as follows:
//! ```no_run
//! #[cfg(unix)]
//! use std::os::unix::io::{AsRawFd, RawFd};
//! #[cfg(windows)]
//! use std::os::windows::io::{AsRawSocket, RawSocket};
//! use x11rb::connection::Connection;
//! use x11rb::rust_connection::RustConnection;
//! use x11rb::errors::ConnectionError;
//!
//! fn main_loop(conn: &RustConnection) -> Result<(), ConnectionError> {
//! #[cfg(unix)]
//! let raw_handle = conn.stream().as_raw_fd();
//! #[cfg(windows)]
//! let raw_handle = conn.stream().as_raw_socket();
//! loop {
//! while let Some(event) = conn.poll_for_event()? {
//! handle_event(event);
//! }
//!
//! poll_for_readable(raw_handle);
//!
//! // Do other work here.
//! }
//! }
//! # fn handle_event<T>(event: T) {}
//! # fn poll_for_readable<T>(event: T) {}
//! ```
//! The function `poll_for_readable` could wait for any number of I/O streams (besides the one from
//! x11rb) to become readable. It can also implement timeouts, as seen in the
//! [`xclock_utc` example](https://github.com/psychon/x11rb/blob/master/x11rb/examples/xclock_utc.rs).
//!
//!
//! ## Threads and Races
//!
//! Both [`RustConnection`](../rust_connection/struct.RustConnection.html) and
//! [`XCBConnection`](../xcb_ffi/struct.XCBConnection.html) are `Sync+Send`. However, it is still
//! possible to see races in the presence of threads and an event loop.
//!
//! The underlying problem is that the following two points are not equivalent:
//!
//! 1. A new event is available and can be returned from `conn.poll_for_event()`.
//! 2. The underlying I/O stream is readable.
//!
//! The reason for this is an internal buffer that is required: When an event is received from the
//! X11 server, but we are currently not in `conn.poll_for_event()`, then this event is added to an
//! internal buffer. Thus, it can happen that there is an event available, but the stream is not
//! readable.
//!
//! An example for such an other function is `conn.get_input_focus()?.reply()?`: The
//! `GetInputFocus` request is sent to the server and then `reply()` waits for the reply. It does
//! so by reading X11 packets from the X11 server until the right reply arrives. Any events that
//! are read during this are buffered internally in the `Connection`.
//!
//! If this race occurs, the main loop would sit in `poll_for_readable` and wait, while the already
//! buffered event is available. When something else wakes up the main loop and
//! `conn.poll_for_event()` is called the next time, the event is finally processed.
//!
//! There are two ways around this:
//!
//! 1. Only interact with x11rb from one thread.
//! 2. Use a dedicated thread for waiting for event.
//!
//! In case (1), one can call `conn.poll_for_event()` before waiting for the underlying I/O stream
//! to be readable. Since there are no other threads, nothing can read a new event from the stream
//! after `conn.poll_for_event()` returned `None`.
//!
//! Option (2) is to start a thread that calls `conn.wait_for_event()` in a loop. This is basically
//! a dedicated event loop for fetching events from the X11 server. All other threads can now
//! freely use the X11 connection without events possibly getting stuck and only being processed
//! later.