Fix dispatching when app (or thread) terminated

Make sure to **not** notify handlers if the captured thread doesn't exist anymore, which would potentially result in dispatching to the wrong thread (ie. nullptr == current thread). This also applies when the app is shutting down and the even loop is not anymore available. In both cases, we should not trigger any error and skip notifications.
This commit is contained in:
Simon Brunel 2018-02-24 12:31:03 +01:00
parent f794916be6
commit 313d3882d7
2 changed files with 30 additions and 7 deletions

View File

@ -32,7 +32,7 @@ namespace QtPromisePrivate {
// https://stackoverflow.com/a/21653558 // https://stackoverflow.com/a/21653558
template <typename F> template <typename F>
static void qtpromise_defer(F&& f, QThread* thread = nullptr) static void qtpromise_defer(F&& f, const QPointer<QThread>& thread)
{ {
struct Event : public QEvent struct Event : public QEvent
{ {
@ -43,11 +43,34 @@ static void qtpromise_defer(F&& f, QThread* thread = nullptr)
FType m_f; FType m_f;
}; };
if (!thread || thread->isFinished()) {
// Make sure to not call `f` if the captured thread doesn't exist anymore,
// which would potentially result in dispatching to the wrong thread (ie.
// nullptr == current thread). Since the target thread is gone, it should
// be safe to simply skip that notification.
return;
}
QObject* target = QAbstractEventDispatcher::instance(thread); QObject* target = QAbstractEventDispatcher::instance(thread);
if (!target && QCoreApplication::closingDown()) {
// When the app is shutting down, the even loop is not anymore available
// so we don't have any way to dispatch `f`. This case can happen when a
// promise is resolved after the app is requested to close, in which case
// we should not trigger any error and skip that notification.
return;
}
Q_ASSERT_X(target, "postMetaCall", "Target thread must have an event loop"); Q_ASSERT_X(target, "postMetaCall", "Target thread must have an event loop");
QCoreApplication::postEvent(target, new Event(std::forward<F>(f))); QCoreApplication::postEvent(target, new Event(std::forward<F>(f)));
} }
template <typename F>
static void qtpromise_defer(F&& f)
{
Q_ASSERT(QThread::currentThread());
qtpromise_defer(std::forward<F>(f), QThread::currentThread());
}
template <typename T> template <typename T>
struct PromiseDeduce struct PromiseDeduce
{ {

View File

@ -261,9 +261,9 @@ void tst_future::fail()
QString result; QString result;
auto input = QPromise<QString>::reject(MyException("bar")); auto input = QPromise<QString>::reject(MyException("bar"));
auto output = input.fail([](const MyException& e) { auto output = input.fail([](const MyException& e) {
return QtConcurrent::run([=]() { return QtConcurrent::run([](const QString& error) {
return QString("foo%1").arg(e.error()); return QString("foo%1").arg(error);
}); }, e.error());
}); });
QCOMPARE(input.isRejected(), true); QCOMPARE(input.isRejected(), true);
@ -282,9 +282,9 @@ void tst_future::fail_void()
QString result; QString result;
auto input = QPromise<void>::reject(MyException("bar")); auto input = QPromise<void>::reject(MyException("bar"));
auto output = input.fail([&](const MyException& e) { auto output = input.fail([&](const MyException& e) {
return QtConcurrent::run([&]() { return QtConcurrent::run([&](const QString& error) {
result = e.error(); result = error;
}); }, e.error());
}); });
QCOMPARE(input.isRejected(), true); QCOMPARE(input.isRejected(), true);