Unsolved Multithread applications blocking when using QDBus::BlockWithGui
-
Hello,
I am using Qt 5.13 to develop a linux multithread application with DBus.
Each thread is calling a dbus method in a loop, and I want to use the QBus::CallMode "BlockWithGui" to prevent any blocking so that I can use signals and slots and keep using the event loop in threads.
When I don't use this mode, everything goes ok. But when I use it, one of the thread always get blocked at some point.
The code pasted is just a sample so that you can easily test and it only reflects the problem I am having in my application.
CMakeLists.txt
CMAKE_MINIMUM_REQUIRED(VERSION 2.8) PROJECT(scenario-test) FIND_PACKAGE(Qt5 COMPONENTS Core DBus Concurrent REQUIRED) SET(CMAKE_INCLUDE_CURRENT_DIR ON) set(${PROJECT_NAME}_moc_headers WorkerThread.hh ) SET(${PROJECT_NAME}_HEADERS ${${PROJECT_NAME}_moc_headers} ) qt5_wrap_cpp(${PROJECT_NAME}_mocs ${${PROJECT_NAME}_moc_headers}) SET(${PROJECT_NAME}_SOURCES main.cpp WorkerThread.cpp ) SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11 -pthread") ADD_EXECUTABLE(${PROJECT_NAME} ${${PROJECT_NAME}_SOURCES} ${${PROJECT_NAME}_HEADERS} ${${PROJECT_NAME}_mocs}) target_link_libraries(${PROJECT_NAME} ${QJSONLIB} dl Qt5::Core Qt5::DBus) INSTALL(TARGETS ${PROJECT_NAME} DESTINATION bin) INSTALL(FILES ${${PROJECT_NAME}_HEADERS} DESTINATION "include/${PROJECT_NAME}")
main.cpp
#include <QtCore/QCoreApplication> #include <QTimer> #include <QThread> #include <QDebug> #include <QVector> #include <QDBusConnection> #include "WorkerThread.hh" int main(int ac, char** av) { QCoreApplication app(ac, av); QCoreApplication::setApplicationName("scenario-test"); QTimer::singleShot(0, []() { QVector<QThread *> threads; if (!QDBusConnection::sessionBus().isConnected()) { qCritical() << "Cannot connect to the D-Bus system bus.\n" << "please check your system settings and try again.\n"; return; } for (int i = 0; i < 2; i++) { WorkerThread *worker = new WorkerThread(); QThread *thread = new QThread(); worker->moveToThread(thread); QObject::connect(thread, &QThread::finished, worker, &QObject::deleteLater); QObject::connect(thread, &QThread::started, worker, &WorkerThread::run); thread->start(); threads.push_back(thread); } for (int i = 0; i < 2; i++) { qDebug() << "Waiting for thread id : " << i; threads[i]->wait(); qDebug() << "Done waiting for thread id : " << i; threads[i]->quit(); } QCoreApplication::quit(); }); int status = app.exec(); qDebug() << "Finished"; return status; }
WorkerThread.hh
#pragma once #include <QThread> #include <QObject> #include <QDebug> #include <QDBusInterface> class WorkerThread : public QObject { Q_OBJECT public: WorkerThread(QObject* parent = nullptr); ~WorkerThread(); public slots: void run(); };
WorkerThread.cpp
#include <QDBusError> #include <QTimer> #include <QDBusInterface> #include "WorkerThread.hh" #include <QDBusReply> #include <QJsonDocument> #include <QJsonObject> WorkerThread::WorkerThread(QObject* parent): QObject(parent) {} WorkerThread::~WorkerThread() {} void WorkerThread::run() { for (int i = 0; i < 100; ++i) { qDebug() << "[" << QThread::currentThread() << "] call before"; QDBusInterface inter("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", QDBusConnection::sessionBus()); auto namesList = inter.call(QDBus::BlockWithGui, "ListNames").arguments().at(0).toList(); qDebug() << "[" << QThread::currentThread() << "] call after"; } QThread::currentThread()->quit(); }
After compiling, you can run the executable until the blocking appears. I see one of the thread is stuck at "call before" but does not print "call after", it looks like its event loop is stuck.
Do you guys have any idea why using this QDBus mode is blocking thread at some point ?
-
I would guess that the underlying QDBusInterface stuff is not threadsafe so you will get into a deadlock.
-
Hmm I hope its not the case because in my real application the DBus service I am calling can respond 10 seconds later, and I need to be able calling it in multiple threads...
I have try to make a shared instance of QDBusInterface in the WorkerThread class and use a mutex :
WorkerThread.hh
#pragma once #include <QThread> #include <QObject> #include <QDebug> #include <QDBusInterface> #include <QMutex> class WorkerThread : public QObject { Q_OBJECT public: WorkerThread(QObject* parent = nullptr); ~WorkerThread(); public slots: void run(); private: static QDBusInterface *_iface; static QMutex _mutex; };
WorkerThread.cpp
#include <QDBusError> #include <QTimer> #include <QDBusInterface> #include "WorkerThread.hh" #include <QDBusReply> #include <QJsonDocument> #include <QJsonObject> QDBusInterface *WorkerThread::_iface = new QDBusInterface("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", QDBusConnection::sessionBus()); QMutex WorkerThread::_mutex; WorkerThread::WorkerThread(QObject* parent): QObject(parent) {} WorkerThread::~WorkerThread() {} void WorkerThread::run() { for (int i = 0; i < 100; ++i) { qDebug() << "[" << QThread::currentThread() << "] call before"; this->_mutex.lock(); auto namesList = this->_iface->call(QDBus::BlockWithGui, "ListNames").arguments().at(0).toList(); this->_mutex.unlock(); qDebug() << "[" << QThread::currentThread() << "] call after"; } QThread::currentThread()->quit(); }
But it does not solve the problem either, sometimes one thread is still blocked.