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