TaskTree: Divide internal data into ConstData and RuntimeData

RuntimeData is of std::optional type, empty when not running.
Restructure internals.

Change-Id: I347bfce3f135b31853f2612103e7b3187dc956b2
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
Jarek Kobus
2023-01-31 12:50:59 +01:00
parent b177f925d1
commit 5dc4cd837f
2 changed files with 221 additions and 262 deletions

View File

@@ -143,26 +143,22 @@ class TaskNode;
class TaskContainer class TaskContainer
{ {
public: public:
TaskContainer(TaskTreePrivate *taskTreePrivate, TaskContainer *parentContainer, TaskContainer(TaskTreePrivate *taskTreePrivate, const TaskItem &task,
const TaskItem &task); TaskContainer *parentContainer)
~TaskContainer(); : m_constData(taskTreePrivate, task, parentContainer, this) {}
TaskAction start(); TaskAction start();
TaskAction continueStart(TaskAction startAction, int nextChild);
TaskAction startChildren(int nextChild); TaskAction startChildren(int nextChild);
void stop();
bool isStarting() const { return m_startGuard.isLocked(); }
bool isRunning() const { return m_doneCount >= 0; }
int currentLimit() const;
TaskAction childDone(bool success); TaskAction childDone(bool success);
void groupDone(); void stop();
void invokeEndHandler(); void invokeEndHandler();
void resetSuccessBit(); // only on start bool isRunning() const { return m_runtimeData.has_value(); }
void updateSuccessBit(bool success); // only on childDone bool isStarting() const { return isRunning() && m_runtimeData->m_startGuard.isLocked(); }
void createStorages();
void deleteStorages();
void activateStorages();
void deactivateStorages();
struct ConstData {
ConstData(TaskTreePrivate *taskTreePrivate, const TaskItem &task,
TaskContainer *parentContainer, TaskContainer *thisContainer);
~ConstData() { qDeleteAll(m_children); }
TaskTreePrivate * const m_taskTreePrivate = nullptr; TaskTreePrivate * const m_taskTreePrivate = nullptr;
TaskContainer * const m_parentContainer = nullptr; TaskContainer * const m_parentContainer = nullptr;
@@ -172,45 +168,45 @@ public:
const QList<TreeStorageBase> m_storageList; const QList<TreeStorageBase> m_storageList;
const QList<TaskNode *> m_children; const QList<TaskNode *> m_children;
const int m_taskCount = 0; const int m_taskCount = 0;
};
QList<int> m_storageIdList; struct RuntimeData {
int m_doneCount = -1; RuntimeData(const ConstData &constData);
~RuntimeData();
static QList<int> createStorages(const TaskContainer::ConstData &constData);
bool updateSuccessBit(bool success);
int currentLimit() const;
const ConstData &m_constData;
const QList<int> m_storageIdList;
int m_doneCount = 0;
bool m_successBit = true; bool m_successBit = true;
Guard m_startGuard; Guard m_startGuard;
}; };
class StorageActivator const ConstData m_constData;
{ std::optional<RuntimeData> m_runtimeData;
public:
StorageActivator(TaskContainer &container)
: m_container(container)
{
m_container.activateStorages();
}
~StorageActivator()
{
m_container.deactivateStorages();
}
private:
TaskContainer &m_container;
}; };
class TaskNode : public QObject class TaskNode : public QObject
{ {
public: public:
TaskNode(TaskTreePrivate *taskTreePrivate, TaskContainer *parentContainer, TaskNode(TaskTreePrivate *taskTreePrivate, const TaskItem &task,
const TaskItem &task) TaskContainer *parentContainer)
: m_taskHandler(task.taskHandler()) : m_taskHandler(task.taskHandler())
, m_container(taskTreePrivate, parentContainer, task) , m_container(taskTreePrivate, task, parentContainer)
{ {}
}
// If returned value != Start, childDone() needs to be called in parent container (in caller) // If returned value != Continue, childDone() needs to be called in parent container (in caller)
// in order to unwind properly.
TaskAction start(); TaskAction start();
void stop(); void stop();
bool isRunning() const; void invokeEndHandler(bool success);
bool isTask() const; bool isRunning() const { return m_task || m_container.isRunning(); }
int taskCount() const; bool isTask() const { return m_taskHandler.m_createHandler && m_taskHandler.m_setupHandler; }
int taskCount() const { return isTask() ? 1 : m_container.m_constData.m_taskCount; }
TaskContainer *parentContainer() const { return m_container.m_constData.m_parentContainer; }
private: private:
const TaskItem::TaskHandler m_taskHandler; const TaskItem::TaskHandler m_taskHandler;
@@ -316,72 +312,162 @@ public:
std::unique_ptr<TaskNode> m_root = nullptr; // Keep me last in order to destruct first std::unique_ptr<TaskNode> m_root = nullptr; // Keep me last in order to destruct first
}; };
class StorageActivator
{
public:
StorageActivator(TaskContainer *container)
: m_container(container) { activateStorages(m_container); }
~StorageActivator() { deactivateStorages(m_container); }
private:
static void activateStorages(TaskContainer *container)
{
QTC_ASSERT(container && container->isRunning(), return);
const TaskContainer::ConstData &constData = container->m_constData;
if (constData.m_parentContainer)
activateStorages(constData.m_parentContainer);
for (int i = 0; i < constData.m_storageList.size(); ++i)
constData.m_storageList[i].activateStorage(container->m_runtimeData->m_storageIdList.value(i));
}
static void deactivateStorages(TaskContainer *container)
{
QTC_ASSERT(container && container->isRunning(), return);
const TaskContainer::ConstData &constData = container->m_constData;
for (int i = constData.m_storageList.size() - 1; i >= 0; --i) // iterate in reverse order
constData.m_storageList[i].activateStorage(0);
if (constData.m_parentContainer)
deactivateStorages(constData.m_parentContainer);
}
TaskContainer *m_container = nullptr;
};
template <typename Handler, typename ...Args,
typename ReturnType = typename std::invoke_result_t<Handler, Args...>>
ReturnType invokeHandler(TaskContainer *container, Handler &&handler, Args &&...args)
{
StorageActivator activator(container);
GuardLocker locker(container->m_constData.m_taskTreePrivate->m_guard);
return std::invoke(std::forward<Handler>(handler), std::forward<Args>(args)...);
}
static QList<TaskNode *> createChildren(TaskTreePrivate *taskTreePrivate, TaskContainer *container, static QList<TaskNode *> createChildren(TaskTreePrivate *taskTreePrivate, TaskContainer *container,
const TaskItem &task) const TaskItem &task)
{ {
QList<TaskNode *> result; QList<TaskNode *> result;
const QList<TaskItem> &children = task.children(); const QList<TaskItem> &children = task.children();
for (const TaskItem &child : children) for (const TaskItem &child : children)
result.append(new TaskNode(taskTreePrivate, container, child)); result.append(new TaskNode(taskTreePrivate, child, container));
return result; return result;
} }
TaskContainer::TaskContainer(TaskTreePrivate *taskTreePrivate, TaskContainer *parentContainer, TaskContainer::ConstData::ConstData(TaskTreePrivate *taskTreePrivate, const TaskItem &task,
const TaskItem &task) TaskContainer *parentContainer, TaskContainer *thisContainer)
: m_taskTreePrivate(taskTreePrivate) : m_taskTreePrivate(taskTreePrivate)
, m_parentContainer(parentContainer) , m_parentContainer(parentContainer)
, m_parallelLimit(task.parallelLimit()) , m_parallelLimit(task.parallelLimit())
, m_workflowPolicy(task.workflowPolicy()) , m_workflowPolicy(task.workflowPolicy())
, m_groupHandler(task.groupHandler()) , m_groupHandler(task.groupHandler())
, m_storageList(taskTreePrivate->addStorages(task.storageList())) , m_storageList(taskTreePrivate->addStorages(task.storageList()))
, m_children(createChildren(taskTreePrivate, this, task)) , m_children(createChildren(taskTreePrivate, thisContainer, task))
, m_taskCount(std::accumulate(m_children.cbegin(), m_children.cend(), 0, , m_taskCount(std::accumulate(m_children.cbegin(), m_children.cend(), 0,
[](int r, TaskNode *n) { return r + n->taskCount(); })) [](int r, TaskNode *n) { return r + n->taskCount(); }))
{}
QList<int> TaskContainer::RuntimeData::createStorages(const TaskContainer::ConstData &constData)
{ {
QList<int> storageIdList;
for (const TreeStorageBase &storage : constData.m_storageList) {
const int storageId = storage.createStorage();
storageIdList.append(storageId);
constData.m_taskTreePrivate->callSetupHandler(storage, storageId);
}
return storageIdList;
} }
TaskContainer::~TaskContainer() TaskContainer::RuntimeData::RuntimeData(const ConstData &constData)
: m_constData(constData)
, m_storageIdList(createStorages(constData))
{ {
qDeleteAll(m_children); m_successBit = m_constData.m_workflowPolicy != WorkflowPolicy::StopOnDone
deleteStorages(); && m_constData.m_workflowPolicy != WorkflowPolicy::ContinueOnDone;
}
TaskContainer::RuntimeData::~RuntimeData()
{
for (int i = m_constData.m_storageList.size() - 1; i >= 0; --i) { // iterate in reverse order
const TreeStorageBase storage = m_constData.m_storageList[i];
const int storageId = m_storageIdList.value(i);
m_constData.m_taskTreePrivate->callDoneHandler(storage, storageId);
storage.deleteStorage(storageId);
}
}
bool TaskContainer::RuntimeData::updateSuccessBit(bool success)
{
if (m_constData.m_workflowPolicy == WorkflowPolicy::Optional)
return m_successBit;
const bool donePolicy = m_constData.m_workflowPolicy == WorkflowPolicy::StopOnDone
|| m_constData.m_workflowPolicy == WorkflowPolicy::ContinueOnDone;
m_successBit = donePolicy ? (m_successBit || success) : (m_successBit && success);
return m_successBit;
}
int TaskContainer::RuntimeData::currentLimit() const
{
const int childCount = m_constData.m_children.size();
return m_constData.m_parallelLimit
? qMin(m_doneCount + m_constData.m_parallelLimit, childCount) : childCount;
} }
TaskAction TaskContainer::start() TaskAction TaskContainer::start()
{ {
m_doneCount = 0; QTC_CHECK(!isRunning());
resetSuccessBit(); m_runtimeData.emplace(m_constData);
createStorages();
TaskAction groupAction = m_children.isEmpty() ? TaskAction::StopWithDone : TaskAction::Continue; TaskAction startAction = TaskAction::Continue;
if (m_groupHandler.m_setupHandler) { if (m_constData.m_groupHandler.m_setupHandler) {
StorageActivator activator(*this); startAction = invokeHandler(this, m_constData.m_groupHandler.m_setupHandler);
GuardLocker locker(m_taskTreePrivate->m_guard); if (startAction != TaskAction::Continue)
groupAction = m_groupHandler.m_setupHandler(); m_constData.m_taskTreePrivate->advanceProgress(m_constData.m_taskCount);
} }
if (m_constData.m_children.isEmpty() && startAction == TaskAction::Continue)
startAction = TaskAction::StopWithDone;
return continueStart(startAction, 0);
}
if (groupAction == TaskAction::Continue) TaskAction TaskContainer::continueStart(TaskAction startAction, int nextChild)
groupAction = startChildren(0); {
else const TaskAction groupAction = startAction == TaskAction::Continue ? startChildren(nextChild)
m_taskTreePrivate->advanceProgress(m_taskCount); : startAction;
QTC_CHECK(isRunning()); // TODO: superfluous
if (groupAction != TaskAction::Continue) { if (groupAction != TaskAction::Continue) {
updateSuccessBit(groupAction == TaskAction::StopWithDone); const bool success = m_runtimeData->updateSuccessBit(groupAction == TaskAction::StopWithDone);
groupDone(); invokeEndHandler();
if (TaskContainer *parentContainer = m_constData.m_parentContainer) {
QTC_CHECK(parentContainer->isRunning());
if (!parentContainer->isStarting())
parentContainer->childDone(success);
} else if (success) {
m_constData.m_taskTreePrivate->emitDone();
} else {
m_constData.m_taskTreePrivate->emitError();
}
} }
return groupAction; return groupAction;
} }
TaskAction TaskContainer::startChildren(int nextChild) TaskAction TaskContainer::startChildren(int nextChild)
{ {
GuardLocker locker(m_startGuard); QTC_CHECK(isRunning());
const int childCount = m_children.size(); GuardLocker locker(m_runtimeData->m_startGuard);
const int childCount = m_constData.m_children.size();
for (int i = nextChild; i < childCount; ++i) { for (int i = nextChild; i < childCount; ++i) {
const int limit = currentLimit(); const int limit = m_runtimeData->currentLimit();
if (i >= limit) if (i >= limit)
break; break;
const TaskAction startAction = m_children.at(i)->start(); const TaskAction startAction = m_constData.m_children.at(i)->start();
if (startAction == TaskAction::Continue) if (startAction == TaskAction::Continue)
continue; continue;
@@ -392,160 +478,58 @@ TaskAction TaskContainer::startChildren(int nextChild)
int skippedTaskCount = 0; int skippedTaskCount = 0;
// Skip scheduled but not run yet. The current (i) was already notified. // Skip scheduled but not run yet. The current (i) was already notified.
for (int j = i + 1; j < limit; ++j) for (int j = i + 1; j < limit; ++j)
skippedTaskCount += m_children.at(j)->taskCount(); skippedTaskCount += m_constData.m_children.at(j)->taskCount();
m_taskTreePrivate->advanceProgress(skippedTaskCount); m_constData.m_taskTreePrivate->advanceProgress(skippedTaskCount);
return finalizeAction; return finalizeAction;
} }
return TaskAction::Continue; return TaskAction::Continue;
} }
TaskAction TaskContainer::childDone(bool success)
{
QTC_CHECK(isRunning());
const int limit = m_runtimeData->currentLimit(); // Read before bumping m_doneCount and stop()
const bool shouldStop
= (m_constData.m_workflowPolicy == WorkflowPolicy::StopOnDone && success)
| (m_constData.m_workflowPolicy == WorkflowPolicy::StopOnError && !success);
if (shouldStop)
stop();
++m_runtimeData->m_doneCount;
const bool updatedSuccess = m_runtimeData->updateSuccessBit(success);
const TaskAction startAction
= (shouldStop || m_runtimeData->m_doneCount == m_constData.m_children.size())
? toTaskAction(updatedSuccess) : TaskAction::Continue;
if (isStarting())
return startAction;
return continueStart(startAction, limit);
}
void TaskContainer::stop() void TaskContainer::stop()
{ {
if (!isRunning()) if (!isRunning())
return; return;
const int childCount = m_children.size(); const int limit = m_runtimeData->currentLimit();
const int limit = currentLimit();
for (int i = 0; i < limit; ++i) for (int i = 0; i < limit; ++i)
m_children.at(i)->stop(); m_constData.m_children.at(i)->stop();
int skippedTaskCount = 0; int skippedTaskCount = 0;
for (int i = limit; i < childCount; ++i) for (int i = limit; i < m_constData.m_children.size(); ++i)
skippedTaskCount += m_children.at(i)->taskCount(); skippedTaskCount += m_constData.m_children.at(i)->taskCount();
m_taskTreePrivate->advanceProgress(skippedTaskCount); m_constData.m_taskTreePrivate->advanceProgress(skippedTaskCount);
m_doneCount = -1;
}
int TaskContainer::currentLimit() const
{
const int childCount = m_children.size();
return m_parallelLimit ? qMin(m_doneCount + m_parallelLimit, childCount) : childCount;
}
TaskAction TaskContainer::childDone(bool success)
{
const int limit = currentLimit();
const bool shouldStop = (m_workflowPolicy == WorkflowPolicy::StopOnDone && success)
|| (m_workflowPolicy == WorkflowPolicy::StopOnError && !success);
if (shouldStop)
stop();
else
++m_doneCount;
updateSuccessBit(success);
TaskAction groupAction = (shouldStop || m_doneCount == m_children.size())
? toTaskAction(m_successBit) : TaskAction::Continue;
if (isStarting())
return groupAction;
if (groupAction == TaskAction::Continue) {
groupAction = startChildren(limit);
if (groupAction != TaskAction::Continue)
updateSuccessBit(groupAction == TaskAction::StopWithDone);
}
if (groupAction != TaskAction::Continue)
groupDone();
return groupAction;
}
void TaskContainer::groupDone()
{
invokeEndHandler();
if (m_parentContainer) {
if (!m_parentContainer->isStarting())
m_parentContainer->childDone(m_successBit);
return;
}
if (m_successBit)
m_taskTreePrivate->emitDone();
else
m_taskTreePrivate->emitError();
} }
void TaskContainer::invokeEndHandler() void TaskContainer::invokeEndHandler()
{ {
m_doneCount = -1; const TaskItem::GroupHandler &groupHandler = m_constData.m_groupHandler;
const bool callDoneHandler = m_successBit && m_groupHandler.m_doneHandler; if (m_runtimeData->m_successBit && groupHandler.m_doneHandler)
const bool callErrorHandler = !m_successBit && m_groupHandler.m_errorHandler; invokeHandler(this, groupHandler.m_doneHandler);
if (callDoneHandler || callErrorHandler) { else if (!m_runtimeData->m_successBit && groupHandler.m_errorHandler)
StorageActivator activator(*this); invokeHandler(this, groupHandler.m_errorHandler);
GuardLocker locker(m_taskTreePrivate->m_guard); m_runtimeData.reset();
if (callDoneHandler)
m_groupHandler.m_doneHandler();
else if (callErrorHandler)
m_groupHandler.m_errorHandler();
}
deleteStorages();
}
void TaskContainer::resetSuccessBit()
{
if (m_children.isEmpty())
m_successBit = true;
if (m_workflowPolicy == WorkflowPolicy::StopOnDone
|| m_workflowPolicy == WorkflowPolicy::ContinueOnDone) {
m_successBit = false;
} else {
m_successBit = true;
}
}
void TaskContainer::updateSuccessBit(bool success)
{
if (m_workflowPolicy == WorkflowPolicy::Optional)
return;
if (m_workflowPolicy == WorkflowPolicy::StopOnDone
|| m_workflowPolicy == WorkflowPolicy::ContinueOnDone) {
m_successBit = m_successBit || success;
} else {
m_successBit = m_successBit && success;
}
}
void TaskContainer::createStorages()
{
QTC_CHECK(m_storageIdList.isEmpty());
for (int i = 0; i < m_storageList.size(); ++i) {
TreeStorageBase storage = m_storageList[i];
const int storageId = storage.createStorage();
m_storageIdList << storageId;
m_taskTreePrivate->callSetupHandler(storage, storageId);
}
}
void TaskContainer::deleteStorages()
{
for (int i = 0; i < m_storageIdList.size(); ++i) { // iterate in reverse order?
TreeStorageBase storage = m_storageList[i];
const int storageId = m_storageIdList.value(i);
m_taskTreePrivate->callDoneHandler(storage, storageId);
storage.deleteStorage(storageId);
}
m_storageIdList.clear();
}
void TaskContainer::activateStorages()
{
if (m_parentContainer)
m_parentContainer->activateStorages();
for (int i = 0; i < m_storageList.size(); ++i)
m_storageList[i].activateStorage(m_storageIdList.value(i));
}
void TaskContainer::deactivateStorages()
{
for (int i = 0; i < m_storageList.size(); ++i) // iterate in reverse order?
m_storageList[i].activateStorage(0);
if (m_parentContainer)
m_parentContainer->deactivateStorages();
} }
TaskAction TaskNode::start() TaskAction TaskNode::start()
@@ -554,40 +538,25 @@ TaskAction TaskNode::start()
if (!isTask()) if (!isTask())
return m_container.start(); return m_container.start();
const auto finalize = [this] {
m_container.m_taskTreePrivate->advanceProgress(1);
m_task.release()->deleteLater();
};
m_task.reset(m_taskHandler.m_createHandler()); m_task.reset(m_taskHandler.m_createHandler());
{ const TaskAction startAction = invokeHandler(parentContainer(), m_taskHandler.m_setupHandler,
TaskAction action = TaskAction::Continue; *m_task.get());
{ if (startAction != TaskAction::Continue) {
StorageActivator activator(m_container); m_container.m_constData.m_taskTreePrivate->advanceProgress(1);
GuardLocker locker(m_container.m_taskTreePrivate->m_guard); m_task.reset();
action = m_taskHandler.m_setupHandler(*m_task.get()); return startAction;
} }
if (action != TaskAction::Continue) { const std::shared_ptr<TaskAction> unwindAction
finalize(); = std::make_shared<TaskAction>(TaskAction::Continue);
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; invokeEndHandler(success);
const bool callErrorHandler = !success && m_taskHandler.m_errorHandler; disconnect(m_task.get(), &TaskInterface::done, this, nullptr);
if (callDoneHandler || callErrorHandler) { m_task.release()->deleteLater();
StorageActivator activator(m_container); QTC_ASSERT(parentContainer()->isRunning(), return);
GuardLocker locker(m_container.m_taskTreePrivate->m_guard); if (parentContainer()->isStarting())
if (callDoneHandler)
m_taskHandler.m_doneHandler(*m_task.get());
else if (callErrorHandler)
m_taskHandler.m_errorHandler(*m_task.get());
}
finalize();
if (m_container.m_parentContainer->isStarting())
*unwindAction = toTaskAction(success); *unwindAction = toTaskAction(success);
else else
m_container.m_parentContainer->childDone(success); parentContainer()->childDone(success);
}); });
m_task->start(); m_task->start();
@@ -607,28 +576,17 @@ void TaskNode::stop()
// TODO: cancelHandler? // TODO: cancelHandler?
// TODO: call TaskInterface::stop() ? // TODO: call TaskInterface::stop() ?
if (m_taskHandler.m_errorHandler) { invokeEndHandler(false);
StorageActivator activator(m_container);
GuardLocker locker(m_container.m_taskTreePrivate->m_guard);
m_taskHandler.m_errorHandler(*m_task.get());
}
m_container.m_taskTreePrivate->advanceProgress(1);
m_task.reset(); m_task.reset();
} }
bool TaskNode::isRunning() const void TaskNode::invokeEndHandler(bool success)
{ {
return m_task || m_container.isRunning(); if (success && m_taskHandler.m_doneHandler)
} invokeHandler(parentContainer(), m_taskHandler.m_doneHandler, *m_task.get());
else if (!success && m_taskHandler.m_errorHandler)
bool TaskNode::isTask() const invokeHandler(parentContainer(), m_taskHandler.m_errorHandler, *m_task.get());
{ m_container.m_constData.m_taskTreePrivate->advanceProgress(1);
return m_taskHandler.m_createHandler && m_taskHandler.m_setupHandler;
}
int TaskNode::taskCount() const
{
return isTask() ? 1 : m_container.m_taskCount;
} }
/*! /*!
@@ -698,7 +656,7 @@ void TaskTree::setupRoot(const Tasking::Group &root)
QTC_ASSERT(!d->m_guard.isLocked(), qWarning("The setupRoot() is called from one of the" QTC_ASSERT(!d->m_guard.isLocked(), qWarning("The setupRoot() is called from one of the"
"TaskTree handlers, ingoring..."); return); "TaskTree handlers, ingoring..."); return);
d->m_storages.clear(); d->m_storages.clear();
d->m_root.reset(new TaskNode(d, nullptr, root)); d->m_root.reset(new TaskNode(d, root, nullptr));
} }
void TaskTree::start() void TaskTree::start()

View File

@@ -11,8 +11,8 @@
namespace Utils { namespace Utils {
class StorageActivator;
class TaskContainer; class TaskContainer;
class TaskTreePrivate; class TaskTreePrivate;
namespace Tasking { namespace Tasking {
@@ -66,6 +66,7 @@ private:
QSharedPointer<StorageData> m_storageData; QSharedPointer<StorageData> m_storageData;
friend TaskContainer; friend TaskContainer;
friend TaskTreePrivate; friend TaskTreePrivate;
friend StorageActivator;
}; };
template <typename StorageStruct> template <typename StorageStruct>