diff --git a/app/backend/computermanager.cpp b/app/backend/computermanager.cpp index 7e82e0de9..187c6e6d5 100644 --- a/app/backend/computermanager.cpp +++ b/app/backend/computermanager.cpp @@ -397,6 +397,8 @@ void ComputerManager::startPolling() }); connect(m_MultiSeatDiscovery, &MultiSeatDiscovery::aboutToPoll, this, &ComputerManager::updateMultiSeatProbeTargets); + // start() only queues the first poll. It must not poll synchronously: this function holds + // m_Lock for write, and updateMultiSeatProbeTargets() takes it for read. m_MultiSeatDiscovery->start(); // Start polling threads for each known host @@ -438,6 +440,9 @@ void ComputerManager::startPollingComputer(NvComputer* computer) // beside share one address — probing it twice would just double the requests for nothing. Local // addresses only: seat ports are not port-forwarded, so probing a remote address would be four // guaranteed failures per tick against someone else's network. +// +// ⛔ Takes m_Lock for read. It must never run on a thread that already holds m_Lock, because +// QReadWriteLock is not recursive and that thread would wait on itself. void ComputerManager::updateMultiSeatProbeTargets() { if (m_MultiSeatDiscovery == nullptr) { diff --git a/app/backend/multiseatdiscovery.cpp b/app/backend/multiseatdiscovery.cpp index af1eb6c6e..a22046fe5 100644 --- a/app/backend/multiseatdiscovery.cpp +++ b/app/backend/multiseatdiscovery.cpp @@ -26,8 +26,15 @@ MultiSeatDiscovery::~MultiSeatDiscovery() void MultiSeatDiscovery::start() { m_Timer->start(); - // Poll immediately on start, don't wait for first interval - poll(); + + // Poll soon after start rather than waiting a full interval, but from the event loop, never + // from inside start(). + // + // ⛔ Do not call poll() directly here. ComputerManager::startPolling() calls start() while it + // holds m_Lock for write, and poll() emits aboutToPoll(), whose slot takes that same lock for + // read on the same thread. QReadWriteLock is not recursive, so the main thread waited on + // itself forever: 6.3.4 hung ("Not Responding") on every launch. + QMetaObject::invokeMethod(this, &MultiSeatDiscovery::poll, Qt::QueuedConnection); } void MultiSeatDiscovery::stop() @@ -42,6 +49,11 @@ void MultiSeatDiscovery::setHostsToProbe(const QStringList& addresses) void MultiSeatDiscovery::poll() { + // The first poll is queued by start(). If stop() ran before it was delivered, do nothing. + if (!m_Timer->isActive()) { + return; + } + // Refresh the address list first — hosts may have been added or removed since the last tick. emit aboutToPoll();