diff options
author | Arno Rehn <a.rehn@menlosystems.com> | 2023-05-08 15:28:11 +0200 |
---|---|---|
committer | Arno Rehn <a.rehn@menlosystems.com> | 2023-05-30 21:42:46 +0200 |
commit | 07d6d31a4c0c17d8c897d783a9b0841df6834b02 (patch) | |
tree | 851fa5478368fb537523eed5ced7fb7581e5cc96 /tests/auto/corelib/thread | |
parent | 86c044176f16674616a20cd8e6a88b5a2dcf3775 (diff) |
QFuture: Gracefully handle a destroyed context in continuations
This patch relaxes the requirements on the context object of
continuations. Instead of having to stay alive during execution of the
whole chain, it now only has to stay alive during setup of the chain.
If the context object is destroyed before the chain finishes, the
respective future is canceled.
This patch works by using QFutureCallOutInterface and signals instead
of direct invocation of the continuation by the parent future, similar
to how QFutureWatcher is implemented.
If a continuation is used with a context object, a QBasicFutureWatcher
is connected to the QFuture via a QFutureCallOutInterface. When the
future finishes, QBasicFutureWatcher::finished() triggers the
continuation with a signal/slot connection.
This way, we require the context object to stay alive only during setup;
the required synchronization is guaranteed by the existing event and
signal-slot mechanisms. The continuation itself does not need to know
about the context object anymore.
[ChangeLog][QtCore][QFuture] Added support for context objects of
continuations being destroyed before the continuation finishes. In
these cases the future is cancelled immediately.
Fixes: QTBUG-112958
Change-Id: Ie0ef3470b2a0ccfa789d2ae7604b92e509c14591
Reviewed-by: Ivan Solovev <ivan.solovev@qt.io>
Diffstat (limited to 'tests/auto/corelib/thread')
-rw-r--r-- | tests/auto/corelib/thread/qfuture/tst_qfuture.cpp | 47 |
1 files changed, 47 insertions, 0 deletions
diff --git a/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp b/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp index 08c417019e..79ca38209e 100644 --- a/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp +++ b/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp @@ -11,6 +11,7 @@ #include <QVarLengthArray> #include <QSet> #include <QList> +#include <private/qobject_p.h> #include <QTest> #include <qfuture.h> @@ -139,6 +140,18 @@ private: std::function<void ()> m_fn; }; +// Emulates QWidget behavior by deleting its children early in the destructor +// instead of leaving it to ~QObject() +class FakeQWidget : public QObject +{ + Q_OBJECT +public: + ~FakeQWidget() override { + auto *d = QObjectPrivate::get(this); + d->deleteChildren(); + } +}; + using UniquePtr = std::unique_ptr<int>; class tst_QFuture: public QObject @@ -3264,6 +3277,40 @@ void tst_QFuture::continuationsWithContext() QCOMPARE(future.result(), 2); } + // Cancellation when the context object is destroyed + { + // Use something like QWidget which deletes its children early, i.e. + // before ~QObject() runs. This behavior can lead to side-effects + // like QPointers to the parent not being set to nullptr during child + // object destruction. + QPointer shortLivedContext = new FakeQWidget(); + shortLivedContext->moveToThread(&thread); + + QPromise<int> promise; + auto future = promise.future() + .then(shortLivedContext, [&](int val) { + if (QThread::currentThread() != &thread) + return 0; + return val + 1000; + }) + .onCanceled([&, ptr=QPointer(shortLivedContext)] { + if (QThread::currentThread() != &thread) + return 0; + if (ptr) + return 1; + return 2; + }); + promise.start(); + + QMetaObject::invokeMethod(shortLivedContext, [&]() { + delete shortLivedContext; + }, Qt::BlockingQueuedConnection); + + promise.finish(); + + QCOMPARE(future.result(), 2); + } + #ifndef QT_NO_EXCEPTIONS // .onFaled() { |