https://bugs.kde.org/show_bug.cgi?id=527147

steven <[email protected]> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
                 CC|                            |[email protected]

--- Comment #2 from steven <[email protected]> ---
Created attachment 197161
  --> https://bugs.kde.org/attachment.cgi?id=197161&action=edit
plasma-hang-bt

I can add a second, fully symbolized hang site in the same teardown chain,
captured red-handed during a deterministic login-race repro. The description's
backtrace hangs in QQmlEngine (Qt6Quick frames); this one hangs one library
over, in kguiaddons — and it is a true deadlock cycle, not just a slow join.

Environment: Plasma 6.8.0 (plasma-workspace 6.8.0-1), Arch Linux, kernel
7.2.8-1-cachyos, Wayland session, kguiaddons 6.31.0-1.1.

Repro: plasmashell starts at login (systemd user service); an autostart entry
runs `plasmashell --replace` while the session's Wayland connection to KWin is
still establishing. The old shell is told to quit, never finishes quitting, and
the replacement process dies with "Failed to register name" because the old
shell still holds org.kde.plasmashell. End state: frozen zombie shell, no new
shell. From a settled session, every quit path (SIGTERM, D-Bus /MainApplication
quit, --replace, unit stop) exits cleanly — verified across seven variants. The
hang needs the quit to happen during session startup.

Main thread of the hung shell (gdb attach; plasma-workspace-debug +
libplasma-debug installed):

Thread 1 (LWP 1103 "plasmashell"):
#0  futex_wait_cancelled (libc)
#1  futex_wait (libc)
#2  pthread_cond_wait (libc)
#3  (Qt6Core internal)
#4  QThread::wait(QDeadlineTimer)                    libQt6Core
#5  (local symbol, libKF6GuiAddons.so.6.31.0 +0x2b417)
#6  (local symbol, libKF6GuiAddons.so.6.31.0 +0x2b472)
#7  QObjectPrivate::deleteChildren()                 libQt6Core
#8  QObject::~QObject()                              libQt6Core
#9  main () at shell/main.cpp:223                    plasma-workspace 6.8.0

main.cpp:223 is `return app.exec();` — the event loop already returned; the
hang is in the destructor chain of the local QCoreApplication during unwinding.
Same shape as the description's backtrace, but frames #5/#6 are in
libKF6GuiAddons, not libQt6Quick.

Frames #5/#6 are local (non-exported) symbols; I resolved them by offset
against the shipped .so (symbolization note at the end). The only QThread
wait() call site in kguiaddons 6.31.0 src/ is
src/systemclipboard/waylandclipboard.cpp:

    WaylandClipboard::~WaylandClipboard()
    {
        if (m_thread && m_thread->isRunning()) {
            m_thread->requestInterruption();
            m_thread->syncQueue();
            m_thread->wait();          // line 631 — no deadline
        }
    }

The thread being joined is also in the dump, and this is where it stops being a
slow join and becomes a deadlock cycle:

Thread 2 (LWP 1624 "QThread"):
#0  syscall ()                                        libc
#1  QBasicMutex::lockInternal()                       libQt6Core
#2  QRecursiveMutex::tryLock(QDeadlineTimer)          libQt6Core
#3  (local symbol, libKF6GuiAddons.so.6)              libKF6GuiAddons
#4  (Qt6Core thread start)

Thread 16 (LWP 1224 "WaylandEventThr"):
#0  futex_wait_cancelled (libc)
#1  pthread_cond_wait (libc)
#2  QWaitCondition::wait(QMutex*, QDeadlineTimer)     libQt6Core
#3  (Qt6WaylandClient display roundtrip wait)         libQt6WaylandClient
#4  (Qt6WaylandClient event thread loop)

Cycle: the main thread enters ~WaylandClipboard holding kguiaddons' internal
mutex, then blocks in m_thread->wait(). Thread 2 (the clipboard's own worker
thread) is blocked trying to lock that same mutex inside libKF6GuiAddons, so it
can never reach thread completion. requestInterruption() sets a flag the
blocked thread cannot observe; syncQueue() queues an event that is never
processed. Neither side can make progress. Thread 16 (a Qt6WaylandClient
ConnectionThread parked in a compositor roundtrip wait) is consistent with the
connection being mid-establishment at quit time.

Note the difference in log tells: the description's repro logs
"qt.core.qobject.connect: QObject::disconnect: Unexpected nullptr parameter";
this repro's journal has no such line. Two hang sites, one failure mode:
deadline-less QThread::wait() calls reached via ~app ->
QObjectPrivate::deleteChildren(), where the joined thread is blocked on work
that needs the main thread (or a compositor reply that a young connection will
never answer).

Suggested fix directions:
1. WaylandClipboard::~WaylandClipboard(): give wait() a deadline (e.g.
wait(5000)) and abandon/detach the thread on timeout.
2. Break the lock cycle: do not hold kguiaddons' internal mutex across the
join, or have the worker's sync path check the interruption flag before taking
the lock.
3. Audit the other deadline-less joins in the same teardown chain (the
QQmlEngine site from the description, and KKeySequenceRecorder's
threaded-connection pattern) — fixing one site will not close this bug.

Symbolization note: the repo kguiaddons-debug (6.31.0-1) does not match the
shipped kguiaddons 6.31.0-1.1 (gdb CRC mismatch), so kguiaddons frames show as
?? in gdb. I resolved them manually: exec segment base from /proc/<pid>/maps,
eu-addr2line + nearest dynsym below the offsets. Full 17-thread backtrace
attached.

-- 
You are receiving this mail because:
You are watching all bug changes.

Reply via email to