From 24dedaeaa1a94bfe9ade2da2a2c9aa112241b07a Mon Sep 17 00:00:00 2001 From: Sona Kurazyan Date: Fri, 9 Sep 2022 16:08:33 +0200 Subject: QFuture: fix handling of cancelled continuation chain MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To support cancellation of continuations attached via the parent future, for each future returned by a continuation we store a pointer to its parent (i.e. future the continuation is attached to). Later, before executing a continuation, we go through chain of parents and check if any of them is cancelled. However, if one of the parents is destroyed while the chain is executing, the next continuations' parent pointers will become invalid. So storing the parent pointers isn't safe. This commit changes the logic of handling the cancelled continuation chain in the following way: - Instead of storing a parent pointer in the continuation future's data, we do the opposite: we store a pointer to continuation's future in the parent. - When a future is cancelled, we mark all continuation futures in the chain with a flag indicating that the chain is cancelled. - To guarantee that the pointers to continuation future's data don't become invalid, we clean the continuation (that stores a copy of its future's data and keeps it alive) only when the associated promise is destructed, instead of cleaning it after the continuation is run. Fixes: QTBUG-105182 Fixes: QTBUG-106083 Pick-to: 6.2 6.3 6.4 Change-Id: I48afa98152672c0fc737112be4ca3b1b42f6ed30 Reviewed-by: Qt CI Bot Reviewed-by: MÃ¥rten Nordheim --- tests/auto/corelib/thread/qfuture/tst_qfuture.cpp | 71 +++++++++++++++++++++++ 1 file changed, 71 insertions(+) (limited to 'tests/auto/corelib/thread') diff --git a/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp b/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp index 638dc380f7..81120c6f67 100644 --- a/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp +++ b/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp @@ -2240,6 +2240,26 @@ void tst_QFuture::then() QVERIFY(threadId1 != QThread::currentThreadId()); QVERIFY(threadId2 != QThread::currentThreadId()); } + + // QTBUG-106083 & QTBUG-105182 + { + QThread thread; + thread.start(); + + QObject context; + context.moveToThread(&thread); + + auto future = QtConcurrent::run([] { + return 42; + }).then([] (int result) { + return result + 1; + }).then(&context, [] (int result) { + return result + 1; + }); + QCOMPARE(future.result(), 44); + thread.quit(); + thread.wait(); + } } template @@ -3116,6 +3136,57 @@ void tst_QFuture::cancelContinuations() QCOMPARE(checkpoint, 3); } #endif // QT_NO_EXCEPTIONS + + // Check notifications from QFutureWatcher + { + QPromise p; + auto f = p.future(); + + auto f1 = f.then([] {}); + auto f2 = f1.then([] {}); + + QFutureWatcher watcher1, watcher2; + int state = 0; + QObject::connect(&watcher1, &QFutureWatcher::started, [&] { + QCOMPARE(state, 0); + ++state; + }); + QObject::connect(&watcher1, &QFutureWatcher::canceled, [&] { + QCOMPARE(state, 1); + ++state; + }); + QObject::connect(&watcher1, &QFutureWatcher::finished, [&] { + QCOMPARE(state, 2); + ++state; + }); + QObject::connect(&watcher2, &QFutureWatcher::started, [&] { + QCOMPARE(state, 3); + ++state; + }); + QObject::connect(&watcher2, &QFutureWatcher::canceled, [&] { + QCOMPARE(state, 4); + ++state; + }); + QObject::connect(&watcher2, &QFutureWatcher::finished, [&] { + QCOMPARE(state, 5); + ++state; + }); + + watcher1.setFuture(f1); + watcher2.setFuture(f2); + + p.start(); + f.cancel(); + p.finish(); + + qApp->processEvents(); + + QCOMPARE(state, 6); + QVERIFY(watcher1.isFinished()); + QVERIFY(watcher1.isCanceled()); + QVERIFY(watcher2.isFinished()); + QVERIFY(watcher2.isCanceled()); + } } void tst_QFuture::continuationsWithContext() -- cgit v1.2.3