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