diff options
author | Sona Kurazyan <sona.kurazyan@qt.io> | 2020-10-16 12:25:13 +0200 |
---|---|---|
committer | Sona Kurazyan <sona.kurazyan@qt.io> | 2020-10-26 14:27:02 +0100 |
commit | 4c793e6353ece51d4c04373f54e13d540b45195e (patch) | |
tree | 6aa9367b84ec9ace22dfe6e9a2cde4172ac3a379 | |
parent | 1aa3459d0a534473582806b0988d26533bc7e16a (diff) |
Store std::exception_ptr in QUnhandledException
For historical reasons Qt Concurrent reports QUnhandledException in
case if an exception that is not derived from QException is thrown
from a worker thread. Changing this behavior may not be a good idea,
since the existing user code may rely on it. Changed QUnhandledException
to wrap the std::exception_ptr to the actual exception, so that the
users can obtain the information about the thrown exception if needed.
[ChangeLog][QtCore][QUnhandledException] Improved QUnhandledException to
store the std::exception_ptr to the actual exception thrown from a
QtCocnurrent worker thread.
Change-Id: I30e7c1d3e01aff6e1ed9938c421da0a888f12066
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
-rw-r--r-- | src/concurrent/qtconcurrentrunbase.h | 2 | ||||
-rw-r--r-- | src/concurrent/qtconcurrentthreadengine.cpp | 4 | ||||
-rw-r--r-- | src/corelib/doc/snippets/code/src_corelib_thread_qexception.cpp | 16 | ||||
-rw-r--r-- | src/corelib/thread/qexception.cpp | 82 | ||||
-rw-r--r-- | src/corelib/thread/qexception.h | 20 | ||||
-rw-r--r-- | tests/auto/concurrent/qtconcurrentrun/tst_qtconcurrentrun.cpp | 20 | ||||
-rw-r--r-- | tests/auto/concurrent/qtconcurrentthreadengine/tst_qtconcurrentthreadengine.cpp | 42 |
7 files changed, 165 insertions, 21 deletions
diff --git a/src/concurrent/qtconcurrentrunbase.h b/src/concurrent/qtconcurrentrunbase.h index c748d8e2b9..20267a2d5b 100644 --- a/src/concurrent/qtconcurrentrunbase.h +++ b/src/concurrent/qtconcurrentrunbase.h @@ -114,7 +114,7 @@ public: } catch (QException &e) { promise.reportException(e); } catch (...) { - promise.reportException(QUnhandledException()); + promise.reportException(QUnhandledException(std::current_exception())); } #endif diff --git a/src/concurrent/qtconcurrentthreadengine.cpp b/src/concurrent/qtconcurrentthreadengine.cpp index 081018fbcc..fcc504a96c 100644 --- a/src/concurrent/qtconcurrentthreadengine.cpp +++ b/src/concurrent/qtconcurrentthreadengine.cpp @@ -196,7 +196,7 @@ void ThreadEngineBase::startBlocking() } catch (QException &e) { handleException(e); } catch (...) { - handleException(QUnhandledException()); + handleException(QUnhandledException(std::current_exception())); } #endif @@ -325,7 +325,7 @@ void ThreadEngineBase::run() // implements QRunnable. } catch (QException &e) { handleException(e); } catch (...) { - handleException(QUnhandledException()); + handleException(QUnhandledException(std::current_exception())); } #endif threadExit(); diff --git a/src/corelib/doc/snippets/code/src_corelib_thread_qexception.cpp b/src/corelib/doc/snippets/code/src_corelib_thread_qexception.cpp index 723c1630f6..08bbee7dd3 100644 --- a/src/corelib/doc/snippets/code/src_corelib_thread_qexception.cpp +++ b/src/corelib/doc/snippets/code/src_corelib_thread_qexception.cpp @@ -83,3 +83,19 @@ void MyException::raise() const { throw *this; } MyException *MyException::clone() const { return new MyException(*this); } //! [3] + +//! [4] + +try { + auto f = QtConcurrent::run([] { throw MyException {}; }); + // ... +} catch (const QUnhandledException &e) { + try { + if (e.exception()) + std::rethrow_exception(e.exception()); + } catch (const MyException &ex) { + // Process 'ex' + } +} + +//! [4] diff --git a/src/corelib/thread/qexception.cpp b/src/corelib/thread/qexception.cpp index 1a6ee7bd01..c9b7fb6cf6 100644 --- a/src/corelib/thread/qexception.cpp +++ b/src/corelib/thread/qexception.cpp @@ -62,7 +62,7 @@ QT_BEGIN_NAMESPACE \snippet code/src_corelib_thread_qexception.cpp 1 If you throw an exception that is not a subclass of QException, - the Qt functions will throw a QUnhandledException + the \l{Qt Concurrent} functions will throw a QUnhandledException in the receiver thread. When using QFuture, transferred exceptions will be thrown when calling the following functions: @@ -92,12 +92,18 @@ QT_BEGIN_NAMESPACE \class QUnhandledException \inmodule QtCore - \brief The UnhandledException class represents an unhandled exception in a worker thread. + \brief The QUnhandledException class represents an unhandled exception in a + Qt Concurrent worker thread. \since 5.0 If a worker thread throws an exception that is not a subclass of QException, - the Qt functions will throw a QUnhandledException - on the receiver thread side. + the \l{Qt Concurrent} functions will throw a QUnhandledException on the receiver + thread side. The information about the actual exception that has been thrown + will be saved in the QUnhandledException class and can be obtained using the + exception() method. For example, you can process the exception held by + QUnhandledException in the following way: + + \snippet code/src_corelib_thread_qexception.cpp 4 Inheriting from this class is not supported. */ @@ -127,6 +133,74 @@ QException *QException::clone() const return new QException(*this); } +class QUnhandledExceptionPrivate : public QSharedData +{ +public: + QUnhandledExceptionPrivate(std::exception_ptr exception) noexcept : exceptionPtr(exception) { } + std::exception_ptr exceptionPtr; +}; + +/*! + \fn QUnhandledException::QUnhandledException(std::exception_ptr exception = nullptr) noexcept + \since 6.0 + + Constructs a new QUnhandledException object. Saves the pointer to the actual + exception object if \a exception is passed. + + \sa exception() +*/ +QUnhandledException::QUnhandledException(std::exception_ptr exception) noexcept + : d(new QUnhandledExceptionPrivate(exception)) +{ +} + +/*! + Move-constructs a QUnhandledException, making it point to the same + object as \a other was pointing to. +*/ +QUnhandledException::QUnhandledException(QUnhandledException &&other) noexcept + : d(std::exchange(other.d, {})) +{ +} + +/*! + Constructs a QUnhandledException object as a copy of \a other. +*/ +QUnhandledException::QUnhandledException(const QUnhandledException &other) noexcept + : d(other.d) +{ +} + +/*! + Assigns \a other to this QUnhandledException object and returns a reference + to this QUnhandledException object. +*/ +QUnhandledException &QUnhandledException::operator=(const QUnhandledException &other) noexcept +{ + d = other.d; + return *this; +} + +/*! + \fn void QUnhandledException::swap(QUnhandledException &other) + \since 6.0 + + Swaps this QUnhandledException with \a other. This function is very fast and + never fails. +*/ + +/*! + \since 6.0 + + Returns a \l{https://en.cppreference.com/w/cpp/error/exception_ptr}{pointer} to + the actual exception that has been saved in this QUnhandledException. Returns a + \c null pointer, if it does not point to an exception object. +*/ +std::exception_ptr QUnhandledException::exception() const +{ + return d->exceptionPtr; +} + QUnhandledException::~QUnhandledException() noexcept { } diff --git a/src/corelib/thread/qexception.h b/src/corelib/thread/qexception.h index 1a21d17525..986eb43e1b 100644 --- a/src/corelib/thread/qexception.h +++ b/src/corelib/thread/qexception.h @@ -62,12 +62,28 @@ public: virtual QException *clone() const; }; -class Q_CORE_EXPORT QUnhandledException : public QException +class QUnhandledExceptionPrivate; +class Q_CORE_EXPORT QUnhandledException final : public QException { public: - ~QUnhandledException() noexcept; + QUnhandledException(std::exception_ptr exception = nullptr) noexcept; + ~QUnhandledException() noexcept override; + + QUnhandledException(QUnhandledException &&other) noexcept; + QUnhandledException(const QUnhandledException &other) noexcept; + + void swap(QUnhandledException &other) noexcept { qSwap(d, other.d); } + + QT_MOVE_ASSIGNMENT_OPERATOR_IMPL_VIA_PURE_SWAP(QUnhandledException) + QUnhandledException &operator=(const QUnhandledException &other) noexcept; + void raise() const override; QUnhandledException *clone() const override; + + std::exception_ptr exception() const; + +private: + QSharedDataPointer<QUnhandledExceptionPrivate> d; }; namespace QtPrivate { diff --git a/tests/auto/concurrent/qtconcurrentrun/tst_qtconcurrentrun.cpp b/tests/auto/concurrent/qtconcurrentrun/tst_qtconcurrentrun.cpp index e4b728f950..ae1edb54a5 100644 --- a/tests/auto/concurrent/qtconcurrentrun/tst_qtconcurrentrun.cpp +++ b/tests/auto/concurrent/qtconcurrentrun/tst_qtconcurrentrun.cpp @@ -48,6 +48,7 @@ private slots: void recursive(); #ifndef QT_NO_EXCEPTIONS void exceptions(); + void unhandledException(); #endif void functor(); void lambda(); @@ -890,6 +891,25 @@ void tst_QtConcurrentRun::exceptions() QVERIFY2(caught, "did not get exception"); } + +void tst_QtConcurrentRun::unhandledException() +{ + struct Exception {}; + bool caught = false; + try { + auto f = QtConcurrent::run([] { throw Exception {}; }); + f.waitForFinished(); + } catch (const QUnhandledException &e) { + try { + if (e.exception()) + std::rethrow_exception(e.exception()); + } catch (const Exception &) { + caught = true; + } + } + + QVERIFY(caught); +} #endif // Compiler supports decltype diff --git a/tests/auto/concurrent/qtconcurrentthreadengine/tst_qtconcurrentthreadengine.cpp b/tests/auto/concurrent/qtconcurrentthreadengine/tst_qtconcurrentthreadengine.cpp index 6825be19e6..f5ddf4560e 100644 --- a/tests/auto/concurrent/qtconcurrentthreadengine/tst_qtconcurrentthreadengine.cpp +++ b/tests/auto/concurrent/qtconcurrentthreadengine/tst_qtconcurrentthreadengine.cpp @@ -430,10 +430,10 @@ public: QThread *blockThread; }; -class UnrelatedExceptionThrower : public ThreadEngine<void> +class IntExceptionThrower : public ThreadEngine<void> { public: - UnrelatedExceptionThrower(QThread *blockThread = nullptr) + IntExceptionThrower(QThread *blockThread = nullptr) : ThreadEngine(QThreadPool::globalInstance()) { this->blockThread = blockThread; @@ -480,7 +480,7 @@ void tst_QtConcurrentThreadEngine::exceptions() { bool caught = false; try { - QtConcurrentExceptionThrower e(0); + QtConcurrentExceptionThrower e(nullptr); e.startBlocking(); } catch (const QException &) { caught = true; @@ -492,11 +492,17 @@ void tst_QtConcurrentThreadEngine::exceptions() { bool caught = false; try { - UnrelatedExceptionThrower *e = new UnrelatedExceptionThrower(); + IntExceptionThrower *e = new IntExceptionThrower(); QFuture<void> f = e->startAsynchronously(); f.waitForFinished(); - } catch (const QUnhandledException &) { - caught = true; + } catch (const QUnhandledException &ex) { + // Make sure the exception info is not lost + try { + if (ex.exception()) + std::rethrow_exception(ex.exception()); + } catch (int) { + caught = true; + } } QVERIFY2(caught, "did not get exception"); } @@ -506,10 +512,16 @@ void tst_QtConcurrentThreadEngine::exceptions() { bool caught = false; try { - UnrelatedExceptionThrower e(QThread::currentThread()); + IntExceptionThrower e(QThread::currentThread()); e.startBlocking(); - } catch (const QUnhandledException &) { - caught = true; + } catch (const QUnhandledException &ex) { + // Make sure the exception info is not lost + try { + if (ex.exception()) + std::rethrow_exception(ex.exception()); + } catch (int) { + caught = true; + } } QVERIFY2(caught, "did not get exception"); } @@ -518,10 +530,16 @@ void tst_QtConcurrentThreadEngine::exceptions() { bool caught = false; try { - UnrelatedExceptionThrower e(0); + IntExceptionThrower e(nullptr); e.startBlocking(); - } catch (const QUnhandledException &) { - caught = true; + } catch (const QUnhandledException &ex) { + // Make sure the exception info is not lost + try { + if (ex.exception()) + std::rethrow_exception(ex.exception()); + } catch (int) { + caught = true; + } } QVERIFY2(caught, "did not get exception"); } |