From ba517e8ff5a1a6e126e55b1bf5e6bc4ac689a7e1 Mon Sep 17 00:00:00 2001 From: YaNing Lu Date: Mon, 13 Apr 2026 11:11:04 +0800 Subject: [PATCH] fix: prevent deadlock during Wayland client destruction wl_display_flush_clients uses wl_list_for_each_safe to iterate the client list. The _safe variant pre-saves the next pointer so that removing the *current* node is safe. However, when wl_client_destroy is called on the current client, its destroy_signal may cascade and destroy the pre-saved *next* client as well. The destroyed next client undergoes wl_list_remove + wl_list_init, making its link self-referencing (prev = next = self). On the next loop iteration, client == next and the termination condition (&client->link != head) is always true, causing an infinite loop with 100% CPU. Fix: - Replace wl_display_flush_clients with safeFlushClients() that detects self-linked nodes (node->next == node) and stops traversal. Unlike wl_display_flush_clients, safeFlushClients uses wl_client_flush (pure sendmsg I/O) instead of wl_connection_flush + wl_client_destroy, so it cannot trigger cascading client destruction during traversal. - Separate dispatch and flush into different callbacks: activated does only wl_event_loop_dispatch, aboutToBlock does only safeFlushClients. This matches the upstream wl_display_run order (flush before dispatch). - Add isProcessingEvents reentrant guard to prevent aboutToBlock from re-entering during dispatch. - In stop(), disconnect event handlers before destroying clients to prevent callbacks from firing during teardown. --- waylib/src/server/kernel/private/wserver_p.h | 5 +- waylib/src/server/kernel/wserver.cpp | 48 ++++++++++++++++---- 2 files changed, 42 insertions(+), 11 deletions(-) diff --git a/waylib/src/server/kernel/private/wserver_p.h b/waylib/src/server/kernel/private/wserver_p.h index 81173be27..73f22d52c 100644 --- a/waylib/src/server/kernel/private/wserver_p.h +++ b/waylib/src/server/kernel/private/wserver_p.h @@ -1,4 +1,4 @@ -// Copyright (C) 2023 JiDe Zhang . +// Copyright (C) 2023-2026 JiDe Zhang . // SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #pragma once @@ -42,6 +42,9 @@ class Q_DECL_HIDDEN WServerPrivate : public WObjectPrivate GlobalFilterFunc globalFilterFunc = nullptr; void *globalFilterFuncData = nullptr; + + bool isProcessingEvents = false; + void safeFlushClients(); }; WAYLIB_SERVER_END_NAMESPACE diff --git a/waylib/src/server/kernel/wserver.cpp b/waylib/src/server/kernel/wserver.cpp index 0fa3eb8c7..61a9af050 100644 --- a/waylib/src/server/kernel/wserver.cpp +++ b/waylib/src/server/kernel/wserver.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2023 JiDe Zhang . +// Copyright (C) 2023-2026 JiDe Zhang . // SPDX-License-Identifier: Apache-2.0 OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -104,18 +105,26 @@ void WServerPrivate::init() loop = wl_display_get_event_loop(display->handle()); int fd = wl_event_loop_get_fd(loop); - auto processWaylandEvents = [this] { + sockNot.reset(new QSocketNotifier(fd, QSocketNotifier::Read)); + QObject::connect(sockNot.get(), &QSocketNotifier::activated, q, [this] { + if (isProcessingEvents) + return; + + QScopedValueRollback guard(isProcessingEvents, true); + int ret = wl_event_loop_dispatch(loop, 0); if (ret) fprintf(stderr, "wl_event_loop_dispatch error: %d\n", ret); - wl_display_flush_clients(display->handle()); - }; - - sockNot.reset(new QSocketNotifier(fd, QSocketNotifier::Read)); - QObject::connect(sockNot.get(), &QSocketNotifier::activated, q, processWaylandEvents); + }); + // Match upstream wl_display_run order: flush before dispatch. QAbstractEventDispatcher *dispatcher = QThread::currentThread()->eventDispatcher(); - QObject::connect(dispatcher, &QAbstractEventDispatcher::aboutToBlock, q, processWaylandEvents); + QObject::connect(dispatcher, &QAbstractEventDispatcher::aboutToBlock, q, [this] { + if (isProcessingEvents) + return; + + safeFlushClients(); + }); for (auto socket : std::as_const(sockets)) initSocket(socket); @@ -127,6 +136,12 @@ void WServerPrivate::stop() { W_Q(WServer); + // Disconnect event handlers BEFORE destroying clients to prevent + // callbacks from firing during client destruction. + sockNot.reset(); + if (auto *dispatcher = QThread::currentThread()->eventDispatcher()) + QObject::disconnect(dispatcher, nullptr, q, nullptr); + if (display) wl_display_destroy_clients(*display); @@ -137,9 +152,22 @@ void WServerPrivate::stop() (*i)->destroy(q); delete *i; } +} - sockNot.reset(); - QThread::currentThread()->eventDispatcher()->disconnect(q); +// Replace wl_display_flush_clients: its wl_list_for_each_safe loop deadlocks +// when destroy-signal cascades make the pre-saved next node self-linked. +void WServerPrivate::safeFlushClients() +{ + struct wl_list *head = wl_display_get_client_list(display->handle()); + struct wl_list *node = head->next; + while (node != head) { + // Self-linked node: already destroyed, stop to avoid infinite loop. + if (node->next == node) + break; + struct wl_list *next = node->next; + wl_client_flush(wl_client_from_link(node)); + node = next; + } } void WServerPrivate::initSocket(WSocket *socketServer)