Expand description
Display-server plumbing: process-wide Xlib setup, and the one place that marshals a display read onto the windowing system’s main thread.
§Why Xlib threading has to be initialised (#333)
Entracte is a multi-threaded X client whether it wants to be or not:
GTK/WebKit own the main thread, while the scheduler run loop — a
tauri::async_runtime task, i.e. a tokio worker — polls
crate::scheduler::idle once a second, and on X11 user_idle answers
that by calling XOpenDisplay / XScreenSaverQueryInfo /
XCloseDisplay directly on the calling thread.
libX11’s internal locking is inert until XInitThreads() has been
called, and the call has to come before any Display is opened to be
effective. Without it, two threads touching Xlib corrupt the request
queue and libxcb aborts the whole process:
[xcb] Unknown request in queue while dequeuing
[xcb] Most likely this is a multi-threaded client and XInitThreads has not been called
[xcb] Aborting, sorry about that.That abort killed the smoke (ubuntu-22.04) e2e job intermittently, and
it is not a CI artefact — the same two threads race in a real X11
session. The abort firing is itself the proof that Xlib locking was not
enabled in the process: with it enabled, concurrent same-display use is
Xlib’s job to serialise.
XInitThreads() is the documented prerequisite here, not a workaround
for a symptom. The alternative — marshalling every X call onto the main
thread — cannot cover the idle probe: that connection belongs to a
third-party crate, and parking a blocking 1 Hz X round-trip on the GTK
main thread would be worse than the problem.
§Why the main-thread hop still exists
Xlib locking makes concurrent access safe, not correct. A handful of
tauri APIs reach into the event loop’s window_target with no
dispatch at all, and the unsafe impl Send that lets that type cross
threads states its own precondition: “we ensure this type is only used
on the main thread”. on_main_thread is how every such read upholds
it, in one place.
Both of those mechanisms report themselves, because a silent fix for an
intermittent abort is one you get to debug twice: the locking state lands
in the startup banner via threading_state, and a hop that gives up
warns with the name of the read it abandoned.
Enums§
- Xlib
Locking 🔒 - Whether libX11’s internal locking is on for this process. Only meaningful
on Linux/X11; callers off Linux report “n/a” themselves, the way the
banner already handles
crate::window::WaylandFix.
Constants§
- MAIN_
THREAD_ 🔒READ_ TIMEOUT - How long to wait for the main thread to answer a display read before giving up. Generous enough to absorb a busy event loop, short enough that a wedged main thread degrades to “no answer” rather than stalling the caller indefinitely.
Statics§
- THREADING 🔒
- Recorded by
init_display_threadingso the startup banner can report it. It has to be recorded rather than logged on the spot: that function runs as the very first statement ofrun(), beforetauri_plugin_logis installed, so alog::warn!there would go nowhere.
Functions§
- gave_up 🔒
- Report a read the main thread never answered and degrade to
None.whydistinguishes the cases on its own: aRecvTimeoutErrorsays whether the wait timed out or the task was dropped, and a dispatch error says the event loop is gone. - hop_
outcome 🔒 - Interpret the hop’s answer: the value if the main thread sent one, else a
warning and
None. Split out so the give-up arm — which no test can provoke through a live runtime — is still exercised directly. - init_
display_ 🔒threading - Enable libX11’s internal locking for this process and record the outcome
for
threading_state. A no-op off Linux, which has no Xlib. - on_
main_ 🔒thread - Run
readon the windowing system’s main thread and hand back its result. - threading_
init_ 🔒result - Map
XInitThreads’ return code (non-zero on success). Pure so both outcomes are unit-testable on every OS without an X server. - threading_
state 🔒 - This process’ Xlib locking state, for the startup banner.