[kwin] [Bug 523909] New: Entire Wayland session freezes 2-3.6s when a short-lived X11 window with an unresolvable WM_CLIENT_MACHINE is destroyed (blocking getaddrinfo wait in KWin::GetAddrInfo destructor on main thread)
"Dmitrii P" <[email protected]> Wed, 05 Aug 2026 22:44:59 +0000
| Newsgroups | gmane.comp.kde.devel.bugs |
|---|---|
| Message-ID | <[email protected]/> |
https://bugs.kde.org/show_bug.cgi?id=523909
Bug ID: 523909
Summary: Entire Wayland session freezes 2-3.6s when a
short-lived X11 window with an unresolvable
WM_CLIENT_MACHINE is destroyed (blocking getaddrinfo
wait in KWin::GetAddrInfo destructor on main thread)
Classification: Plasma
Product: kwin
Version First 6.7.3
Reported In:
Platform: Fedora RPMs
OS: Linux
Status: REPORTED
Severity: normal
Priority: NOR
Component: xwayland
Assignee: [email protected]
Reporter: [email protected]
Target Milestone: ---
Summary
Entire Wayland session freezes 2–3.6 s when a short-lived X11 window with an
unresolvable WM_CLIENT_MACHINE is destroyed (blocking getaddrinfo wait in
KWin::GetAddrInfo destructor on the main thread)
Description
When an X11 (XWayland) client whose `WM_CLIENT_MACHINE` property contains a
hostname that is not resolvable on the host creates a short-lived window -
typically a tooltip or popup - and that window is destroyed shortly after
mapping, KWin's main thread blocks for the full DNS resolver timeout
(2–3.6 s here). The whole desktop stops rendering: all outputs freeze,
Wayland round-trips stall. XWayland itself stays responsive during the
freeze (verified with an independent X11 client measuring GetInputFocus
round-trips: < 2 ms throughout).
This happens in practice for **any GUI app running inside a container**
(podman/docker/toolbox-style sandboxes) whose container hostname differs
from the host's, and would equally affect `ssh -X` clients from hosts not
in DNS. Qt and GTK set `WM_CLIENT_MACHINE` on every window, and tooltips
live shorter than the failed lookup, so every tooltip shown+hidden freezes
the compositor for seconds. Wayland-native apps are unaffected; the same
apps on the host (resolvable hostname) are unaffected.
Analysis
`KWin::ClientMachine` starts an async `getaddrinfo()` (`KWin::GetAddrInfo`)
to decide whether the client is local. If the X11 window is destroyed while
the lookup is still pending, the cleanup path blocks the compositor:
Main thread (TID of kwin_wayland), identical in ~30 consecutive samples
(eu-stack, 0.1 s interval) across three reproduced freezes:
#0 __syscall_cancel_arch
#1 __internal_syscall_cancel
#2 __syscall_cancel
#3 __GI___futex_abstimed_wait_cancelable64
#4 pthread_cond_wait@@GLIBC_2.3.2
#5 QWaitCondition::wait(QMutex*, QDeadlineTimer)
#6 QFutureInterfaceBase::waitForFinished()
#7 KWin::GetAddrInfo::~GetAddrInfo()
#8 KWin::GetAddrInfo::~GetAddrInfo()
#9 QObjectPrivate::deleteChildren()
#10 QObject::~QObject()
#11 KWin::ClientMachine::~ClientMachine()
#12 QObjectPrivate::deleteChildren()
#13 QObject::~QObject()
#14 KWin::X11Window::~X11Window()
The freeze duration equals the resolver timeout for the nonexistent name.
Expected behavior: the pending lookup should be cancelled/detached (or its
result delivered via the event loop and discarded), never awaited
synchronously on the compositor thread.
Steps to Reproduce
1. Run a Plasma Wayland session.
2. Run the self-contained Python reproducer below (no dependencies beyond
python3) against the session's XWayland display. It creates an
override-redirect window, sets `WM_CLIENT_MACHINE` to an unresolvable
name, maps it, then unmaps+destroys it 0.3 s later - exactly a tooltip's
lifecycle.
3. Observe the whole desktop freeze for 2–3.6 s
(`journalctl --user -b | grep "The main thread was hanging"` also fires).
Not every cycle necessarily freezes: the resolver's negative cache can
absorb immediate repeats of the same name, so allow a few seconds between
attempts or vary the hostname argument.
4. Control: run it with your host's own hostname as argument - no freeze.
Real-world trigger: `podman run` any Qt/GTK app with X11 socket shared and a
container-specific hostname; hover any tooltip and let it hide (e.g. VLC's
toolbar tooltips).
#!/usr/bin/env python3
# usage: python3 repro.py [hostname] (default: unresolvable name -> freeze)
import os, socket, struct, sys, time
def cookie(path):
d = open(path, "rb").read(); i = 0; c = None
while i + 2 <= len(d):
i += 2
r = []
for _ in range(4):
n = struct.unpack(">H", d[i:i+2])[0]; r.append(d[i+2:i+2+n]); i +=
2 + n
if r[2] == b"MIT-MAGIC-COOKIE-1": c = r[3]
return c
def pad4(b): return b + b"\x00" * ((4 - len(b) % 4) % 4)
def rx(s, n):
b = b""
while len(b) < n:
t = s.recv(n - len(b)); assert t; b += t
return b
disp = os.environ.get("DISPLAY", ":0").split(":")[1].split(".")[0]
s = socket.socket(socket.AF_UNIX); s.connect(f"/tmp/.X11-unix/X{disp}")
ck = cookie(os.environ["XAUTHORITY"]) if os.environ.get("XAUTHORITY") else None
nm = b"MIT-MAGIC-COOKIE-1" if ck else b""
s.sendall(struct.pack("<BxHHHHxx", 0x6C, 11, 0, len(nm), len(ck or b"")) +
pad4(nm) + pad4(ck or b""))
st, _, _, ln = struct.unpack("<BxHHH", rx(s, 8)); body = rx(s, ln * 4)
assert st == 1, body[:64]
rid, = struct.unpack_from("<I", body, 4)
vlen, = struct.unpack_from("<H", body, 16)
off = 32 + (vlen + 3) // 4 * 4 + body[21] * 8
root, = struct.unpack_from("<I", body, off); depth = body[off + 38]
host = (sys.argv[1] if len(sys.argv) > 1 else "unresolvable-host-xyz").encode()
for i in range(3):
w = rid + 1 + i
s.sendall(struct.pack("<BBHIIhhHHHHII", 1, depth, 10, w, root, 100, 100,
300, 60, 0, 1, 0, 0x202)
+ struct.pack("<II", 0xFFFF00, 1)) # CreateWindow,
override-redirect
s.sendall(struct.pack("<BBHIIIBxxxI", 18, 0, 6 + (len(host) + 3) // 4, w,
36, 31, 8, len(host))
+ pad4(host)) # WM_CLIENT_MACHINE
= host
s.sendall(struct.pack("<BxHI", 8, 2, w)) # MapWindow
time.sleep(0.3)
s.sendall(struct.pack("<BxHI", 10, 2, w)) # UnmapWindow
s.sendall(struct.pack("<BxHI", 4, 2, w)) # DestroyWindow
s.sendall(struct.pack("<BxH", 43, 1)); rx(s, 32) # sync
print(f"cycle {i}: window destroyed - desktop should freeze now")
time.sleep(3)
Observed Result
Compositor main thread blocks in `QFutureInterfaceBase::waitForFinished()`
inside `~GetAddrInfo()`; all outputs frozen 2–3.6 s per destroyed window.
Expected Result
Window destruction never blocks the compositor on name resolution.
Environment
- Operating System: Fedora 44 (host), kernel 7.1.5-201.fc44.x86_64
- KDE Plasma Version: 6.7.3
- KWin version: 6.7.3
- Graphics: AMD Radeon RX 9070 XT (dGPU, display) + Ryzen iGPU, amdgpu/Mesa
- X11 clients ran inside a rootless podman container (hostname differs from
host); reproducer above needs no container.
--
You are receiving this mail because:
You are watching all bug changes.