fix: keep event handlers alive during dispatch

This commit is contained in:
开心 2026-07-10 22:33:04 +08:00 committed by Chris Rizzitello
parent 39bf4fbe84
commit 6934dbe62b
6 changed files with 101 additions and 10 deletions

View file

@ -167,12 +167,12 @@ bool EventQueue::getEvent(Event &event, double timeout)
bool EventQueue::dispatchEvent(const Event &event) bool EventQueue::dispatchEvent(const Event &event)
{ {
void *target = event.getTarget(); void *target = event.getTarget();
if (const auto *type_handler = getHandler(event.getType(), target); type_handler) { if (auto typeHandler = getHandler(event.getType(), target); typeHandler.has_value()) {
(*type_handler)(event); (*typeHandler)(event);
return true; return true;
} }
if (const auto *any_handler = getHandler(EventTypes::Unknown, target); any_handler) { if (auto anyHandler = getHandler(EventTypes::Unknown, target); anyHandler.has_value()) {
(*any_handler)(event); (*anyHandler)(event);
return true; return true;
} }
return false; return false;
@ -295,17 +295,17 @@ void EventQueue::removeHandlers(void *target)
} }
} }
const EventQueue::EventHandler *EventQueue::getHandler(EventTypes type, void *target) const std::optional<EventQueue::EventHandler> EventQueue::getHandler(EventTypes type, void *target) const
{ {
std::scoped_lock lock{m_mutex}; std::scoped_lock lock{m_mutex};
if (HandlerTable::const_iterator index = m_handlers.find(target); index != m_handlers.end()) { if (HandlerTable::const_iterator index = m_handlers.find(target); index != m_handlers.end()) {
const TypeHandlerTable &typeHandlers = index->second; const TypeHandlerTable &typeHandlers = index->second;
TypeHandlerTable::const_iterator index2 = typeHandlers.find(type); TypeHandlerTable::const_iterator index2 = typeHandlers.find(type);
if (index2 != typeHandlers.end()) { if (index2 != typeHandlers.end()) {
return &index2->second; return index2->second;
} }
} }
return nullptr; return std::nullopt;
} }
uint32_t EventQueue::saveEvent(Event &&event) uint32_t EventQueue::saveEvent(Event &&event)

View file

@ -16,6 +16,7 @@
#include <map> #include <map>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <optional>
#include <queue> #include <queue>
#include <set> #include <set>
@ -50,7 +51,7 @@ public:
void waitForReady() const override; void waitForReady() const override;
private: private:
const EventHandler *getHandler(EventTypes type, void *target) const; std::optional<EventHandler> getHandler(EventTypes type, void *target) const;
uint32_t saveEvent(Event &&event); uint32_t saveEvent(Event &&event);
Event removeEvent(uint32_t eventID); Event removeEvent(uint32_t eventID);
bool hasTimerExpired(Event &event); bool hasTimerExpired(Event &event);

View file

@ -67,8 +67,8 @@ public:
Looks up the dispatcher for the event's target and invokes it. Looks up the dispatcher for the event's target and invokes it.
Returns true iff a dispatcher exists for the target. 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 The handler may unregister itself while running. The event target object must remain alive until
removeHandlers(). the handler starts.
*/ */
virtual bool dispatchEvent(const Event &event) = 0; virtual bool dispatchEvent(const Event &event) = 0;

View file

@ -37,3 +37,10 @@ create_test(
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/src/lib/base" 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"
)

View file

@ -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 <QTest>
#include <memory>
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<int>(1);
std::weak_ptr<int> 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)

View file

@ -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 <QObject>
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;
};