summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSona Kurazyan <sona.kurazyan@qt.io>2020-10-16 12:25:13 +0200
committerSona Kurazyan <sona.kurazyan@qt.io>2020-10-26 14:27:02 +0100
commit4c793e6353ece51d4c04373f54e13d540b45195e (patch)
tree6aa9367b84ec9ace22dfe6e9a2cde4172ac3a379
parent1aa3459d0a534473582806b0988d26533bc7e16a (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.h2
-rw-r--r--src/concurrent/qtconcurrentthreadengine.cpp4
-rw-r--r--src/corelib/doc/snippets/code/src_corelib_thread_qexception.cpp16
-rw-r--r--src/corelib/thread/qexception.cpp82
-rw-r--r--src/corelib/thread/qexception.h20
-rw-r--r--tests/auto/concurrent/qtconcurrentrun/tst_qtconcurrentrun.cpp20
-rw-r--r--tests/auto/concurrent/qtconcurrentthreadengine/tst_qtconcurrentthreadengine.cpp42
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");
}