Skip to main content

Module display

Module display 

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

XlibLocking 🔒
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_threading so 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 of run(), before tauri_plugin_log is installed, so a log::warn! there would go nowhere.

Functions§

gave_up 🔒
Report a read the main thread never answered and degrade to None. why distinguishes the cases on its own: a RecvTimeoutError says 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 read on 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.