summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/corelib/thread/qfutureinterface.cpp102
-rw-r--r--src/corelib/thread/qfutureinterface.h8
-rw-r--r--tests/auto/corelib/thread/qfuture/tst_qfuture.cpp56
3 files changed, 75 insertions, 91 deletions
diff --git a/src/corelib/thread/qfutureinterface.cpp b/src/corelib/thread/qfutureinterface.cpp
index 3cf2566bd6..0292d5b6ca 100644
--- a/src/corelib/thread/qfutureinterface.cpp
+++ b/src/corelib/thread/qfutureinterface.cpp
@@ -44,72 +44,18 @@ const auto suspendingOrSuspended =
} // unnamed namespace
-class QBasicFutureWatcher : public QObject, QFutureCallOutInterface
+class QObjectContinuationWrapper : public QObject
{
Q_OBJECT
public:
- explicit QBasicFutureWatcher(QObject *parent = nullptr);
- ~QBasicFutureWatcher() override;
-
- void setFuture(QFutureInterfaceBase &fi);
-
- bool event(QEvent *event) override;
-
-Q_SIGNALS:
- void finished();
-
-private:
- QFutureInterfaceBase future;
-
- void postCallOutEvent(const QFutureCallOutEvent &event) override;
- void callOutInterfaceDisconnected() override;
-};
-
-void QBasicFutureWatcher::postCallOutEvent(const QFutureCallOutEvent &event)
-{
- if (thread() == QThread::currentThread()) {
- // If we are in the same thread, don't queue up anything.
- std::unique_ptr<QFutureCallOutEvent> clonedEvent(event.clone());
- QCoreApplication::sendEvent(this, clonedEvent.get());
- } else {
- QCoreApplication::postEvent(this, event.clone());
+ explicit QObjectContinuationWrapper(QObject *parent = nullptr)
+ : QObject(parent)
+ {
}
-}
-
-void QBasicFutureWatcher::callOutInterfaceDisconnected()
-{
- QCoreApplication::removePostedEvents(this, QEvent::FutureCallOut);
-}
-
-/*
- * QBasicFutureWatcher is a more lightweight version of QFutureWatcher for internal use
- */
-QBasicFutureWatcher::QBasicFutureWatcher(QObject *parent)
- : QObject(parent)
-{
-}
-QBasicFutureWatcher::~QBasicFutureWatcher()
-{
- future.d->disconnectOutputInterface(this);
-}
-
-void QBasicFutureWatcher::setFuture(QFutureInterfaceBase &fi)
-{
- future = fi;
- future.d->connectOutputInterface(this);
-}
-
-bool QBasicFutureWatcher::event(QEvent *event)
-{
- if (event->type() == QEvent::FutureCallOut) {
- QFutureCallOutEvent *callOutEvent = static_cast<QFutureCallOutEvent *>(event);
- if (callOutEvent->callOutType == QFutureCallOutEvent::Finished)
- emit finished();
- return true;
- }
- return QObject::event(event);
-}
+signals:
+ void run();
+};
void QtPrivate::watchContinuationImpl(const QObject *context, QSlotObjectBase *slotObj,
QFutureInterfaceBase &fi)
@@ -119,22 +65,40 @@ void QtPrivate::watchContinuationImpl(const QObject *context, QSlotObjectBase *s
auto slot = SlotObjUniquePtr(slotObj);
- auto *watcher = new QBasicFutureWatcher;
+ auto *watcher = new QObjectContinuationWrapper;
watcher->moveToThread(context->thread());
+
+ // We need to protect acccess to the watcher. The context object (and in turn, the watcher)
+ // could be destroyed while the continuation that emits the signal is running. We have to
+ // prevent that.
+ // The mutex has to be recursive, because the continuation itself could delete the context
+ // object (and thus the watcher), which will try to lock the mutex from the same thread twice.
+ auto watcherMutex = std::make_shared<QRecursiveMutex>();
+ const auto destroyWatcher = [watcherMutex, watcher]() mutable {
+ QMutexLocker lock(watcherMutex.get());
+ watcher->deleteLater();
+ };
+
// ### we're missing a convenient way to `QObject::connect()` to a `QSlotObjectBase`...
- QObject::connect(watcher, &QBasicFutureWatcher::finished,
+ QObject::connect(watcher, &QObjectContinuationWrapper::run,
// for the following, cf. QMetaObject::invokeMethodImpl():
// we know `slot` is a lambda returning `void`, so we can just
// `call()` with `obj` and `args[0]` set to `nullptr`:
- watcher, [slot = std::move(slot)] {
+ context, [slot = std::move(slot)] {
void *args[] = { nullptr }; // for `void` return value
slot->call(nullptr, args);
});
- QObject::connect(watcher, &QBasicFutureWatcher::finished,
- watcher, &QObject::deleteLater);
- QObject::connect(context, &QObject::destroyed,
- watcher, &QObject::deleteLater);
- watcher->setFuture(fi);
+ QObject::connect(watcher, &QObjectContinuationWrapper::run, watcher, &QObject::deleteLater);
+ QObject::connect(context, &QObject::destroyed, watcher, destroyWatcher);
+
+ fi.setContinuation([watcherMutex, watcher = QPointer(watcher)]
+ (const QFutureInterfaceBase &parentData)
+ {
+ Q_UNUSED(parentData);
+ QMutexLocker lock(watcherMutex.get());
+ if (watcher)
+ emit watcher->run();
+ });
}
QFutureCallOutInterface::~QFutureCallOutInterface()
diff --git a/src/corelib/thread/qfutureinterface.h b/src/corelib/thread/qfutureinterface.h
index 151584e66b..fa5e8ea7ba 100644
--- a/src/corelib/thread/qfutureinterface.h
+++ b/src/corelib/thread/qfutureinterface.h
@@ -21,6 +21,7 @@ QT_BEGIN_NAMESPACE
template <typename T> class QFuture;
class QThreadPool;
+class QFutureInterfaceBase;
class QFutureInterfaceBasePrivate;
class QFutureWatcherBase;
class QFutureWatcherBasePrivate;
@@ -39,8 +40,10 @@ template<class Function, class ResultType>
class FailureHandler;
#endif
+void Q_CORE_EXPORT watchContinuationImpl(const QObject *context,
+ QtPrivate::QSlotObjectBase *slotObj,
+ QFutureInterfaceBase &fi);
}
-class QBasicFutureWatcher;
class Q_CORE_EXPORT QFutureInterfaceBase
{
@@ -178,7 +181,8 @@ private:
friend class QtPrivate::FailureHandler;
#endif
- friend class QBasicFutureWatcher;
+ friend Q_CORE_EXPORT void QtPrivate::watchContinuationImpl(
+ const QObject *context, QtPrivate::QSlotObjectBase *slotObj, QFutureInterfaceBase &fi);
template<class T>
friend class QPromise;
diff --git a/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp b/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp
index f54ea940f7..15b3742b7b 100644
--- a/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp
+++ b/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp
@@ -204,6 +204,7 @@ private slots:
#endif
void onCanceled();
void cancelContinuations();
+ void continuationsWithContext_data();
void continuationsWithContext();
void continuationsWithMoveOnlyLambda();
#if 0
@@ -3223,15 +3224,35 @@ void tst_QFuture::cancelContinuations()
}
}
+void tst_QFuture::continuationsWithContext_data()
+{
+ QTest::addColumn<bool>("inOtherThread");
+ QTest::addRow("in-other-thread") << true;
+ QTest::addRow("in-main-thread-qtbug119406") << false;
+}
+
void tst_QFuture::continuationsWithContext()
{
- QThread thread;
- thread.start();
+ QFETCH(bool, inOtherThread);
+ auto tstThread = QThread::currentThread();
+ QThread *thread = inOtherThread ? new QThread
+ : tstThread;
auto context = new QObject();
- context->moveToThread(&thread);
- auto tstThread = QThread::currentThread();
+ const auto cleanupGuard = qScopeGuard([&] {
+ context->deleteLater();
+ if (thread != tstThread) {
+ thread->quit();
+ thread->wait();
+ delete thread;
+ }
+ });
+
+ if (inOtherThread) {
+ thread->start();
+ context->moveToThread(thread);
+ }
// .then()
{
@@ -3244,12 +3265,12 @@ void tst_QFuture::continuationsWithContext()
})
.then(context,
[&](int val) {
- if (QThread::currentThread() != &thread)
+ if (QThread::currentThread() != thread)
return 0;
return val + 1;
})
.then([&](int val) {
- if (QThread::currentThread() != &thread)
+ if (QThread::currentThread() != thread)
return 0;
return val + 1;
});
@@ -3265,12 +3286,12 @@ void tst_QFuture::continuationsWithContext()
auto future = promise.future()
.onCanceled(context,
[&] {
- if (QThread::currentThread() != &thread)
+ if (QThread::currentThread() != thread)
return 0;
return 1;
})
.then([&](int val) {
- if (QThread::currentThread() != &thread)
+ if (QThread::currentThread() != thread)
return 0;
return val + 1;
});
@@ -3287,17 +3308,17 @@ void tst_QFuture::continuationsWithContext()
// like QPointers to the parent not being set to nullptr during child
// object destruction.
QPointer shortLivedContext = new FakeQWidget();
- shortLivedContext->moveToThread(&thread);
+ shortLivedContext->moveToThread(thread);
QPromise<int> promise;
auto future = promise.future()
.then(shortLivedContext, [&](int val) {
- if (QThread::currentThread() != &thread)
+ if (QThread::currentThread() != thread)
return 0;
return val + 1000;
})
.onCanceled([&, ptr=QPointer(shortLivedContext)] {
- if (QThread::currentThread() != &thread)
+ if (QThread::currentThread() != thread)
return 0;
if (ptr)
return 1;
@@ -3307,10 +3328,10 @@ void tst_QFuture::continuationsWithContext()
QMetaObject::invokeMethod(shortLivedContext, [&]() {
delete shortLivedContext;
- }, Qt::BlockingQueuedConnection);
+ }, inOtherThread ? Qt::BlockingQueuedConnection
+ : Qt::DirectConnection);
promise.finish();
-
QCOMPARE(future.result(), 2);
}
@@ -3326,12 +3347,12 @@ void tst_QFuture::continuationsWithContext()
})
.onFailed(context,
[&] {
- if (QThread::currentThread() != &thread)
+ if (QThread::currentThread() != thread)
return 0;
return 1;
})
.then([&](int val) {
- if (QThread::currentThread() != &thread)
+ if (QThread::currentThread() != thread)
return 0;
return val + 1;
});
@@ -3340,11 +3361,6 @@ void tst_QFuture::continuationsWithContext()
QCOMPARE(future.result(), 2);
}
#endif // QT_NO_EXCEPTIONS
-
- context->deleteLater();
-
- thread.quit();
- thread.wait();
}
void tst_QFuture::continuationsWithMoveOnlyLambda()