| 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. |
| 83 | |