forked from qt-creator/qt-creator
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:
@@ -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,25 +348,21 @@ 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
|
||||||
|| m_groupConfig.action == GroupAction::StopWithError) {
|
|| m_groupConfig.action == GroupAction::StopWithError) {
|
||||||
@@ -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();
|
||||||
|
|
||||||
const int childCount = m_selectedChildren.size();
|
GuardLocker locker(m_startGuard);
|
||||||
const int startCount = m_parallelLimit ? qMin(m_parallelLimit, childCount) : childCount;
|
return startChildren();
|
||||||
for (int i = 0; i < startCount; ++i) {
|
|
||||||
m_selectedChildren.at(i)->start();
|
|
||||||
if (!isRunning())
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TaskAction TaskContainer::startChildren()
|
||||||
|
{
|
||||||
|
const int childCount = m_selectedChildren.size();
|
||||||
|
for (int i = m_doneCount; i < childCount; ++i) {
|
||||||
|
const int limit = currentLimit();
|
||||||
|
if (i >= limit)
|
||||||
|
break;
|
||||||
|
|
||||||
|
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();
|
||||||
finalize(success);
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user