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>
This commit is contained in:
Sona Kurazyan 2020-10-16 12:25:13 +02:00
parent 1aa3459d0a
commit 4c793e6353
7 changed files with 165 additions and 21 deletions

View File

@ -114,7 +114,7 @@ public:
} catch (QException &e) { } catch (QException &e) {
promise.reportException(e); promise.reportException(e);
} catch (...) { } catch (...) {
promise.reportException(QUnhandledException()); promise.reportException(QUnhandledException(std::current_exception()));
} }
#endif #endif

View File

@ -196,7 +196,7 @@ void ThreadEngineBase::startBlocking()
} catch (QException &e) { } catch (QException &e) {
handleException(e); handleException(e);
} catch (...) { } catch (...) {
handleException(QUnhandledException()); handleException(QUnhandledException(std::current_exception()));
} }
#endif #endif
@ -325,7 +325,7 @@ void ThreadEngineBase::run() // implements QRunnable.
} catch (QException &e) { } catch (QException &e) {
handleException(e); handleException(e);
} catch (...) { } catch (...) {
handleException(QUnhandledException()); handleException(QUnhandledException(std::current_exception()));
} }
#endif #endif
threadExit(); threadExit();

View File

@ -83,3 +83,19 @@ void MyException::raise() const { throw *this; }
MyException *MyException::clone() const { return new MyException(*this); } MyException *MyException::clone() const { return new MyException(*this); }
//! [3] //! [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]

View File

@ -62,7 +62,7 @@ QT_BEGIN_NAMESPACE
\snippet code/src_corelib_thread_qexception.cpp 1 \snippet code/src_corelib_thread_qexception.cpp 1
If you throw an exception that is not a subclass of QException, 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. in the receiver thread.
When using QFuture, transferred exceptions will be thrown when calling the following functions: When using QFuture, transferred exceptions will be thrown when calling the following functions:
@ -92,12 +92,18 @@ QT_BEGIN_NAMESPACE
\class QUnhandledException \class QUnhandledException
\inmodule QtCore \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 \since 5.0
If a worker thread throws an exception that is not a subclass of QException, If a worker thread throws 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 on the receiver
on the receiver thread side. 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. Inheriting from this class is not supported.
*/ */
@ -127,6 +133,74 @@ QException *QException::clone() const
return new QException(*this); 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 QUnhandledException::~QUnhandledException() noexcept
{ {
} }

View File

@ -62,12 +62,28 @@ public:
virtual QException *clone() const; virtual QException *clone() const;
}; };
class Q_CORE_EXPORT QUnhandledException : public QException class QUnhandledExceptionPrivate;
class Q_CORE_EXPORT QUnhandledException final : public QException
{ {
public: 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; void raise() const override;
QUnhandledException *clone() const override; QUnhandledException *clone() const override;
std::exception_ptr exception() const;
private:
QSharedDataPointer<QUnhandledExceptionPrivate> d;
}; };
namespace QtPrivate { namespace QtPrivate {

View File

@ -48,6 +48,7 @@ private slots:
void recursive(); void recursive();
#ifndef QT_NO_EXCEPTIONS #ifndef QT_NO_EXCEPTIONS
void exceptions(); void exceptions();
void unhandledException();
#endif #endif
void functor(); void functor();
void lambda(); void lambda();
@ -890,6 +891,25 @@ void tst_QtConcurrentRun::exceptions()
QVERIFY2(caught, "did not get exception"); 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 #endif
// Compiler supports decltype // Compiler supports decltype

View File

@ -430,10 +430,10 @@ public:
QThread *blockThread; QThread *blockThread;
}; };
class UnrelatedExceptionThrower : public ThreadEngine<void> class IntExceptionThrower : public ThreadEngine<void>
{ {
public: public:
UnrelatedExceptionThrower(QThread *blockThread = nullptr) IntExceptionThrower(QThread *blockThread = nullptr)
: ThreadEngine(QThreadPool::globalInstance()) : ThreadEngine(QThreadPool::globalInstance())
{ {
this->blockThread = blockThread; this->blockThread = blockThread;
@ -480,7 +480,7 @@ void tst_QtConcurrentThreadEngine::exceptions()
{ {
bool caught = false; bool caught = false;
try { try {
QtConcurrentExceptionThrower e(0); QtConcurrentExceptionThrower e(nullptr);
e.startBlocking(); e.startBlocking();
} catch (const QException &) { } catch (const QException &) {
caught = true; caught = true;
@ -492,11 +492,17 @@ void tst_QtConcurrentThreadEngine::exceptions()
{ {
bool caught = false; bool caught = false;
try { try {
UnrelatedExceptionThrower *e = new UnrelatedExceptionThrower(); IntExceptionThrower *e = new IntExceptionThrower();
QFuture<void> f = e->startAsynchronously(); QFuture<void> f = e->startAsynchronously();
f.waitForFinished(); f.waitForFinished();
} catch (const QUnhandledException &) { } catch (const QUnhandledException &ex) {
caught = true; // 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"); QVERIFY2(caught, "did not get exception");
} }
@ -506,10 +512,16 @@ void tst_QtConcurrentThreadEngine::exceptions()
{ {
bool caught = false; bool caught = false;
try { try {
UnrelatedExceptionThrower e(QThread::currentThread()); IntExceptionThrower e(QThread::currentThread());
e.startBlocking(); e.startBlocking();
} catch (const QUnhandledException &) { } catch (const QUnhandledException &ex) {
caught = true; // 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"); QVERIFY2(caught, "did not get exception");
} }
@ -518,10 +530,16 @@ void tst_QtConcurrentThreadEngine::exceptions()
{ {
bool caught = false; bool caught = false;
try { try {
UnrelatedExceptionThrower e(0); IntExceptionThrower e(nullptr);
e.startBlocking(); e.startBlocking();
} catch (const QUnhandledException &) { } catch (const QUnhandledException &ex) {
caught = true; // 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"); QVERIFY2(caught, "did not get exception");
} }