From aa25fd4f7afa922e85868db8b9ef20dced8959c2 Mon Sep 17 00:00:00 2001 From: quasar Date: Sat, 10 Oct 2026 22:26:53 +0000 Subject: [PATCH 1/2] Remove the dedicated sender thread from AbstractNode (#37) All sockets and the data sender now work in the node worker (main) thread instead of a separate QThread. AbstractNode::stop() now pumps the main-thread event loop while waiting for pool workers so queued socket jobs can complete (prevents deadlock). --- src/public/abstractnode.cpp | 40 ++++++++++++++++++++++++++----------- src/public/abstractnode.h | 1 - src/public/async.cpp | 5 +---- src/public/async.h | 3 ++- 4 files changed, 31 insertions(+), 18 deletions(-) diff --git a/src/public/abstractnode.cpp b/src/public/abstractnode.cpp index b0846a57..265145ed 100644 --- a/src/public/abstractnode.cpp +++ b/src/public/abstractnode.cpp @@ -54,14 +54,9 @@ AbstractNode::AbstractNode( QObject *ptr): initThreadId(); - _senderThread = new QThread(); - _senderThread->setObjectName("Sender"); - - _senderThread->start(); - - // This object moving to _senderThread. - _dataSender = new DataSender(_senderThread); - _socketWorker = new AsyncLauncher(_senderThread); + // All sockets and the data sender are created and work in the node worker thread (main thread). + _dataSender = new DataSender(mainThreadID()); + _socketWorker = new AsyncLauncher(mainThreadID()); _tasksheduller = new TaskScheduler(); _apiVersionParser = new APIVersionParser(this); @@ -89,9 +84,6 @@ AbstractNode::AbstractNode( QObject *ptr): AbstractNode::~AbstractNode() { - _senderThread->quit(); - _senderThread->wait(); - for (auto it: std::as_const(_receiveData)) { delete it; } @@ -99,7 +91,6 @@ AbstractNode::~AbstractNode() { _receiveData.clear(); delete _dataSender; - delete _senderThread; delete _socketWorker; delete _tasksheduller; delete _apiVersionParser; @@ -151,6 +142,31 @@ void AbstractNode::stop() { } _connectionsMutex.unlock(); + // Pool workers can be blocked while they wait for a job that was posted + // to the main thread (for example, socket writes executed by the data + // sender). Only this thread can deliver those jobs, so it must keep + // processing events until all workers are finished. Otherwise this + // method would wait for the workers and the workers would wait for this + // thread (deadlock). + const auto deadline = QDeadlineTimer(WAIT_TIME); + while (!deadline.hasExpired()) { + bool hasWork = false; + _workersMutex.lock(); + for (const auto &worker : _workers) { + if (worker && !worker->isFinished()) { + hasWork = true; + break; + } + } + _workersMutex.unlock(); + + if (!hasWork) { + break; + } + + QCoreApplication::processEvents(QEventLoop::AllEvents, 100); + } + _workersMutex.lock(); for (auto it: std::as_const(_workers)) { if (!it->isFinished()) { diff --git a/src/public/abstractnode.h b/src/public/abstractnode.h index c61ae62b..8aa665e1 100644 --- a/src/public/abstractnode.h +++ b/src/public/abstractnode.h @@ -789,7 +789,6 @@ private slots: DataSender * _dataSender = nullptr; AsyncLauncher * _socketWorker = nullptr; - QThread *_senderThread = nullptr; TaskScheduler *_tasksheduller = nullptr; APIVersionParser *_apiVersionParser = nullptr; diff --git a/src/public/async.cpp b/src/public/async.cpp index cecfc4a6..14538981 100644 --- a/src/public/async.cpp +++ b/src/public/async.cpp @@ -5,7 +5,6 @@ * of this license document, but changing it is not allowed. */ -#include "abstractnode.h" #include "async.h" #include @@ -62,9 +61,7 @@ void Async::asyncHandler(Job job, } void Async::threadAnalize(QThread *thread) { - QThread * mainThread = AbstractNode::mainThreadID(); - - debug_assert((mainThread != thread) && thread, "You try create async object into main thread"); + debug_assert(thread, "Async object must be initialized with a valid work thread"); } bool Async::waitFor(bool *condition, int timeout, bool freaze) const { diff --git a/src/public/async.h b/src/public/async.h index 9553f16a..0bfc7ed5 100644 --- a/src/public/async.h +++ b/src/public/async.h @@ -48,7 +48,8 @@ class HEARTSHARED_EXPORT Async: public QObject { * @brief Async This is default constructor of the async object. * @param thread This is work thread of the async object. * @param ptr This is pointer to qtparent of this object. - * @note @a thread must be different of the main thread. + * @note @a thread must be a valid thread. It is allowed to use the node worker (main) thread: + * in this case all jobs are executed directly in that thread. */ Async(QThread* thread); From c180dab90c06c8d809c575d705c2d1cdacc6a8dd Mon Sep 17 00:00:00 2001 From: Andrei Yankovich Date: Sun, 11 Oct 2026 17:53:04 +0200 Subject: [PATCH 2/2] Apply suggestion from @EndrII just for trigger ci Signed-off-by: Andrei Yankovich --- src/public/async.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/public/async.h b/src/public/async.h index 0bfc7ed5..7566b92d 100644 --- a/src/public/async.h +++ b/src/public/async.h @@ -49,7 +49,7 @@ class HEARTSHARED_EXPORT Async: public QObject { * @param thread This is work thread of the async object. * @param ptr This is pointer to qtparent of this object. * @note @a thread must be a valid thread. It is allowed to use the node worker (main) thread: - * in this case all jobs are executed directly in that thread. + * in this case all jobs are executed directly in that thread. */ Async(QThread* thread);