Search code examples
c++multithreadingqtexceptionqt4

Qt: handling exceptions thrown from handlers of background threads


I know that you need to reimplement the QApplication::notify() method to properly catch exceptions thrown from the main thread's event handlers. But what about other threads? Say, I have an object with a slot, and this object lives in a QThread (with default run() method, which just calls exec()), i.e. the thread affinity of this object is the background QThread. So, where should I catch exceptions thrown from this object's slot?

IOW, how do I reimplement the notify() method of a background thread?


Solution

  • When you create your Custom Application with overriden notify method; the QThread you create uses this overriden method too (as the main thread) once it has started its own event loop

    It means in practice that if you connect any slot to the QThread::started signal; then this slot executes outside the event loop of thethread, thus not in the overriden notify method.

    Here is a sample of code that helps to understand what happen:

    #include "mainwindow.h"
    #include <QApplication>
    
    #include <QDebug>
    #include <QThread>
    #include <QTimer>
    #include <exception>
    
    class ThrowingObject : public QObject
    {
    public:
        void doThrowInNotify()
        {
            qDebug() << "I am execution on thread id" << QThread::currentThreadId() << " and I am throwing";
            throw std::exception("KBOOOM");
        }
    
        void scheduleThrow()
        {
            QTimer* singleShot = new QTimer(this);
            singleShot->setSingleShot(true);
    
            connect(singleShot, &QTimer::timeout, this, &ThrowingObject::doThrow);
    
            singleShot->start();
    
            qDebug() << "I am execution on thread id" << QThread::currentThreadId() << " and I will throw in run";
        }
    
        void doThrow()
        {
            qDebug() << "I am execution on thread id" << QThread::currentThreadId() << " and I am throwing right now";
    
            //This exception is not catched, and definitly crash the process
            throw std::exception("KBOOOM");
        }
    
        void doThrowOutsideNotify()
        {
            //wait 5s for demo purpose, this slot is called by Object2, after Object1 throw in thread1 event loop
            QThread::sleep(5);
            qDebug() << "I am execution on thread id" << QThread::currentThreadId() << " and I am throwing right now";
    
            //This exception is not catched, and definitly crash the process
            throw std::exception("FATAL KBOOOM");
        }
    
    };
    
    class ApplicationWithExceptionCatchedInNotify : public QApplication
    {
    public:
        ApplicationWithExceptionCatchedInNotify(int argc, char *argv[]) :
            QApplication(argc,argv)
        {}
    
        bool notify(QObject* receiver, QEvent *e) override
        {
            try {
                return QApplication::notify(receiver, e);
            }
            catch(std::runtime_error e)
            {
                qDebug() << "std::runtime_error in thread : " << QThread::currentThreadId();
                qDebug() << e.what();
            }
            catch(std::exception e)
            {
                qDebug() << "std::exception in thread : " << QThread::currentThreadId();
                qDebug() << e.what();
            }
            catch(...)
            {
                qDebug() << "exception thread : " << QThread::currentThreadId();
            }
    
            qDebug() << "catch in notify ";
            return false;
        }
    
    };
    
    
    
    int main(int argc, char *argv[])
    {
    ApplicationWithExceptionCatchedInNotify app(argc, argv);
    
    qDebug() << "Main QThread id" << QThread::currentThreadId();
    
    //Object o1 will throw in its event loop (in notify)
    QThread thread1;
    ThrowingObject o1;
    o1.moveToThread(&thread1);
    
    QObject::connect(&thread1, &QThread::started, &o1, &ThrowingObject::scheduleThrow);
    
    thread1.start();
    
    //Object o2 will throw before the event loop is installed
    QThread thread2;
    ThrowingObject o2;
    o2.moveToThread(&thread2);
    //Connect to started signal.
    QObject::connect(&thread2, &QThread::started, &o2, &ThrowingObject::doThrowOutsideNotify);
    thread2.start();
    
    app.exec();
    }
    

    Running this code sample on windows , Qt 5.9 with Qt creator gives for example :

    Output :

    Main QThread id 0x11e4
    I am execution on thread id 0x180c  and I will throw in run
    I am execution on thread id 0x180c  and I am throwing right now
    std::exception in thread :  0x180c
    KBOOOM
    catch in notify 
    I am execution on thread id 0x27b8  and I am throwing right now
    

    and a Microsoft Visual Studio Runtime Library pop up crying that :

    Microsoft Visual C++ Runtime Library
    
    Debug Error!
    
    Program: ...ad-Desktop_Qt_5_9_2_MSVC2017_64bit-Debug\debug\DemoThread.exe
    
    abort() has been called
    
    (Press Retry to debug the application)
    

    If you put breakpoint; once can realize that :

    Object o1 throws in QThread::exec methods, going down the callstack we can see we are in ApplicationWithExceptionCatchedInNotify::Notify

    1 ThrowingObject::doThrow main.cpp 35 0x7ff66615352b 2 QtPrivate::FunctorCall,QtPrivate::List<>,void,void (__cdecl ThrowingObject:: *)(void) __ptr64>::call qobjectdefs_impl.h 136 0x7ff66615358c 3 QtPrivate::FunctionPointer::call,void> qobjectdefs_impl.h 170 0x7ff666152ce7 4 QtPrivate::QSlotObject,void>::impl qobject_impl.h 121 0x7ff66615363e 5 QtPrivate::QSlotObjectBase::call qobject_impl.h 101 0x54a82428
    6 QMetaObject::activate qobject.cpp 3754 0x54a70ee0
    7 QMetaObject::activate qobject.cpp 3629 0x54a707a8
    8 QTimer::timeout moc_qtimer.cpp 202 0x54a8f739
    9 QTimer::timerEvent qtimer.cpp 257 0x54a8f79a
    10 QObject::event qobject.cpp 1228 0x54a72b73
    11 QApplicationPrivate::notify_helper qapplication.cpp 3722 0x53aeb8ee
    12 QApplication::notify qapplication.cpp 3094 0x53ae6323
    13 ApplicationWithExceptionCatchedInNotify::notify main.cpp 60 0x7ff666158730 14 QCoreApplication::notifyInternal2 qcoreapplication.cpp 1018 0x54a1b0c6
    15 QCoreApplication::sendEvent qcoreapplication.h 233 0x54a26062
    16 QEventDispatcherWin32::event qeventdispatcher_win.cpp 1041 0x54ad8cab
    17 QApplicationPrivate::notify_helper qapplication.cpp 3722 0x53aeb8ee
    18 QApplication::notify qapplication.cpp 3094 0x53ae6323
    19 ApplicationWithExceptionCatchedInNotify::notify main.cpp 60 0x7ff666158730 20 QCoreApplication::notifyInternal2 qcoreapplication.cpp 1018 0x54a1b0c6
    21 QCoreApplication::sendEvent qcoreapplication.h 233 0x54a26062
    22 QCoreApplicationPrivate::sendPostedEvents qcoreapplication.cpp 1678 0x54a1c982
    23 QEventDispatcherWin32::sendPostedEvents qeventdispatcher_win.cpp 1064 0x54ad8e6a
    24 qt_internal_proc qeventdispatcher_win.cpp 237 0x54ad6b47
    25 CallWindowProcW USER32 0x7ffba1571c24 26 DispatchMessageW USER32 0x7ffba157156c 27 QEventDispatcherWin32::processEvents qeventdispatcher_win.cpp 628 0x54ad755b
    28 QEventLoop::processEvents qeventloop.cpp 135 0x54a15498
    29 QEventLoop::exec qeventloop.cpp 212 0x54a156de
    30 QThread::exec qthread.cpp 515 0x5465028f
    31 QThread::run qthread.cpp 583 0x546501c3
    32 QThreadPrivate::start qthread_win.cpp 380 0x5465caed
    33 BaseThreadInitThunk KERNEL32 0x7ffb9f5b8364 34 RtlUserThreadStart ntdll 0x7ffba1bb7091

    Object o2 throws in QThread::start methods; outside of the thread2 event loop

    1 ThrowingObject::doThrowOutsideNotify main.cpp 45 0x7ff666152ba6 2 QtPrivate::FunctorCall,QtPrivate::List<>,void,void (__cdecl ThrowingObject:: *)(void) __ptr64>::call qobjectdefs_impl.h 136 0x7ff66615358c 3 QtPrivate::FunctionPointer::call,void> qobjectdefs_impl.h 170 0x7ff666152ce7 4 QtPrivate::QSlotObject,void>::impl qobject_impl.h 121 0x7ff66615363e 5 QtPrivate::QSlotObjectBase::call qobject_impl.h 101 0x54a82428
    6 QMetaObject::activate qobject.cpp 3754 0x54a70ee0
    7 QMetaObject::activate qobject.cpp 3629 0x54a707a8
    8 QThread::started moc_qthread.cpp 160 0x54650149
    9 QThreadPrivate::start qthread_win.cpp 377 0x5465cad6
    10 BaseThreadInitThunk KERNEL32 0x7ffb9f5b8364 11 RtlUserThreadStart ntdll 0x7ffba1bb7091