TaskTree: Implement asyncCount()

This might be useful for checking the integrality of tasks'
invocations and detecting the unwanted asynchronity in the task tree
execution.

Example: we may want to ensure that the setup handler of the 2nd task
was executed synchronously after the done handler of the 1st task,
without returning the control to the event loop.

This addresses the 34th point in the bugreport below.

Task-number: QTCREATORBUG-28741
Change-Id: I7213ee62a7555818cbe18fd7bb8362353a3be8ae
Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
Jarek Kobus
2024-02-19 23:52:17 +01:00
parent 62a203c926
commit b74bb782bb
2 changed files with 58 additions and 18 deletions

View File

@@ -1516,9 +1516,8 @@ public:
void start();
void stop();
void bumpAsyncCount();
void advanceProgress(int byValue);
void emitStartedAndProgress();
void emitProgress();
void emitDone(DoneWith result);
void callSetupHandler(StorageBase storage, StoragePtr storagePtr) {
callStorageHandler(storage, storagePtr, &StorageHandler::m_setupHandler);
@@ -1578,6 +1577,7 @@ public:
TaskTree *q = nullptr;
Guard m_guard;
int m_progressValue = 0;
int m_asyncCount = 0;
QSet<StorageBase> m_storages;
QHash<StorageBase, StorageHandler> m_storageHandlers;
std::optional<TaskNode> m_root;
@@ -1722,8 +1722,14 @@ void TaskTreePrivate::start()
{
QT_ASSERT(m_root, return);
QT_ASSERT(!m_runtimeRoot, return);
m_asyncCount = 0;
m_progressValue = 0;
emitStartedAndProgress();
{
GuardLocker locker(m_guard);
emit q->started();
emit q->asyncCountChanged(m_asyncCount);
emit q->progressValueChanged(m_progressValue);
}
// TODO: check storage handlers for not existing storages in tree
for (auto it = m_storageHandlers.cbegin(); it != m_storageHandlers.cend(); ++it) {
QT_ASSERT(m_storages.contains(it.key()), qWarning("The registered storage doesn't "
@@ -1731,6 +1737,7 @@ void TaskTreePrivate::start()
}
m_runtimeRoot.reset(new RuntimeTask{*m_root});
start(m_runtimeRoot.get());
bumpAsyncCount();
}
void TaskTreePrivate::stop()
@@ -1743,6 +1750,15 @@ void TaskTreePrivate::stop()
emitDone(DoneWith::Cancel);
}
void TaskTreePrivate::bumpAsyncCount()
{
if (!m_runtimeRoot)
return;
++m_asyncCount;
GuardLocker locker(m_guard);
emit q->asyncCountChanged(m_asyncCount);
}
void TaskTreePrivate::advanceProgress(int byValue)
{
if (byValue == 0)
@@ -1750,18 +1766,6 @@ void TaskTreePrivate::advanceProgress(int byValue)
QT_CHECK(byValue > 0);
QT_CHECK(m_progressValue + byValue <= m_root->taskCount());
m_progressValue += byValue;
emitProgress();
}
void TaskTreePrivate::emitStartedAndProgress()
{
GuardLocker locker(m_guard);
emit q->started();
emit q->progressValueChanged(m_progressValue);
}
void TaskTreePrivate::emitProgress()
{
GuardLocker locker(m_guard);
emit q->progressValueChanged(m_progressValue);
}
@@ -2063,10 +2067,12 @@ SetupResult TaskTreePrivate::start(RuntimeTask *node)
node->m_task.release()->deleteLater();
RuntimeIteration *parentIteration = node->m_parentIteration;
parentIteration->deleteChild(node);
if (parentIteration->m_container->isStarting())
if (parentIteration->m_container->isStarting()) {
*unwindAction = toSetupResult(result);
else
} else {
childDone(parentIteration, result);
bumpAsyncCount();
}
});
node->m_task->start();
@@ -2987,6 +2993,38 @@ DoneWith TaskTree::runBlocking(const Group &recipe, const QFuture<void> &future,
return taskTree.runBlocking(future);
}
/*!
Returns the current real count of asynchronous chains of invocations.
The returned value indicates how many times the control has returned to the caller's
event loop while the task tree is running. Initially, this value is 0.
If the execution of the task tree finishes fully synchronously, this value remains 0.
If the task tree contains any asynchronous task that is successfully started during
a call to start(), this value is bumped to 1 just before the call to start() finishes.
Later, when any asynchronous task finishes and any possible continuations are started,
this value is bumped again. The bumping continues until the task tree finishes.
After the task tree emitted the done() signal, this value isn't bumped anymore.
The asyncCountChanged() signal is emitted on every bump of this value.
\sa asyncCountChanged()
*/
int TaskTree::asyncCount() const
{
return d->m_asyncCount;
}
/*!
\fn void TaskTree::asyncCountChanged(int count)
This signal is emitted when the running task tree is about to return control to the caller's
event loop. When the task tree is started, this signal is emitted with the value of 0,
and emitted later on every asyncCount() value bump. Every signal sent
(except the initial one with the value of 0) guarantees that the task tree is still running
asynchronously after the emission.
\sa asyncCount()
*/
/*!
Returns the number of asynchronous tasks contained in the stored recipe.
@@ -3032,7 +3070,7 @@ int TaskTree::taskCount() const
When the task tree is started, this number is set to \c 0.
When the task tree is finished, this number always equals progressMaximum().
\sa progressMaximum()
\sa progressMaximum(), progressValueChanged()
*/
int TaskTree::progressValue() const
{

View File

@@ -541,6 +541,7 @@ public:
static DoneWith runBlocking(const Group &recipe, const QFuture<void> &future,
std::chrono::milliseconds timeout = std::chrono::milliseconds::max());
int asyncCount() const;
int taskCount() const;
int progressMaximum() const { return taskCount(); }
int progressValue() const; // all finished / skipped / stopped tasks, groups itself excluded
@@ -565,6 +566,7 @@ public:
signals:
void started();
void done(DoneWith result);
void asyncCountChanged(int count);
void progressValueChanged(int value); // updated whenever task finished / skipped / stopped
private: