aboutsummaryrefslogtreecommitdiffstats
path: root/src/libs/utils/tasktree.cpp
diff options
context:
space:
mode:
authorJarek Kobus <jaroslaw.kobus@qt.io>2022-10-12 14:30:24 +0200
committerJarek Kobus <jaroslaw.kobus@qt.io>2022-11-09 17:06:31 +0000
commitc49de14c9dd4c92b25642d379d525a057fa0d409 (patch)
tree73733c055161d502f3cc6ea47f4a972cc4e4c086 /src/libs/utils/tasktree.cpp
parentb8d68b6f659315b03caa00a1b2b369c27a5cf18f (diff)
Utils: Introduce TaskTree and Tasking namespace
The TaskTree class is responsible for running async task tree structure defined in a declarative way. Change-Id: Ieaf706c7d2efdc8b431a17b2db8b28bf4b7c38e5 Reviewed-by: hjk <hjk@qt.io>
Diffstat (limited to 'src/libs/utils/tasktree.cpp')
-rw-r--r--src/libs/utils/tasktree.cpp382
1 files changed, 382 insertions, 0 deletions
diff --git a/src/libs/utils/tasktree.cpp b/src/libs/utils/tasktree.cpp
new file mode 100644
index 00000000000..a7051cf78e4
--- /dev/null
+++ b/src/libs/utils/tasktree.cpp
@@ -0,0 +1,382 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#include "tasktree.h"
+
+#include "guard.h"
+#include "qtcassert.h"
+
+namespace Utils {
+namespace Tasking {
+
+ExecuteInSequence sequential;
+ExecuteInParallel parallel;
+WorkflowPolicy stopOnError(TaskItem::WorkflowPolicy::StopOnError);
+WorkflowPolicy continueOnError(TaskItem::WorkflowPolicy::ContinueOnError);
+WorkflowPolicy stopOnDone(TaskItem::WorkflowPolicy::StopOnDone);
+WorkflowPolicy continueOnDone(TaskItem::WorkflowPolicy::ContinueOnDone);
+WorkflowPolicy optional(TaskItem::WorkflowPolicy::Optional);
+
+void TaskItem::addChildren(const QList<TaskItem> &children)
+{
+ QTC_ASSERT(m_type == Type::Group, qWarning("Only Task may have children, skipping..."); return);
+ for (const TaskItem &child : children) {
+ switch (child.m_type) {
+ case Type::Group:
+ m_children.append(child);
+ break;
+ case Type::Mode:
+ QTC_ASSERT(m_type == Type::Group,
+ qWarning("Mode may only be a child of Group, skipping..."); return);
+ m_executeMode = child.m_executeMode; // 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);
+ m_workflowPolicy = child.m_workflowPolicy; // TODO: Assert on redefinition?
+ break;
+ case Type::TaskHandler:
+ QTC_ASSERT(child.m_taskHandler.m_createHandler,
+ qWarning("Task Create Handler can't be null, skipping..."); return);
+ QTC_ASSERT(child.m_taskHandler.m_setupHandler,
+ qWarning("Task Setup Handler can't be null, skipping..."); return);
+ m_children.append(child);
+ break;
+ case Type::GroupHandler:
+ QTC_ASSERT(m_type == Type::Group, qWarning("Group Handler may only be a "
+ "child of Group, skipping..."); break);
+ QTC_ASSERT(!child.m_groupHandler.m_simpleSetupHandler
+ || !m_groupHandler.m_simpleSetupHandler,
+ qWarning("Group Setup Handler redefinition, overriding..."));
+ QTC_ASSERT(!child.m_groupHandler.m_simpleDoneHandler
+ || !m_groupHandler.m_simpleDoneHandler,
+ qWarning("Group Done Handler redefinition, overriding..."));
+ QTC_ASSERT(!child.m_groupHandler.m_simpleErrorHandler
+ || !m_groupHandler.m_simpleErrorHandler,
+ qWarning("Group Error Handler redefinition, overriding..."));
+ if (child.m_groupHandler.m_simpleSetupHandler)
+ m_groupHandler.m_simpleSetupHandler = child.m_groupHandler.m_simpleSetupHandler;
+ if (child.m_groupHandler.m_simpleDoneHandler)
+ m_groupHandler.m_simpleDoneHandler = child.m_groupHandler.m_simpleDoneHandler;
+ if (child.m_groupHandler.m_simpleErrorHandler)
+ m_groupHandler.m_simpleErrorHandler = child.m_groupHandler.m_simpleErrorHandler;
+ break;
+ }
+ }
+}
+
+} // namespace Tasking
+
+using namespace Tasking;
+
+class TaskTreePrivate;
+class TaskNode;
+
+class TaskContainer
+{
+public:
+ TaskContainer(TaskTreePrivate *taskTreePrivate, TaskContainer *parentContainer,
+ const TaskItem &task);
+ ~TaskContainer();
+ void start();
+ void stop();
+ bool isRunning() const;
+ void childDone(bool success);
+ void invokeSubTreeHandler(bool success);
+ void resetSuccessBit();
+ void updateSuccessBit(bool success);
+
+ TaskTreePrivate *m_taskTreePrivate = nullptr;
+ TaskContainer *m_parentContainer = nullptr;
+ const TaskItem::ExecuteMode m_executeMode = TaskItem::ExecuteMode::Parallel;
+ TaskItem::WorkflowPolicy m_workflowPolicy = TaskItem::WorkflowPolicy::StopOnError;
+ const TaskItem::GroupHandler m_groupHandler;
+ QList<TaskNode *> m_children;
+ int m_currentIndex = -1;
+ bool m_successBit = true;
+};
+
+class TaskNode : public QObject
+{
+public:
+ TaskNode(TaskTreePrivate *taskTreePrivate, TaskContainer *parentContainer,
+ const TaskItem &task)
+ : m_taskHandler(task.taskHandler())
+ , m_container(taskTreePrivate, parentContainer, task)
+ {
+ }
+
+ bool start();
+ void stop();
+ bool isRunning();
+
+private:
+ const TaskItem::TaskHandler m_taskHandler;
+ TaskContainer m_container;
+ std::unique_ptr<TaskInterface> m_task;
+};
+
+class TaskTreePrivate
+{
+public:
+ TaskTreePrivate(TaskTree *taskTree, const Group &root)
+ : q(taskTree)
+ , m_root(this, nullptr, root) {}
+
+ void emitDone() {
+ GuardLocker locker(m_guard);
+ emit q->done();
+ }
+ void emitError() {
+ GuardLocker locker(m_guard);
+ emit q->errorOccurred();
+ }
+
+ TaskTree *q = nullptr;
+ TaskNode m_root;
+ Guard m_guard;
+};
+
+TaskContainer::TaskContainer(TaskTreePrivate *taskTreePrivate, TaskContainer *parentContainer,
+ const TaskItem &task)
+ : m_taskTreePrivate(taskTreePrivate)
+ , m_parentContainer(parentContainer)
+ , m_executeMode(task.executeMode())
+ , m_workflowPolicy(task.workflowPolicy())
+ , m_groupHandler(task.groupHandler())
+{
+ const QList<TaskItem> &children = task.children();
+ for (const TaskItem &child : children)
+ m_children.append(new TaskNode(m_taskTreePrivate, this, child));
+}
+
+TaskContainer::~TaskContainer()
+{
+ qDeleteAll(m_children);
+}
+
+void TaskContainer::start()
+{
+ if (m_groupHandler.m_simpleSetupHandler) {
+ GuardLocker locker(m_taskTreePrivate->m_guard);
+ m_groupHandler.m_simpleSetupHandler();
+ }
+
+ if (m_children.isEmpty()) {
+ invokeSubTreeHandler(true);
+ return;
+ }
+
+ m_currentIndex = 0;
+ resetSuccessBit();
+
+ if (m_executeMode == TaskItem::ExecuteMode::Sequential) {
+ m_children.at(m_currentIndex)->start();
+ return;
+ }
+
+ // Parallel case
+ for (TaskNode *child : std::as_const(m_children)) {
+ if (!child->start())
+ return;
+ }
+}
+
+void TaskContainer::stop()
+{
+ m_currentIndex = -1;
+ for (TaskNode *child : std::as_const(m_children))
+ child->stop();
+}
+
+bool TaskContainer::isRunning() const
+{
+ return m_currentIndex >= 0;
+}
+
+void TaskContainer::childDone(bool success)
+{
+ if ((m_workflowPolicy == TaskItem::WorkflowPolicy::StopOnDone && success)
+ || (m_workflowPolicy == TaskItem::WorkflowPolicy::StopOnError && !success)) {
+ stop();
+ invokeSubTreeHandler(success);
+ return;
+ }
+
+ ++m_currentIndex;
+ updateSuccessBit(success);
+
+ if (m_currentIndex == m_children.size()) {
+ invokeSubTreeHandler(m_successBit);
+ return;
+ }
+
+ if (m_executeMode == TaskItem::ExecuteMode::Sequential)
+ m_children.at(m_currentIndex)->start();
+}
+
+void TaskContainer::invokeSubTreeHandler(bool success)
+{
+ m_currentIndex = -1;
+ m_successBit = success;
+ if (success && m_groupHandler.m_simpleDoneHandler) {
+ GuardLocker locker(m_taskTreePrivate->m_guard);
+ m_groupHandler.m_simpleDoneHandler();
+ } else if (!success && m_groupHandler.m_simpleErrorHandler) {
+ GuardLocker locker(m_taskTreePrivate->m_guard);
+ m_groupHandler.m_simpleErrorHandler();
+ }
+ if (m_parentContainer) {
+ m_parentContainer->childDone(success);
+ return;
+ }
+ if (success)
+ m_taskTreePrivate->emitDone();
+ else
+ m_taskTreePrivate->emitError();
+}
+
+void TaskContainer::resetSuccessBit()
+{
+ if (m_children.isEmpty())
+ m_successBit = true;
+
+ if (m_workflowPolicy == TaskItem::WorkflowPolicy::StopOnDone
+ || m_workflowPolicy == TaskItem::WorkflowPolicy::ContinueOnDone) {
+ m_successBit = false;
+ } else {
+ m_successBit = true;
+ }
+}
+
+void TaskContainer::updateSuccessBit(bool success)
+{
+ if (m_workflowPolicy == TaskItem::WorkflowPolicy::Optional)
+ return;
+ if (m_workflowPolicy == TaskItem::WorkflowPolicy::StopOnDone
+ || m_workflowPolicy == TaskItem::WorkflowPolicy::ContinueOnDone) {
+ m_successBit = m_successBit || success;
+ } else {
+ m_successBit = m_successBit && success;
+ }
+}
+
+
+bool TaskNode::start()
+{
+ if (!m_taskHandler.m_createHandler || !m_taskHandler.m_setupHandler) {
+ m_container.start();
+ return true;
+ }
+ m_task.reset(m_taskHandler.m_createHandler());
+ {
+ GuardLocker locker(m_container.m_taskTreePrivate->m_guard);
+ m_taskHandler.m_setupHandler(*m_task.get());
+ }
+ connect(m_task.get(), &TaskInterface::done, this, [this](bool success) {
+ if (success && m_taskHandler.m_doneHandler) {
+ GuardLocker locker(m_container.m_taskTreePrivate->m_guard);
+ m_taskHandler.m_doneHandler(*m_task.get());
+ } else if (!success && m_taskHandler.m_errorHandler) {
+ GuardLocker locker(m_container.m_taskTreePrivate->m_guard);
+ m_taskHandler.m_errorHandler(*m_task.get());
+ }
+
+ m_task.release()->deleteLater();
+
+ QTC_CHECK(m_container.m_parentContainer);
+ m_container.m_parentContainer->childDone(success);
+ });
+
+ m_task->start();
+ return m_task.get(); // In case of failed to start, done handler already released process
+}
+
+void TaskNode::stop()
+{
+ m_task.reset();
+ m_container.stop();
+}
+
+bool TaskNode::isRunning()
+{
+ return m_task || m_container.isRunning();
+}
+
+/*!
+ \class Utils::TaskTree
+
+ \brief The TaskTree class is responsible for running async task tree structure defined in a
+ declarative way.
+
+ The Tasking namespace (similar to Layouting) is designer for building declarative task
+ tree structure. The examples of tasks that can be used inside TaskTree are e.g. QtcProcess,
+ FileTransfer, AsyncTask<>. It's extensible, so any possible asynchronous task may be
+ integrated and used inside TaskTree. TaskTree enables to form sophisticated mixtures of
+ parallel or sequential flow of tasks in tree form.
+
+ The TaskTree consist of Group root element. The Group can have nested Group elements.
+ The Group may also contain any number of tasks, e.g. Process, FileTransfer,
+ AsyncTask<ReturnType>.
+
+ Each Group can contain various other elements describing the processing flow.
+
+ The execute mode elements of a Group specify how direct children of a Group will be executed.
+ The "sequential" element of a Group means all tasks in a group will be executed in chain,
+ so after the previous task finished, the next will be started. This is the default Group
+ behavior. The "parallel" element of a Group means that all tasks in a Group will be started
+ simultaneously. When having nested Groups hierarchy, we may mix execute modes freely
+ and each Group will be executed according to its own execute mode.
+ The "sequential" mode may be very useful in cases when result data from one task is need to
+ be passed as an input data to the other task - sequential mode guarantees that the next
+ task will be started only after the previous task has already finished.
+
+ There are many possible "workflow" behaviors for the Group. E.g. "stopOnError",
+ the default Group workflow behavior, means that whenever any direct child of a Group
+ finished with error, we immediately stop processing other tasks in this group
+ (in parallel case) by canceling them and immediately finish the Group with error.
+
+ The user of TaskTree specifies how to setup his tasks (by providing TaskSetupHandlers)
+ and how to collect output data from the finished tasks (by providing TaskEndHandlers).
+ The user don't need to create tasks manually - TaskTree will create them when it's needed
+ and destroy when they are not used anymore.
+
+ Whenever a Group elemenent is being started, the Group's OnGroupSetup handler is being called.
+ Just after the handler finishes, all Group's children are executed (either in parallel or
+ in sequence). When all Group's children finished, one of Group's OnGroupDone or OnGroupError
+ is being executed, depending on results of children execution and Group's workflow policy.
+*/
+
+TaskTree::TaskTree(const Group &root)
+ : d(new TaskTreePrivate(this, root))
+{
+}
+
+TaskTree::~TaskTree()
+{
+ QTC_ASSERT(!d->m_guard.isLocked(), qWarning("Deleting TaskTree instance directly from "
+ "one of its handlers will lead to crash!"));
+ delete d;
+}
+
+void TaskTree::start()
+{
+ QTC_ASSERT(!isRunning(), qWarning("The TaskTree is already running, ignoring..."); return);
+ QTC_ASSERT(!d->m_guard.isLocked(), qWarning("The start() is called from one of the"
+ "TaskTree handlers, ingoring..."); return);
+ d->m_root.start();
+}
+
+void TaskTree::stop()
+{
+ QTC_ASSERT(!d->m_guard.isLocked(), qWarning("The stop() is called from one of the"
+ "TaskTree handlers, ingoring..."); return);
+ d->m_root.stop();
+}
+
+bool TaskTree::isRunning() const
+{
+ return d->m_root.isRunning();
+}
+
+} // namespace Utils