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) {
promise.reportException(e);
} catch (...) {
promise.reportException(QUnhandledException());
promise.reportException(QUnhandledException(std::current_exception()));
}
#endif

View File

@ -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();

View File

@ -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]

View File

@ -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
{
}

View File

@ -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 {

View File

@ -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

View File

@ -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");
}