Store QFuture exceptions as std::exception_ptr

Replaced the internal ExceptionHolder for storing QException* by
std::exception_ptr. This will allow to report and store exceptions
of types that are not derived from QException.

Task-number: QTBUG-81588
Change-Id: I96be919d8289448b3e608310e51a16cebc586301
Reviewed-by: Vitaly Fanaskov <vitaly.fanaskov@qt.io>
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
This commit is contained in:
Sona Kurazyan 2020-03-26 13:22:46 +01:00
parent 7909de1beb
commit 495f958b9a
7 changed files with 121 additions and 83 deletions

View File

@ -324,7 +324,7 @@ void ThreadEngineBase::handleException(const QException &exception)
{ {
if (futureInterface) if (futureInterface)
futureInterface->reportException(exception); futureInterface->reportException(exception);
else else if (!exceptionStore.hasException())
exceptionStore.setException(exception); exceptionStore.setException(exception);
} }
#endif #endif

View File

@ -158,65 +158,38 @@ QUnhandledException *QUnhandledException::clone() const
namespace QtPrivate { namespace QtPrivate {
class Base : public QSharedData
{
public:
Base(QException *exception)
: exception(exception), hasThrown(false) { }
~Base() { delete exception; }
QException *exception;
bool hasThrown;
};
ExceptionHolder::ExceptionHolder(QException *exception)
: base(exception ? new Base(exception) : nullptr) {}
ExceptionHolder::ExceptionHolder(const ExceptionHolder &other)
: base(other.base)
{}
void ExceptionHolder::operator=(const ExceptionHolder &other)
{
base = other.base;
}
ExceptionHolder::~ExceptionHolder()
{}
QException *ExceptionHolder::exception() const
{
if (!base)
return nullptr;
return base->exception;
}
void ExceptionStore::setException(const QException &e) void ExceptionStore::setException(const QException &e)
{ {
if (hasException() == false) Q_ASSERT(!hasException());
exceptionHolder = ExceptionHolder(e.clone()); try {
e.raise();
} catch (...) {
exceptionHolder = std::current_exception();
}
}
void ExceptionStore::setException(std::exception_ptr e)
{
Q_ASSERT(!hasException());
exceptionHolder = e;
} }
bool ExceptionStore::hasException() const bool ExceptionStore::hasException() const
{ {
return (exceptionHolder.exception() != nullptr); return !!exceptionHolder;
} }
ExceptionHolder ExceptionStore::exception() std::exception_ptr ExceptionStore::exception() const
{ {
return exceptionHolder; return exceptionHolder;
} }
void ExceptionStore::throwPossibleException() void ExceptionStore::throwPossibleException()
{ {
if (hasException() ) { if (hasException())
exceptionHolder.base->hasThrown = true; std::rethrow_exception(exceptionHolder);
exceptionHolder.exception()->raise();
}
} }
bool ExceptionStore::hasThrown() const { return exceptionHolder.base->hasThrown; }
} // namespace QtPrivate } // namespace QtPrivate
#endif //Q_CLANG_QDOC #endif //Q_CLANG_QDOC

View File

@ -84,27 +84,15 @@ public:
namespace QtPrivate { namespace QtPrivate {
class Base;
class Q_CORE_EXPORT ExceptionHolder
{
public:
ExceptionHolder(QException *exception = nullptr);
ExceptionHolder(const ExceptionHolder &other);
void operator=(const ExceptionHolder &other); // ### Qt6: copy-assign operator shouldn't return void. Remove this method and the copy-ctor, they are unneeded.
~ExceptionHolder();
QException *exception() const;
QExplicitlySharedDataPointer<Base> base;
};
class Q_CORE_EXPORT ExceptionStore class Q_CORE_EXPORT ExceptionStore
{ {
public: public:
void setException(const QException &e); void setException(const QException &e);
void setException(std::exception_ptr e);
bool hasException() const; bool hasException() const;
ExceptionHolder exception(); std::exception_ptr exception() const;
void throwPossibleException(); void throwPossibleException();
bool hasThrown() const; std::exception_ptr exceptionHolder;
ExceptionHolder exceptionHolder;
}; };
} // namespace QtPrivate } // namespace QtPrivate

View File

@ -226,10 +226,8 @@ void Continuation<Function, ResultType, ParentResultType>::runFunction()
} }
} }
#ifndef QT_NO_EXCEPTIONS #ifndef QT_NO_EXCEPTIONS
} catch (QException &e) {
promise.reportException(e);
} catch (...) { } catch (...) {
promise.reportException(QUnhandledException()); promise.reportException(std::current_exception());
} }
#endif #endif
promise.reportFinished(); promise.reportFinished();
@ -249,8 +247,7 @@ bool Continuation<Function, ResultType, ParentResultType>::execute()
// interrupt the continuation chain, so don't report anything yet. // interrupt the continuation chain, so don't report anything yet.
if constexpr (!std::is_invocable_v<std::decay_t<Function>, QFuture<ParentResultType>>) { if constexpr (!std::is_invocable_v<std::decay_t<Function>, QFuture<ParentResultType>>) {
promise.reportStarted(); promise.reportStarted();
const QException *e = parentFuture.d.exceptionStore().exception().exception(); promise.reportException(parentFuture.d.exceptionStore().exception());
promise.reportException(*e);
promise.reportFinished(); promise.reportFinished();
return false; return false;
} }

View File

@ -282,6 +282,15 @@ void QFutureInterfaceBase::reportCanceled()
#ifndef QT_NO_EXCEPTIONS #ifndef QT_NO_EXCEPTIONS
void QFutureInterfaceBase::reportException(const QException &exception) void QFutureInterfaceBase::reportException(const QException &exception)
{
try {
exception.raise();
} catch (...) {
reportException(std::current_exception());
}
}
void QFutureInterfaceBase::reportException(std::exception_ptr exception)
{ {
QMutexLocker locker(&d->m_mutex); QMutexLocker locker(&d->m_mutex);
if (d->state.loadRelaxed() & (Canceled|Finished)) if (d->state.loadRelaxed() & (Canceled|Finished))

View File

@ -90,6 +90,7 @@ public:
void reportCanceled(); void reportCanceled();
#ifndef QT_NO_EXCEPTIONS #ifndef QT_NO_EXCEPTIONS
void reportException(const QException &e); void reportException(const QException &e);
void reportException(std::exception_ptr e);
#endif #endif
void reportResultsReady(int beginIndex, int endIndex); void reportResultsReady(int beginIndex, int endIndex);

View File

@ -1418,6 +1418,23 @@ QFuture<void> createDerivedExceptionFuture()
return f; return f;
} }
struct TestException
{
};
QFuture<int> createCustomExceptionFuture()
{
QFutureInterface<int> i;
i.reportStarted();
QFuture<int> f = i.future();
int r = 0;
i.reportResult(r);
auto exception = std::make_exception_ptr(TestException());
i.reportException(exception);
i.reportFinished();
return f;
}
void tst_QFuture::exceptions() void tst_QFuture::exceptions()
{ {
// test throwing from waitForFinished // test throwing from waitForFinished
@ -1502,6 +1519,18 @@ void tst_QFuture::exceptions()
} }
QVERIFY(caught); QVERIFY(caught);
} }
// Custom exceptions
{
QFuture<int> f = createCustomExceptionFuture();
bool caught = false;
try {
f.result();
} catch (const TestException &) {
caught = true;
}
QVERIFY(caught);
}
} }
class MyClass class MyClass
@ -2040,46 +2069,87 @@ void tst_QFuture::thenOnExceptionFuture()
} }
} }
template<class Exception, bool hasTestMsg = false>
QFuture<void> createExceptionContinuation(QtFuture::Launch policy = QtFuture::Launch::Sync)
{
QFutureInterface<void> promise;
auto then = promise.future().then(policy, [] {
if constexpr (hasTestMsg)
throw Exception("TEST");
else
throw Exception();
});
promise.reportStarted();
promise.reportFinished();
return then;
}
void tst_QFuture::thenThrows() void tst_QFuture::thenThrows()
{ {
// Continuation throws an exception // Continuation throws a QException
{ {
QFutureInterface<void> promise; auto future = createExceptionContinuation<QException>();
QFuture<void> then = promise.future().then([]() { throw QException(); });
promise.reportStarted();
promise.reportFinished();
bool caught = false; bool caught = false;
try { try {
then.waitForFinished(); future.waitForFinished();
} catch (QException &) { } catch (const QException &) {
caught = true; caught = true;
} }
QVERIFY(caught); QVERIFY(caught);
} }
// Continuation throws an exception derived from QException
{
auto future = createExceptionContinuation<DerivedException>();
bool caught = false;
try {
future.waitForFinished();
} catch (const QException &) {
caught = true;
} catch (const std::exception &) {
QFAIL("The exception should be caught by the above catch block.");
}
QVERIFY(caught);
}
// Continuation throws std::exception
{
auto future = createExceptionContinuation<std::runtime_error, true>();
bool caught = false;
try {
future.waitForFinished();
} catch (const QException &) {
QFAIL("The exception should be caught by the below catch block.");
} catch (const std::exception &e) {
QCOMPARE(e.what(), "TEST");
caught = true;
}
QVERIFY(caught);
}
// Same with QtFuture::Launch::Async // Same with QtFuture::Launch::Async
{ {
QFutureInterface<void> promise; auto future = createExceptionContinuation<QException>(QtFuture::Launch::Async);
QFuture<void> then =
promise.future().then(QtFuture::Launch::Async, []() { throw QException(); });
promise.reportStarted();
promise.reportFinished();
bool caught = false; bool caught = false;
try { try {
then.waitForFinished(); future.waitForFinished();
} catch (QException &) { } catch (const QException &) {
caught = true; caught = true;
} }
QVERIFY(caught); QVERIFY(caught);
} }
} }
#endif
#endif // QT_NO_EXCEPTIONS
void tst_QFuture::testSingleResult(const UniquePtr &p) void tst_QFuture::testSingleResult(const UniquePtr &p)
{ {