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:
parent
1aa3459d0a
commit
4c793e6353
@ -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
|
||||||
|
|
||||||
|
@ -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();
|
||||||
|
@ -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]
|
||||||
|
@ -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
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
|
@ -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");
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user