TaskTree: Introduce WaitFor, Condition and ConditionActivator

WaitFor(condition) Group element enables postponing Group's
children execution until the condition is met. Use
ConditionActivator::activate() method to signal that the condition
is met. A call to ConditionActivator::activate() schedules a request
to the TaskTree instructing it to resume execution of awaiting
condition.

The Group containing the WaitFor element is started itself,
and its setup handler is being called. If setup handler
returned TaskAction::Continue, the children execution is being
postponed. Otherwise, when StopWithDone or StopWithError is
returned, the Group finishes and WaitFor element is no-op in
this context.

This functionality is going to be used when some part of the
task tree may continue only after some data has been collected,
and data collection took place not from inside start or done
handlers. The example is running debugger for already started
process - the debugger may run after the process already started,
delivered its PID and it's still running. In this way
we may start a debugger process in parallel in right point of time.

This patch implements the 5th point inside QTCREATORBUG-28741.

Task-number: QTCREATORBUG-28741
Change-Id: I4afaedb0b34fe0383c16a6d1f74bf07f74cc088a
Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
Reviewed-by: hjk <hjk@qt.io>
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
This commit is contained in:
Jarek Kobus
2023-02-20 21:32:00 +01:00
parent 61de69ea90
commit 29f634a8ca
3 changed files with 482 additions and 176 deletions

View File

@@ -74,6 +74,54 @@ void TreeStorageBase::activateStorage(int id) const
m_storageData->m_activeStorage = id;
}
Condition::Condition()
: m_conditionData(new ConditionData()) {}
Condition::ConditionData::~ConditionData()
{
QTC_CHECK(m_activatorHash.isEmpty());
qDeleteAll(m_activatorHash);
}
ConditionActivator *Condition::activator() const
{
QTC_ASSERT(m_conditionData->m_activeActivator, return nullptr);
const auto it = m_conditionData->m_activatorHash.constFind(m_conditionData->m_activeActivator);
QTC_ASSERT(it != m_conditionData->m_activatorHash.constEnd(), return nullptr);
return it.value();
}
int Condition::createActivator(TaskNode *node) const
{
QTC_ASSERT(m_conditionData->m_activeActivator == 0, return 0); // TODO: should be allowed?
const int newId = ++m_conditionData->m_activatorCounter;
m_conditionData->m_activatorHash.insert(newId, new ConditionActivator(node));
return newId;
}
void Condition::deleteActivator(int id) const
{
QTC_ASSERT(m_conditionData->m_activeActivator == 0, return); // TODO: should be allowed?
const auto it = m_conditionData->m_activatorHash.constFind(id);
QTC_ASSERT(it != m_conditionData->m_activatorHash.constEnd(), return);
delete it.value();
m_conditionData->m_activatorHash.erase(it);
}
// passing 0 deactivates currently active condition
void Condition::activateActivator(int id) const
{
if (id == 0) {
QTC_ASSERT(m_conditionData->m_activeActivator, return);
m_conditionData->m_activeActivator = 0;
return;
}
QTC_ASSERT(m_conditionData->m_activeActivator == 0, return);
const auto it = m_conditionData->m_activatorHash.find(id);
QTC_ASSERT(it != m_conditionData->m_activatorHash.end(), return);
m_conditionData->m_activeActivator = id;
}
ParallelLimit sequential(1);
ParallelLimit parallel(0);
Workflow stopOnError(WorkflowPolicy::StopOnError);
@@ -84,20 +132,21 @@ Workflow optional(WorkflowPolicy::Optional);
void TaskItem::addChildren(const QList<TaskItem> &children)
{
QTC_ASSERT(m_type == Type::Group, qWarning("Only Task may have children, skipping..."); return);
QTC_ASSERT(m_type == Type::Group, qWarning("Only Group may have children, skipping...");
return);
for (const TaskItem &child : children) {
switch (child.m_type) {
case Type::Group:
m_children.append(child);
break;
case Type::Limit:
QTC_ASSERT(m_type == Type::Group,
qWarning("Mode may only be a child of Group, skipping..."); return);
QTC_ASSERT(m_type == Type::Group, qWarning("Execution Mode may only be a child of a "
"Group, skipping..."); return);
m_parallelLimit = child.m_parallelLimit; // TODO: Assert on redefinition?
break;
case Type::Policy:
QTC_ASSERT(m_type == Type::Group,
qWarning("Workflow Policy may only be a child of Group, skipping..."); return);
QTC_ASSERT(m_type == Type::Group, qWarning("Workflow Policy may only be a child of a "
"Group, skipping..."); return);
m_workflowPolicy = child.m_workflowPolicy; // TODO: Assert on redefinition?
break;
case Type::TaskHandler:
@@ -109,7 +158,7 @@ void TaskItem::addChildren(const QList<TaskItem> &children)
break;
case Type::GroupHandler:
QTC_ASSERT(m_type == Type::Group, qWarning("Group Handler may only be a "
"child of Group, skipping..."); break);
"child of a Group, skipping..."); break);
QTC_ASSERT(!child.m_groupHandler.m_setupHandler
|| !m_groupHandler.m_setupHandler,
qWarning("Group Setup Handler redefinition, overriding..."));
@@ -126,6 +175,12 @@ void TaskItem::addChildren(const QList<TaskItem> &children)
if (child.m_groupHandler.m_errorHandler)
m_groupHandler.m_errorHandler = child.m_groupHandler.m_errorHandler;
break;
case Type::Condition:
QTC_ASSERT(m_type == Type::Group, qWarning("WaitFor may only be a child of a Group, "
"skipping..."); break);
QTC_ASSERT(!m_condition, qWarning("WaitFor redefinition, overriding..."));
m_condition = child.m_condition;
break;
case Type::Storage:
m_storageList.append(child.m_storageList);
break;
@@ -140,147 +195,25 @@ using namespace Tasking;
class TaskTreePrivate;
class TaskNode;
class TaskContainer
{
public:
TaskContainer(TaskTreePrivate *taskTreePrivate, const TaskItem &task,
TaskContainer *parentContainer)
: m_constData(taskTreePrivate, task, parentContainer, this) {}
TaskAction start();
TaskAction continueStart(TaskAction startAction, int nextChild);
TaskAction startChildren(int nextChild);
TaskAction childDone(bool success);
void stop();
void invokeEndHandler();
bool isRunning() const { return m_runtimeData.has_value(); }
bool isStarting() const { return isRunning() && m_runtimeData->m_startGuard.isLocked(); }
struct ConstData {
ConstData(TaskTreePrivate *taskTreePrivate, const TaskItem &task,
TaskContainer *parentContainer, TaskContainer *thisContainer);
~ConstData() { qDeleteAll(m_children); }
TaskTreePrivate * const m_taskTreePrivate = nullptr;
TaskContainer * const m_parentContainer = nullptr;
const int m_parallelLimit = 1;
const WorkflowPolicy m_workflowPolicy = WorkflowPolicy::StopOnError;
const TaskItem::GroupHandler m_groupHandler;
const QList<TreeStorageBase> m_storageList;
const QList<TaskNode *> m_children;
const int m_taskCount = 0;
};
struct RuntimeData {
RuntimeData(const ConstData &constData);
~RuntimeData();
static QList<int> createStorages(const TaskContainer::ConstData &constData);
void callStorageDoneHandlers();
bool updateSuccessBit(bool success);
int currentLimit() const;
const ConstData &m_constData;
const QList<int> m_storageIdList;
int m_doneCount = 0;
bool m_successBit = true;
Guard m_startGuard;
};
const ConstData m_constData;
std::optional<RuntimeData> m_runtimeData;
};
class TaskNode : public QObject
{
public:
TaskNode(TaskTreePrivate *taskTreePrivate, const TaskItem &task,
TaskContainer *parentContainer)
: m_taskHandler(task.taskHandler())
, m_container(taskTreePrivate, task, parentContainer)
{}
// If returned value != Continue, childDone() needs to be called in parent container (in caller)
// in order to unwind properly.
TaskAction start();
void stop();
void invokeEndHandler(bool success);
bool isRunning() const { return m_task || m_container.isRunning(); }
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:
const TaskItem::TaskHandler m_taskHandler;
TaskContainer m_container;
std::unique_ptr<TaskInterface> m_task;
};
class TaskTreePrivate
{
public:
TaskTreePrivate(TaskTree *taskTree)
: q(taskTree) {}
void start() {
QTC_ASSERT(m_root, return);
m_progressValue = 0;
emitStartedAndProgress();
// TODO: check storage handlers for not existing storages in tree
for (auto it = m_storageHandlers.cbegin(); it != m_storageHandlers.cend(); ++it) {
QTC_ASSERT(m_storages.contains(it.key()), qWarning("The registered storage doesn't "
"exist in task tree. Its handlers will never be called."));
}
m_root->start();
}
void stop() {
QTC_ASSERT(m_root, return);
if (!m_root->isRunning())
return;
// TODO: should we have canceled flag (passed to handler)?
// Just one done handler with result flag:
// FinishedWithSuccess, FinishedWithError, Canceled, TimedOut.
// Canceled either directly by user, or by workflow policy - doesn't matter, in both
// cases canceled from outside.
m_root->stop();
emitError();
}
void advanceProgress(int byValue) {
if (byValue == 0)
return;
QTC_CHECK(byValue > 0);
QTC_CHECK(m_progressValue + byValue <= m_root->taskCount());
m_progressValue += byValue;
emitProgress();
}
void emitStartedAndProgress() {
GuardLocker locker(m_guard);
emit q->started();
emit q->progressValueChanged(m_progressValue);
}
void emitProgress() {
GuardLocker locker(m_guard);
emit q->progressValueChanged(m_progressValue);
}
void emitDone() {
QTC_CHECK(m_progressValue == m_root->taskCount());
GuardLocker locker(m_guard);
emit q->done();
}
void emitError() {
QTC_CHECK(m_progressValue == m_root->taskCount());
GuardLocker locker(m_guard);
emit q->errorOccurred();
}
QList<TreeStorageBase> addStorages(const QList<TreeStorageBase> &storages) {
QList<TreeStorageBase> addedStorages;
for (const TreeStorageBase &storage : storages) {
QTC_ASSERT(!m_storages.contains(storage), qWarning("Can't add the same storage into "
"one TaskTree twice, skipping..."); continue);
addedStorages << storage;
m_storages << storage;
}
return addedStorages;
}
void start();
void stop();
void advanceProgress(int byValue);
void emitStartedAndProgress();
void emitProgress();
void emitDone();
void emitError();
bool addCondition(const TaskItem &task, TaskContainer *container);
void createConditionActivators();
void deleteConditionActivators();
void activateConditions();
void deactivateConditions();
QList<TreeStorageBase> addStorages(const QList<TreeStorageBase> &storages);
void callSetupHandler(TreeStorageBase storage, int storageId) {
callStorageHandler(storage, storageId, &StorageHandler::m_setupHandler);
}
@@ -308,36 +241,252 @@ public:
TaskTree *q = nullptr;
Guard m_guard;
int m_progressValue = 0;
QHash<Condition, TaskContainer *> m_conditions;
QSet<TreeStorageBase> m_storages;
QHash<TreeStorageBase, StorageHandler> m_storageHandlers;
std::unique_ptr<TaskNode> m_root = nullptr; // Keep me last in order to destruct first
};
class StorageActivator
class TaskContainer
{
public:
StorageActivator(TaskContainer *container)
: m_container(container) { activateStorages(m_container); }
~StorageActivator() { deactivateStorages(m_container); }
TaskContainer(TaskTreePrivate *taskTreePrivate, const TaskItem &task,
TaskNode *parentNode, TaskContainer *parentContainer)
: m_constData(taskTreePrivate, task, parentNode, parentContainer, this)
, m_conditionData(taskTreePrivate->addCondition(task, this)
? ConditionData() : std::optional<ConditionData>()) {}
TaskAction start();
TaskAction continueStart(TaskAction startAction, int nextChild);
TaskAction startChildren(int nextChild);
TaskAction childDone(bool success);
void activateCondition();
void stop();
void invokeEndHandler();
bool isRunning() const { return m_runtimeData.has_value(); }
bool isStarting() const { return isRunning() && m_runtimeData->m_startGuard.isLocked(); }
struct ConstData {
ConstData(TaskTreePrivate *taskTreePrivate, const TaskItem &task, TaskNode *parentNode,
TaskContainer *parentContainer, TaskContainer *thisContainer);
~ConstData() { qDeleteAll(m_children); }
TaskTreePrivate * const m_taskTreePrivate = nullptr;
TaskNode * const m_parentNode = nullptr;
TaskContainer * const m_parentContainer = nullptr;
const int m_parallelLimit = 1;
const WorkflowPolicy m_workflowPolicy = WorkflowPolicy::StopOnError;
const TaskItem::GroupHandler m_groupHandler;
const QList<TreeStorageBase> m_storageList;
const QList<TaskNode *> m_children;
const int m_taskCount = 0;
};
struct ConditionData {
bool m_activated = false;
int m_conditionId = 0;
};
struct RuntimeData {
RuntimeData(const ConstData &constData);
~RuntimeData();
static QList<int> createStorages(const TaskContainer::ConstData &constData);
void callStorageDoneHandlers();
bool updateSuccessBit(bool success);
int currentLimit() const;
const ConstData &m_constData;
const QList<int> m_storageIdList;
int m_doneCount = 0;
bool m_successBit = true;
Guard m_startGuard;
};
const ConstData m_constData;
std::optional<ConditionData> m_conditionData;
std::optional<RuntimeData> m_runtimeData;
};
class TaskNode : public QObject
{
public:
TaskNode(TaskTreePrivate *taskTreePrivate, const TaskItem &task,
TaskContainer *parentContainer)
: m_taskHandler(task.taskHandler())
, m_container(taskTreePrivate, task, this, parentContainer)
{}
// If returned value != Continue, childDone() needs to be called in parent container (in caller)
// in order to unwind properly.
TaskAction start();
void stop();
void invokeEndHandler(bool success);
bool isRunning() const { return m_task || m_container.isRunning(); }
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; }
void activateCondition();
private:
static void activateStorages(TaskContainer *container)
const TaskItem::TaskHandler m_taskHandler;
TaskContainer m_container;
std::unique_ptr<TaskInterface> m_task;
};
void TaskTreePrivate::start()
{
QTC_ASSERT(m_root, return);
m_progressValue = 0;
emitStartedAndProgress();
// TODO: check storage handlers for not existing storages in tree
for (auto it = m_storageHandlers.cbegin(); it != m_storageHandlers.cend(); ++it) {
QTC_ASSERT(m_storages.contains(it.key()), qWarning("The registered storage doesn't "
"exist in task tree. Its handlers will never be called."));
}
createConditionActivators();
m_root->start();
}
void TaskTreePrivate::stop()
{
QTC_ASSERT(m_root, return);
if (!m_root->isRunning())
return;
// TODO: should we have canceled flag (passed to handler)?
// Just one done handler with result flag:
// FinishedWithSuccess, FinishedWithError, Canceled, TimedOut.
// Canceled either directly by user, or by workflow policy - doesn't matter, in both
// cases canceled from outside.
m_root->stop();
emitError();
}
void TaskTreePrivate::advanceProgress(int byValue)
{
if (byValue == 0)
return;
QTC_CHECK(byValue > 0);
QTC_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);
}
void TaskTreePrivate::emitDone()
{
deleteConditionActivators();
QTC_CHECK(m_progressValue == m_root->taskCount());
GuardLocker locker(m_guard);
emit q->done();
}
void TaskTreePrivate::emitError()
{
deleteConditionActivators();
QTC_CHECK(m_progressValue == m_root->taskCount());
GuardLocker locker(m_guard);
emit q->errorOccurred();
}
bool TaskTreePrivate::addCondition(const TaskItem &task, TaskContainer *container)
{
if (!task.condition())
return false;
QTC_ASSERT(!m_conditions.contains(*task.condition()), qWarning("Can't add the same condition "
"into one TaskTree twice, skipping..."); return false);
m_conditions.insert(*task.condition(), container);
return true;
}
void TaskTreePrivate::createConditionActivators()
{
for (auto it = m_conditions.cbegin(); it != m_conditions.cend(); ++it) {
Condition condition = it.key();
TaskContainer *container = it.value();
container->m_conditionData->m_conditionId
= condition.createActivator(container->m_constData.m_parentNode);
}
}
void TaskTreePrivate::deleteConditionActivators()
{
for (auto it = m_conditions.cbegin(); it != m_conditions.cend(); ++it) {
Condition condition = it.key();
TaskContainer *container = it.value();
condition.deleteActivator(container->m_conditionData->m_conditionId);
container->m_conditionData = TaskContainer::ConditionData();
}
}
void TaskTreePrivate::activateConditions()
{
for (auto it = m_conditions.cbegin(); it != m_conditions.cend(); ++it) {
Condition condition = it.key();
TaskContainer *container = it.value();
condition.activateActivator(container->m_conditionData->m_conditionId);
}
}
void TaskTreePrivate::deactivateConditions()
{
for (auto it = m_conditions.cbegin(); it != m_conditions.cend(); ++it)
it.key().activateActivator(0);
}
QList<TreeStorageBase> TaskTreePrivate::addStorages(const QList<TreeStorageBase> &storages)
{
QList<TreeStorageBase> addedStorages;
for (const TreeStorageBase &storage : storages) {
QTC_ASSERT(!m_storages.contains(storage), qWarning("Can't add the same storage into "
"one TaskTree twice, skipping..."); continue);
addedStorages << storage;
m_storages << storage;
}
return addedStorages;
}
// TODO: Activate/deactivate Conditions
class ExecutionContextActivator
{
public:
ExecutionContextActivator(TaskContainer *container)
: m_container(container) { activateContext(m_container); }
~ExecutionContextActivator() { deactivateContext(m_container); }
private:
static void activateContext(TaskContainer *container)
{
QTC_ASSERT(container && container->isRunning(), return);
const TaskContainer::ConstData &constData = container->m_constData;
if (constData.m_parentContainer)
activateStorages(constData.m_parentContainer);
activateContext(constData.m_parentContainer);
else
constData.m_taskTreePrivate->activateConditions();
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)
static void deactivateContext(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);
deactivateContext(constData.m_parentContainer);
else
constData.m_taskTreePrivate->deactivateConditions();
}
TaskContainer *m_container = nullptr;
};
@@ -346,7 +495,7 @@ 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);
ExecutionContextActivator activator(container);
GuardLocker locker(container->m_constData.m_taskTreePrivate->m_guard);
return std::invoke(std::forward<Handler>(handler), std::forward<Args>(args)...);
}
@@ -362,8 +511,10 @@ static QList<TaskNode *> createChildren(TaskTreePrivate *taskTreePrivate, TaskCo
}
TaskContainer::ConstData::ConstData(TaskTreePrivate *taskTreePrivate, const TaskItem &task,
TaskContainer *parentContainer, TaskContainer *thisContainer)
TaskNode *parentNode, TaskContainer *parentContainer,
TaskContainer *thisContainer)
: m_taskTreePrivate(taskTreePrivate)
, m_parentNode(parentNode)
, m_parentContainer(parentContainer)
, m_parallelLimit(task.parallelLimit())
, m_workflowPolicy(task.workflowPolicy())
@@ -440,8 +591,12 @@ TaskAction TaskContainer::start()
if (startAction != TaskAction::Continue)
m_constData.m_taskTreePrivate->advanceProgress(m_constData.m_taskCount);
}
if (m_constData.m_children.isEmpty() && startAction == TaskAction::Continue)
startAction = TaskAction::StopWithDone;
if (startAction == TaskAction::Continue) {
if (m_conditionData && !m_conditionData->m_activated) // Group has condition and it wasn't activated yet
return TaskAction::Continue;
if (m_constData.m_children.isEmpty())
startAction = TaskAction::StopWithDone;
}
return continueStart(startAction, 0);
}
@@ -513,6 +668,35 @@ TaskAction TaskContainer::childDone(bool success)
return continueStart(startAction, limit);
}
void ConditionActivator::activate()
{
m_node->activateCondition();
}
void TaskContainer::activateCondition()
{
QTC_ASSERT(m_conditionData, return);
if (!m_constData.m_taskTreePrivate->m_root->isRunning())
return;
if (!isRunning())
return; // Condition not run yet or group already skipped or stopped
if (!m_conditionData->m_activated)
return; // May it happen that scheduled call is coming from previous TaskTree's start?
if (m_runtimeData->m_doneCount != 0)
return; // In meantime the group was started
for (TaskNode *child : m_constData.m_children) {
if (child->isRunning())
return; // In meantime the group was started
}
const TaskAction startAction = m_constData.m_children.isEmpty() ? TaskAction::StopWithDone
: TaskAction::Continue;
continueStart(startAction, 0);
}
void TaskContainer::stop()
{
if (!isRunning())
@@ -597,6 +781,26 @@ void TaskNode::invokeEndHandler(bool success)
m_container.m_constData.m_taskTreePrivate->advanceProgress(1);
}
void TaskNode::activateCondition()
{
QTC_ASSERT(m_container.m_conditionData, return);
QTC_ASSERT(m_container.m_constData.m_taskTreePrivate->m_root->isRunning(), return);
if (m_container.m_conditionData->m_activated)
return; // Was already activated
m_container.m_conditionData->m_activated = true;
if (!isRunning())
return; // Condition not run yet or group already skipped or stopped
QTC_CHECK(m_container.m_runtimeData->m_doneCount == 0);
for (TaskNode *child : m_container.m_constData.m_children)
QTC_CHECK(!child->isRunning());
QMetaObject::invokeMethod(this, [this] { m_container.activateCondition(); },
Qt::QueuedConnection);
}
/*!
\class Utils::TaskTree
\inheaderfile utils/tasktree.h
@@ -1324,6 +1528,9 @@ TaskTree::~TaskTree()
{
QTC_ASSERT(!d->m_guard.isLocked(), qWarning("Deleting TaskTree instance directly from "
"one of its handlers will lead to crash!"));
if (isRunning())
d->deleteConditionActivators();
// TODO: delete storages explicitly here?
delete d;
}
@@ -1332,6 +1539,7 @@ void TaskTree::setupRoot(const Tasking::Group &root)
QTC_ASSERT(!isRunning(), qWarning("The TaskTree is already running, ignoring..."); return);
QTC_ASSERT(!d->m_guard.isLocked(), qWarning("The setupRoot() is called from one of the"
"TaskTree handlers, ingoring..."); return);
d->m_conditions.clear();
d->m_storages.clear();
d->m_root.reset(new TaskNode(d, root, nullptr));
}

View File

@@ -11,8 +11,9 @@
namespace Utils {
class StorageActivator;
class ExecutionContextActivator;
class TaskContainer;
class TaskNode;
class TaskTreePrivate;
namespace Tasking {
@@ -66,7 +67,7 @@ private:
QSharedPointer<StorageData> m_storageData;
friend TaskContainer;
friend TaskTreePrivate;
friend StorageActivator;
friend ExecutionContextActivator;
};
template <typename StorageStruct>
@@ -87,6 +88,50 @@ private:
}
};
class QTCREATOR_UTILS_EXPORT ConditionActivator
{
public:
void activate();
private:
ConditionActivator(TaskNode *container) : m_node(container) {}
TaskNode *m_node = nullptr;
friend class Condition;
};
class QTCREATOR_UTILS_EXPORT Condition
{
public:
Condition();
ConditionActivator &operator*() const noexcept { return *activator(); }
ConditionActivator *operator->() const noexcept { return activator(); }
ConditionActivator *activator() const;
private:
int createActivator(TaskNode *node) const;
void deleteActivator(int id) const;
void activateActivator(int id) const;
friend bool operator==(const Condition &first, const Condition &second)
{ return first.m_conditionData == second.m_conditionData; }
friend bool operator!=(const Condition &first, const Condition &second)
{ return first.m_conditionData != second.m_conditionData; }
friend size_t qHash(const Condition &storage, uint seed = 0)
{ return size_t(storage.m_conditionData.get()) ^ seed; }
struct ConditionData {
~ConditionData();
QHash<int, ConditionActivator *> m_activatorHash = {};
int m_activeActivator = 0; // 0 means no active activator
int m_activatorCounter = 0;
};
QSharedPointer<ConditionData> m_conditionData;
friend TaskTreePrivate;
friend ExecutionContextActivator;
};
// WorkflowPolicy:
// 1. When all children finished with done -> report done, otherwise:
// a) Report error on first error and stop executing other children (including their subtree)
@@ -143,11 +188,13 @@ public:
TaskHandler taskHandler() const { return m_taskHandler; }
GroupHandler groupHandler() const { return m_groupHandler; }
QList<TaskItem> children() const { return m_children; }
std::optional<Condition> condition() const { return m_condition; }
QList<TreeStorageBase> storageList() const { return m_storageList; }
protected:
enum class Type {
Group,
Condition,
Storage,
Limit,
Policy,
@@ -168,6 +215,9 @@ protected:
TaskItem(const GroupHandler &handler)
: m_type(Type::GroupHandler)
, m_groupHandler(handler) {}
TaskItem(const Condition &condition)
: m_type(Type::Condition)
, m_condition{condition} {}
TaskItem(const TreeStorageBase &storage)
: m_type(Type::Storage)
, m_storageList{storage} {}
@@ -179,6 +229,7 @@ private:
WorkflowPolicy m_workflowPolicy = WorkflowPolicy::StopOnError;
TaskHandler m_taskHandler;
GroupHandler m_groupHandler;
std::optional<Condition> m_condition;
QList<TreeStorageBase> m_storageList;
QList<TaskItem> m_children;
};
@@ -196,6 +247,12 @@ public:
Storage(const TreeStorageBase &storage) : TaskItem(storage) { }
};
class QTCREATOR_UTILS_EXPORT WaitFor : public TaskItem
{
public:
WaitFor(const Condition &condition) : TaskItem(condition) { }
};
class QTCREATOR_UTILS_EXPORT ParallelLimit : public TaskItem
{
public:
@@ -321,7 +378,7 @@ private:
class TaskTreePrivate;
class QTCREATOR_UTILS_EXPORT TaskTree : public QObject
class QTCREATOR_UTILS_EXPORT TaskTree final : public QObject
{
Q_OBJECT

View File

@@ -20,7 +20,8 @@ enum class Handler {
GroupSetup,
GroupDone,
GroupError,
Sync
Sync,
Activator,
};
using Log = QList<QPair<int, Handler>>;
@@ -1053,25 +1054,65 @@ void tst_TaskTree::processTree_data()
<< TestData{storage, root, log, 0, OnStart::NotRunning, OnDone::Failure};
}
const Group root {
Storage(storage),
Sync(setupSync(1, true)),
Process(setupProcess(2)),
Sync(setupSync(3, true)),
Process(setupProcess(4)),
Sync(setupSync(5, true)),
OnGroupDone(groupDone(0))
};
const Log log {
{1, Handler::Sync},
{2, Handler::Setup},
{3, Handler::Sync},
{4, Handler::Setup},
{5, Handler::Sync},
{0, Handler::GroupDone}
};
QTest::newRow("SyncAndAsync")
<< TestData{storage, root, log, 2, OnStart::Running, OnDone::Success};
{
const Group root {
Storage(storage),
Sync(setupSync(1, true)),
Process(setupProcess(2)),
Sync(setupSync(3, true)),
Process(setupProcess(4)),
Sync(setupSync(5, true)),
OnGroupDone(groupDone(0))
};
const Log log {
{1, Handler::Sync},
{2, Handler::Setup},
{3, Handler::Sync},
{4, Handler::Setup},
{5, Handler::Sync},
{0, Handler::GroupDone}
};
QTest::newRow("SyncAndAsync")
<< TestData{storage, root, log, 2, OnStart::Running, OnDone::Success};
}
{
Condition condition;
const auto setupProcessWithCondition
= [storage, condition, setupProcessHelper](int processId) {
return [storage, condition, setupProcessHelper, processId](QtcProcess &process) {
setupProcessHelper(process, {"-return", "0"}, processId);
CustomStorage *currentStorage = storage.activeStorage();
ConditionActivator *currentActivator = condition.activator();
connect(&process, &QtcProcess::started, [currentStorage, currentActivator, processId] {
currentStorage->m_log.append({processId, Handler::Activator});
currentActivator->activate();
});
};
};
const Group root {
parallel,
Storage(storage),
Process(setupProcessWithCondition(1)),
Group {
OnGroupSetup(groupSetup(2)),
WaitFor(condition),
Process(setupProcess(2)),
Process(setupProcess(3))
}
};
const Log log {
{1, Handler::Setup},
{2, Handler::GroupSetup},
{1, Handler::Activator},
{2, Handler::Setup},
{3, Handler::Setup}
};
QTest::newRow("WaitFor")
<< TestData{storage, root, log, 3, OnStart::Running, OnDone::Success};
}
}
void tst_TaskTree::processTree()