From 6934dbe62b33296ddee9b47dacce0c846566c1bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=80=E5=BF=83?= Date: Fri, 10 Jul 2026 22:33:04 +0800 Subject: [PATCH] fix: keep event handlers alive during dispatch --- src/lib/base/EventQueue.cpp | 14 +++---- src/lib/base/EventQueue.h | 3 +- src/lib/base/IEventQueue.h | 4 +- src/unittests/base/CMakeLists.txt | 7 ++++ src/unittests/base/EventQueueTests.cpp | 58 ++++++++++++++++++++++++++ src/unittests/base/EventQueueTests.h | 25 +++++++++++ 6 files changed, 101 insertions(+), 10 deletions(-) create mode 100644 src/unittests/base/EventQueueTests.cpp create mode 100644 src/unittests/base/EventQueueTests.h diff --git a/src/lib/base/EventQueue.cpp b/src/lib/base/EventQueue.cpp index 0e2f21544..638e39fe9 100644 --- a/src/lib/base/EventQueue.cpp +++ b/src/lib/base/EventQueue.cpp @@ -167,12 +167,12 @@ bool EventQueue::getEvent(Event &event, double timeout) bool EventQueue::dispatchEvent(const Event &event) { void *target = event.getTarget(); - if (const auto *type_handler = getHandler(event.getType(), target); type_handler) { - (*type_handler)(event); + if (auto typeHandler = getHandler(event.getType(), target); typeHandler.has_value()) { + (*typeHandler)(event); return true; } - if (const auto *any_handler = getHandler(EventTypes::Unknown, target); any_handler) { - (*any_handler)(event); + if (auto anyHandler = getHandler(EventTypes::Unknown, target); anyHandler.has_value()) { + (*anyHandler)(event); return true; } return false; @@ -295,17 +295,17 @@ void EventQueue::removeHandlers(void *target) } } -const EventQueue::EventHandler *EventQueue::getHandler(EventTypes type, void *target) const +std::optional EventQueue::getHandler(EventTypes type, void *target) const { std::scoped_lock lock{m_mutex}; if (HandlerTable::const_iterator index = m_handlers.find(target); index != m_handlers.end()) { const TypeHandlerTable &typeHandlers = index->second; TypeHandlerTable::const_iterator index2 = typeHandlers.find(type); if (index2 != typeHandlers.end()) { - return &index2->second; + return index2->second; } } - return nullptr; + return std::nullopt; } uint32_t EventQueue::saveEvent(Event &&event) diff --git a/src/lib/base/EventQueue.h b/src/lib/base/EventQueue.h index c84543981..921058182 100644 --- a/src/lib/base/EventQueue.h +++ b/src/lib/base/EventQueue.h @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -50,7 +51,7 @@ public: void waitForReady() const override; private: - const EventHandler *getHandler(EventTypes type, void *target) const; + std::optional getHandler(EventTypes type, void *target) const; uint32_t saveEvent(Event &&event); Event removeEvent(uint32_t eventID); bool hasTimerExpired(Event &event); diff --git a/src/lib/base/IEventQueue.h b/src/lib/base/IEventQueue.h index b1f38eb53..aa1cc6057 100644 --- a/src/lib/base/IEventQueue.h +++ b/src/lib/base/IEventQueue.h @@ -67,8 +67,8 @@ public: Looks up the dispatcher for the event's target and invokes it. Returns true iff a dispatcher exists for the target. - The caller must ensure that the target of the event is not removed by removeHandler() or - removeHandlers(). + The handler may unregister itself while running. The event target object must remain alive until + the handler starts. */ virtual bool dispatchEvent(const Event &event) = 0; diff --git a/src/unittests/base/CMakeLists.txt b/src/unittests/base/CMakeLists.txt index 661de0d7f..45836d1d7 100644 --- a/src/unittests/base/CMakeLists.txt +++ b/src/unittests/base/CMakeLists.txt @@ -37,3 +37,10 @@ create_test( WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/src/lib/base" ) +create_test( + NAME EventQueueTests + DEPENDS base + LIBS arch mt + SOURCE EventQueueTests.cpp + WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/src/lib/base" +) diff --git a/src/unittests/base/EventQueueTests.cpp b/src/unittests/base/EventQueueTests.cpp new file mode 100644 index 000000000..c76eda06b --- /dev/null +++ b/src/unittests/base/EventQueueTests.cpp @@ -0,0 +1,58 @@ +/* + * Deskflow -- mouse and keyboard sharing utility + * SPDX-FileCopyrightText: (C) 2026 Deskflow Developers + * SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception + */ + +#include "EventQueueTests.h" + +#include "base/EventQueue.h" + +#include + +#include + +void EventQueueTests::initTestCase() +{ + m_arch.init(); +} + +void EventQueueTests::dispatchEvent_noHandler_returnsFalse() +{ + EventQueue events; + + QVERIFY(!events.dispatchEvent(Event(EventTypes::ClientDisconnected, this))); +} + +void EventQueueTests::dispatchEvent_noTypeHandler_dispatchesUnknownHandler() +{ + EventQueue events; + bool fallbackCalled = false; + events.addHandler(EventTypes::Unknown, this, [&fallbackCalled](const Event &) { fallbackCalled = true; }); + + QVERIFY(events.dispatchEvent(Event(EventTypes::ClientDisconnected, this))); + QVERIFY(fallbackCalled); +} + +void EventQueueTests::dispatchEvent_handlerRemovesItself_keepsHandlerAliveUntilReturn() +{ + EventQueue events; + auto handlerLifetime = std::make_shared(1); + std::weak_ptr handlerLifetimeObserver = handlerLifetime; + bool handlerAliveAfterRemoval = false; + + events.addHandler( + EventTypes::ClientDisconnected, this, + [this, &events, &handlerLifetimeObserver, &handlerAliveAfterRemoval, handlerLifetime](const Event &) { + events.removeHandler(EventTypes::ClientDisconnected, this); + handlerAliveAfterRemoval = handlerLifetime != nullptr && !handlerLifetimeObserver.expired(); + } + ); + handlerLifetime.reset(); + + QVERIFY(events.dispatchEvent(Event(EventTypes::ClientDisconnected, this))); + QVERIFY(handlerAliveAfterRemoval); + QVERIFY(handlerLifetimeObserver.expired()); +} + +QTEST_MAIN(EventQueueTests) diff --git a/src/unittests/base/EventQueueTests.h b/src/unittests/base/EventQueueTests.h new file mode 100644 index 000000000..8a5d4186a --- /dev/null +++ b/src/unittests/base/EventQueueTests.h @@ -0,0 +1,25 @@ +/* + * Deskflow -- mouse and keyboard sharing utility + * SPDX-FileCopyrightText: (C) 2026 Deskflow Developers + * SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception + */ + +#pragma once + +#include "arch/Arch.h" + +#include + +class EventQueueTests : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase(); + void dispatchEvent_noHandler_returnsFalse(); + void dispatchEvent_noTypeHandler_dispatchesUnknownHandler(); + void dispatchEvent_handlerRemovesItself_keepsHandlerAliveUntilReturn(); + +private: + Arch m_arch; +};