TaskTree: Unwind execution properly on synchronous done

When task or group setup handler finishes synchronously
we may end up with mismatched execution order or may
grow call stack considerably. The fix is to detect
synchronous finishes and leave handling to the synchronous
caller.

Change-Id: Id72cf8cc98e2f53ff601a5e5226cd203a8470aaf
Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
Jarek Kobus
2023-01-19 23:28:56 +01:00
parent e6ca18e194
commit 6269c2cec8

View File

@@ -9,6 +9,11 @@
namespace Utils { namespace Utils {
namespace Tasking { namespace Tasking {
static TaskAction toTaskAction(bool success)
{
return success ? TaskAction::StopWithDone : TaskAction::StopWithError;
}
bool TreeStorageBase::isValid() const bool TreeStorageBase::isValid() const
{ {
return m_storageData && m_storageData->m_constructor && m_storageData->m_destructor; return m_storageData && m_storageData->m_constructor && m_storageData->m_destructor;
@@ -144,13 +149,16 @@ public:
TaskContainer(TaskTreePrivate *taskTreePrivate, TaskContainer *parentContainer, TaskContainer(TaskTreePrivate *taskTreePrivate, TaskContainer *parentContainer,
const TaskItem &task); const TaskItem &task);
~TaskContainer(); ~TaskContainer();
void start(); TaskAction start();
TaskAction startChildren();
int selectChildren(); // returns the skipped child count int selectChildren(); // returns the skipped child count
void stop(); void stop();
bool isRunning() const; bool isRunning() const;
int taskCount() const; int taskCount() const;
void childDone(bool success); int currentLimit() const;
void invokeEndHandler(bool success, bool propagateToParent = true); TaskAction childDone(bool success);
void groupDone(bool success);
void invokeEndHandler(bool success);
void resetSuccessBit(); void resetSuccessBit();
void updateSuccessBit(bool success); void updateSuccessBit(bool success);
@@ -170,8 +178,9 @@ public:
GroupConfig m_groupConfig; GroupConfig m_groupConfig;
QList<TaskNode *> m_children; QList<TaskNode *> m_children;
QList<TaskNode *> m_selectedChildren; QList<TaskNode *> m_selectedChildren;
int m_currentIndex = -1; int m_doneCount = -1;
bool m_successBit = true; bool m_successBit = true;
Guard m_startGuard;
}; };
class StorageActivator class StorageActivator
@@ -200,7 +209,8 @@ public:
{ {
} }
void start(); // If returned value != Start, childDone() needs to be called in parent container (in caller)
TaskAction start();
void stop(); void stop();
bool isRunning() const; bool isRunning() const;
bool isTask() const; bool isTask() const;
@@ -210,6 +220,7 @@ private:
const TaskItem::TaskHandler m_taskHandler; const TaskItem::TaskHandler m_taskHandler;
TaskContainer m_container; TaskContainer m_container;
std::unique_ptr<TaskInterface> m_task; std::unique_ptr<TaskInterface> m_task;
Guard m_startGuard;
}; };
class TaskTreePrivate class TaskTreePrivate
@@ -227,7 +238,11 @@ public:
QTC_ASSERT(m_storages.contains(it.key()), qWarning("The registered storage doesn't " QTC_ASSERT(m_storages.contains(it.key()), qWarning("The registered storage doesn't "
"exist in task tree. Its handlers will never be called.")); "exist in task tree. Its handlers will never be called."));
} }
m_root->start(); const TaskAction action = m_root->start();
if (action == TaskAction::StopWithDone)
emitDone();
else if (action == TaskAction::StopWithError)
emitError();
} }
void stop() { void stop() {
QTC_ASSERT(m_root, return); QTC_ASSERT(m_root, return);
@@ -333,24 +348,20 @@ TaskContainer::~TaskContainer()
deleteStorages(); deleteStorages();
} }
void TaskContainer::start() TaskAction TaskContainer::start()
{ {
m_currentIndex = 0; m_doneCount = 0;
m_groupConfig = {}; m_groupConfig = {};
m_selectedChildren.clear(); m_selectedChildren.clear();
createStorages(); createStorages();
{ if (m_groupHandler.m_setupHandler || m_groupHandler.m_dynamicSetupHandler) {
StorageActivator activator(*this); StorageActivator activator(*this);
if (m_groupHandler.m_setupHandler) { GuardLocker locker(m_taskTreePrivate->m_guard);
GuardLocker locker(m_taskTreePrivate->m_guard); if (m_groupHandler.m_setupHandler)
m_groupHandler.m_setupHandler(); m_groupHandler.m_setupHandler();
} if (m_groupHandler.m_dynamicSetupHandler)
if (m_groupHandler.m_dynamicSetupHandler) {
GuardLocker locker(m_taskTreePrivate->m_guard);
m_groupConfig = m_groupHandler.m_dynamicSetupHandler(); m_groupConfig = m_groupHandler.m_dynamicSetupHandler();
}
} }
if (m_groupConfig.action == GroupAction::StopWithDone if (m_groupConfig.action == GroupAction::StopWithDone
@@ -359,7 +370,7 @@ void TaskContainer::start()
const int skippedTaskCount = taskCount(); const int skippedTaskCount = taskCount();
m_taskTreePrivate->advanceProgress(skippedTaskCount); m_taskTreePrivate->advanceProgress(skippedTaskCount);
invokeEndHandler(success); invokeEndHandler(success);
return; return toTaskAction(success);
} }
const int skippedTaskCount = selectChildren(); const int skippedTaskCount = selectChildren();
@@ -367,18 +378,40 @@ void TaskContainer::start()
if (m_selectedChildren.isEmpty()) { if (m_selectedChildren.isEmpty()) {
invokeEndHandler(true); invokeEndHandler(true);
return; return TaskAction::StopWithDone; // TODO: take workflow policy into account?
} }
resetSuccessBit(); resetSuccessBit();
GuardLocker locker(m_startGuard);
return startChildren();
}
TaskAction TaskContainer::startChildren()
{
const int childCount = m_selectedChildren.size(); const int childCount = m_selectedChildren.size();
const int startCount = m_parallelLimit ? qMin(m_parallelLimit, childCount) : childCount; for (int i = m_doneCount; i < childCount; ++i) {
for (int i = 0; i < startCount; ++i) { const int limit = currentLimit();
m_selectedChildren.at(i)->start(); if (i >= limit)
if (!isRunning()) break;
return;
const TaskAction action = m_selectedChildren.at(i)->start();
if (action == TaskAction::Continue)
continue;
const TaskAction finalizeAction = childDone(action == TaskAction::StopWithDone);
if (finalizeAction == TaskAction::Continue)
continue;
int skippedTaskCount = 0;
// Skip scheduled but not run yet. The current (i) was already notified.
for (int j = i + 1; j < limit; ++j)
skippedTaskCount += m_selectedChildren.at(j)->taskCount();
m_taskTreePrivate->advanceProgress(skippedTaskCount);
return finalizeAction;
} }
return TaskAction::Continue;
} }
int TaskContainer::selectChildren() int TaskContainer::selectChildren()
@@ -403,21 +436,23 @@ void TaskContainer::stop()
if (!isRunning()) if (!isRunning())
return; return;
if (m_parallelLimit) { // skip not started tasks const int childCount = m_selectedChildren.size();
int skippedTaskCount = 0; const int limit = currentLimit();
for (int i = m_currentIndex + m_parallelLimit; i < m_selectedChildren.size(); ++i)
skippedTaskCount += m_selectedChildren.at(i)->taskCount();
m_taskTreePrivate->advanceProgress(skippedTaskCount);
}
m_currentIndex = -1; for (int i = m_doneCount; i < limit; ++i)
for (TaskNode *child : std::as_const(m_selectedChildren)) m_selectedChildren.at(i)->stop();
child->stop();
int skippedTaskCount = 0;
for (int i = limit; i < childCount; ++i)
skippedTaskCount += m_selectedChildren.at(i)->taskCount();
m_taskTreePrivate->advanceProgress(skippedTaskCount);
m_doneCount = -1;
} }
bool TaskContainer::isRunning() const bool TaskContainer::isRunning() const
{ {
return m_currentIndex >= 0; return m_doneCount >= 0;
} }
int TaskContainer::taskCount() const int TaskContainer::taskCount() const
@@ -425,48 +460,43 @@ int TaskContainer::taskCount() const
return m_taskCount; return m_taskCount;
} }
void TaskContainer::childDone(bool success) int TaskContainer::currentLimit() const
{
const int childCount = m_selectedChildren.size();
return m_parallelLimit ? qMin(m_doneCount + m_parallelLimit, childCount) : childCount;
}
TaskAction TaskContainer::childDone(bool success)
{ {
if ((m_workflowPolicy == WorkflowPolicy::StopOnDone && success) if ((m_workflowPolicy == WorkflowPolicy::StopOnDone && success)
|| (m_workflowPolicy == WorkflowPolicy::StopOnError && !success)) { || (m_workflowPolicy == WorkflowPolicy::StopOnError && !success)) {
stop(); stop();
invokeEndHandler(success); invokeEndHandler(success);
return; groupDone(success);
return toTaskAction(success);
} }
++m_currentIndex; ++m_doneCount;
updateSuccessBit(success); updateSuccessBit(success);
if (m_currentIndex == m_selectedChildren.size()) { if (m_doneCount == m_selectedChildren.size()) {
invokeEndHandler(m_successBit); invokeEndHandler(m_successBit);
return; groupDone(m_successBit);
return toTaskAction(m_successBit);
} }
if (m_parallelLimit == 0) if (m_parallelLimit == 0)
return; return TaskAction::Continue;
const int nextIndexToRun = m_currentIndex + m_parallelLimit - 1; if (m_startGuard.isLocked())
if (nextIndexToRun < m_selectedChildren.size()) return TaskAction::Continue;
m_selectedChildren.at(nextIndexToRun)->start();
return startChildren();
} }
void TaskContainer::invokeEndHandler(bool success, bool propagateToParent) void TaskContainer::groupDone(bool success)
{ {
m_currentIndex = -1; if (m_startGuard.isLocked())
m_successBit = success;
{
StorageActivator activator(*this);
if (success && m_groupHandler.m_doneHandler) {
GuardLocker locker(m_taskTreePrivate->m_guard);
m_groupHandler.m_doneHandler();
} else if (!success && m_groupHandler.m_errorHandler) {
GuardLocker locker(m_taskTreePrivate->m_guard);
m_groupHandler.m_errorHandler();
}
}
deleteStorages();
if (!propagateToParent)
return; return;
if (m_parentContainer) { if (m_parentContainer) {
@@ -479,6 +509,23 @@ void TaskContainer::invokeEndHandler(bool success, bool propagateToParent)
m_taskTreePrivate->emitError(); m_taskTreePrivate->emitError();
} }
void TaskContainer::invokeEndHandler(bool success)
{
m_doneCount = -1;
m_successBit = success;
const bool callDoneHandler = success && m_groupHandler.m_doneHandler;
const bool callErrorHandler = !success && m_groupHandler.m_errorHandler;
if (callDoneHandler || callErrorHandler) {
StorageActivator activator(*this);
GuardLocker locker(m_taskTreePrivate->m_guard);
if (callDoneHandler)
m_groupHandler.m_doneHandler();
else if (callErrorHandler)
m_groupHandler.m_errorHandler();
}
deleteStorages();
}
void TaskContainer::resetSuccessBit() void TaskContainer::resetSuccessBit()
{ {
if (m_selectedChildren.isEmpty()) if (m_selectedChildren.isEmpty())
@@ -545,17 +592,14 @@ void TaskContainer::deactivateStorages()
m_parentContainer->deactivateStorages(); m_parentContainer->deactivateStorages();
} }
void TaskNode::start() TaskAction TaskNode::start()
{ {
if (!isTask()) { if (!isTask())
m_container.start(); return m_container.start();
return;
} const auto finalize = [this] {
const auto finalize = [this](bool success) {
m_container.m_taskTreePrivate->advanceProgress(1); m_container.m_taskTreePrivate->advanceProgress(1);
m_task.release()->deleteLater(); m_task.release()->deleteLater();
QTC_CHECK(m_container.m_parentContainer);
m_container.m_parentContainer->childDone(success);
}; };
m_task.reset(m_taskHandler.m_createHandler()); m_task.reset(m_taskHandler.m_createHandler());
{ {
@@ -566,25 +610,32 @@ void TaskNode::start()
action = m_taskHandler.m_setupHandler(*m_task.get()); action = m_taskHandler.m_setupHandler(*m_task.get());
} }
if (action != TaskAction::Continue) { if (action != TaskAction::Continue) {
finalize(action == TaskAction::StopWithDone); finalize();
return; return action;
} }
} }
const std::shared_ptr<TaskAction> unwindAction = std::make_shared<TaskAction>(TaskAction::Continue);
connect(m_task.get(), &TaskInterface::done, this, [=](bool success) { connect(m_task.get(), &TaskInterface::done, this, [=](bool success) {
{ const bool callDoneHandler = success && m_taskHandler.m_doneHandler;
const bool callErrorHandler = !success && m_taskHandler.m_errorHandler;
if (callDoneHandler || callErrorHandler) {
StorageActivator activator(m_container); StorageActivator activator(m_container);
if (success && m_taskHandler.m_doneHandler) { GuardLocker locker(m_container.m_taskTreePrivate->m_guard);
GuardLocker locker(m_container.m_taskTreePrivate->m_guard); if (callDoneHandler)
m_taskHandler.m_doneHandler(*m_task.get()); m_taskHandler.m_doneHandler(*m_task.get());
} else if (!success && m_taskHandler.m_errorHandler) { else if (callErrorHandler)
GuardLocker locker(m_container.m_taskTreePrivate->m_guard);
m_taskHandler.m_errorHandler(*m_task.get()); m_taskHandler.m_errorHandler(*m_task.get());
}
} }
finalize(success); finalize();
if (m_startGuard.isLocked())
*unwindAction = toTaskAction(success);
else
m_container.m_parentContainer->childDone(success);
}); });
GuardLocker locker(m_startGuard);
m_task->start(); m_task->start();
return *unwindAction;
} }
void TaskNode::stop() void TaskNode::stop()
@@ -594,7 +645,7 @@ void TaskNode::stop()
if (!m_task) { if (!m_task) {
m_container.stop(); m_container.stop();
m_container.invokeEndHandler(false, false); m_container.invokeEndHandler(false);
return; return;
} }