From 7501d7587fc931a1454c0fea8412c2cee9a1c065 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Wed, 17 May 2023 19:28:57 +0200 Subject: [PATCH] TaskTree: Introduce WorkflowPolicy::StopOnFinished The policy is useful mainly in parallel mode. It stops executing the Group when any task finishes. It reports the task's result. Change-Id: I7aa98365cdc4c1eb869ab419d42d0cc5438d43bf Reviewed-by: Reviewed-by: hjk --- src/libs/solutions/tasking/tasktree.cpp | 76 ++++++++++++-------- src/libs/solutions/tasking/tasktree.h | 24 ++++--- tests/auto/solutions/tasking/tst_tasking.cpp | 62 ++++++++++++++-- 3 files changed, 115 insertions(+), 47 deletions(-) diff --git a/src/libs/solutions/tasking/tasktree.cpp b/src/libs/solutions/tasking/tasktree.cpp index 9544e356ac0..32e7f3eadd5 100644 --- a/src/libs/solutions/tasking/tasktree.cpp +++ b/src/libs/solutions/tasking/tasktree.cpp @@ -114,6 +114,7 @@ Workflow stopOnError(WorkflowPolicy::StopOnError); Workflow continueOnError(WorkflowPolicy::ContinueOnError); Workflow stopOnDone(WorkflowPolicy::StopOnDone); Workflow continueOnDone(WorkflowPolicy::ContinueOnDone); +Workflow stopOnFinished(WorkflowPolicy::StopOnFinished); Workflow optional(WorkflowPolicy::Optional); void TaskItem::addChildren(const QList &children) @@ -511,6 +512,10 @@ bool TaskContainer::RuntimeData::updateSuccessBit(bool success) { if (m_constData.m_workflowPolicy == WorkflowPolicy::Optional) return m_successBit; + if (m_constData.m_workflowPolicy == WorkflowPolicy::StopOnFinished) { + m_successBit = success; + return m_successBit; + } const bool donePolicy = m_constData.m_workflowPolicy == WorkflowPolicy::StopOnDone || m_constData.m_workflowPolicy == WorkflowPolicy::ContinueOnDone; @@ -595,8 +600,9 @@ 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); + const bool shouldStop = m_constData.m_workflowPolicy == WorkflowPolicy::StopOnFinished + || (m_constData.m_workflowPolicy == WorkflowPolicy::StopOnDone && success) + || (m_constData.m_workflowPolicy == WorkflowPolicy::StopOnError && !success); if (shouldStop) stop(); @@ -1146,15 +1152,15 @@ void TaskNode::invokeEndHandler(bool success) \row \li stopOnError \li Default. If a task finishes with an error, the group: - \list 1 - \li Stops the running tasks (if any - for example, in parallel - mode). - \li Skips executing tasks it has not started (for example, in the - sequential mode). - \li Immediately finishes with an error. - \endlist - If all child tasks finish successfully or the group is empty, the group - finishes with success. + \list 1 + \li Stops the running tasks (if any - for example, in parallel + mode). + \li Skips executing tasks it has not started (for example, in the + sequential mode). + \li Immediately finishes with an error. + \endlist + If all child tasks finish successfully or the group is empty, the group + finishes with success. \row \li continueOnError \li Similar to stopOnError, but in case any child finishes with @@ -1162,22 +1168,22 @@ void TaskNode::invokeEndHandler(bool success) and the group reports an error afterwards, even when some other tasks in group finished with success. If a task finishes with an error, the group: - \list 1 - \li Continues executing the tasks that are running or have not - started yet. - \li Finishes with an error when all tasks finish. - \endlist - If all tasks finish successfully or the group is empty, the group - finishes with success. + \list 1 + \li Continues executing the tasks that are running or have not + started yet. + \li Finishes with an error when all tasks finish. + \endlist + If all tasks finish successfully or the group is empty, the group + finishes with success. \row \li stopOnDone \li If a task finishes with success, the group: - \list 1 - \li Stops running tasks and skips those that it has not started. - \li Immediately finishes with success. - \endlist - If all tasks finish with an error or the group is empty, the group - finishes with an error. + \list 1 + \li Stops running tasks and skips those that it has not started. + \li Immediately finishes with success. + \endlist + If all tasks finish with an error or the group is empty, the group + finishes with an error. \row \li continueOnDone \li Similar to stopOnDone, but in case any child finishes @@ -1185,13 +1191,21 @@ void TaskNode::invokeEndHandler(bool success) and the group reports success afterwards, even when some other tasks in group finished with an error. If a task finishes with success, the group: - \list 1 - \li Continues executing the tasks that are running or have not - started yet. - \li Finishes with success when all tasks finish. - \endlist - If all tasks finish with an error or the group is empty, the group - finishes with an error. + \list 1 + \li Continues executing the tasks that are running or have not + started yet. + \li Finishes with success when all tasks finish. + \endlist + If all tasks finish with an error or the group is empty, the group + finishes with an error. + \row + \li stopOnFinished + \li The group starts as many tasks as it can. When a task finishes + the group stops and reports the task's result. + When the group is empty, it finishes immediately with success. + Useful only in parallel mode. + In sequential mode, only the first task is started, and when finished, + the group finishes too, so the other tasks are ignored. \row \li optional \li The group executes all tasks and ignores their return state. If all diff --git a/src/libs/solutions/tasking/tasktree.h b/src/libs/solutions/tasking/tasktree.h index 462eb5bd2eb..407db58b0ae 100644 --- a/src/libs/solutions/tasking/tasktree.h +++ b/src/libs/solutions/tasking/tasktree.h @@ -93,19 +93,22 @@ private: // 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) -// b) On first error - continue executing all children and report error afterwards +// a) Report error on first error and stop executing other children (including their subtree). +// b) On first error - continue executing all children and report error afterwards. // 2. When all children finished with error -> report error, otherwise: -// a) Report done on first done and stop executing other children (including their subtree) -// b) On first done - continue executing all children and report done afterwards -// 3. Always run all children, ignore their result and report done afterwards +// a) Report done on first done and stop executing other children (including their subtree). +// b) On first done - continue executing all children and report done afterwards. +// 3. Stops on first finished child. In sequential mode it will never run other children then the first one. +// Useful only in parallel mode. +// 4. Always run all children, ignore their result and report done afterwards. enum class WorkflowPolicy { - StopOnError, // 1a - Reports error on first child error, otherwise done (if all children were done) - ContinueOnError, // 1b - The same, but children execution continues. When no children it reports done. - StopOnDone, // 2a - Reports done on first child done, otherwise error (if all children were error) - ContinueOnDone, // 2b - The same, but children execution continues. When no children it reports done. (?) - Optional // 3 - Always reports done after all children finished + StopOnError, // 1a - Reports error on first child error, otherwise done (if all children were done). + ContinueOnError, // 1b - The same, but children execution continues. Reports done when no children. + StopOnDone, // 2a - Reports done on first child done, otherwise error (if all children were error). + ContinueOnDone, // 2b - The same, but children execution continues. Reports error when no children. + StopOnFinished, // 3 - Stops on first finished child and report its result. + Optional // 4 - Reports done after all children finished. }; enum class TaskAction @@ -287,6 +290,7 @@ TASKING_EXPORT extern Workflow stopOnError; TASKING_EXPORT extern Workflow continueOnError; TASKING_EXPORT extern Workflow stopOnDone; TASKING_EXPORT extern Workflow continueOnDone; +TASKING_EXPORT extern Workflow stopOnFinished; TASKING_EXPORT extern Workflow optional; template diff --git a/tests/auto/solutions/tasking/tst_tasking.cpp b/tests/auto/solutions/tasking/tst_tasking.cpp index 06bebf99f51..1efcf7096a3 100644 --- a/tests/auto/solutions/tasking/tst_tasking.cpp +++ b/tests/auto/solutions/tasking/tst_tasking.cpp @@ -233,8 +233,9 @@ void tst_Tasking::testTree_data() const auto setupFailingTask = [setupTaskHelper](int taskId) { return [=](TestTask &task) { setupTaskHelper(task, taskId, false); }; }; - const auto setupSleepingTask = [setupTaskHelper](int taskId, std::chrono::milliseconds sleep) { - return [=](TestTask &task) { setupTaskHelper(task, taskId, true, sleep); }; + const auto setupSleepingTask = [setupTaskHelper](int taskId, bool success, + std::chrono::milliseconds sleep) { + return [=](TestTask &task) { setupTaskHelper(task, taskId, success, sleep); }; }; const auto setupDynamicTask = [setupTaskHelper](int taskId, TaskAction action) { return [=](TestTask &task) { @@ -680,6 +681,55 @@ void tst_Tasking::testTree_data() QTest::newRow("ContinueOnDone") << TestData{storage, root, log, 3, OnDone::Success}; } + { + const Group root = constructSimpleSequence(stopOnFinished); + const Log log { + {1, Handler::Setup}, + {1, Handler::Done}, + {0, Handler::GroupDone} + }; + QTest::newRow("StopOnFinished") << TestData{storage, root, log, 3, OnDone::Success}; + } + + { + const auto setupRoot = [=](bool firstSuccess, bool secondSuccess) { + return Group { + parallel, + stopOnFinished, + Storage(storage), + Test(setupSleepingTask(1, firstSuccess, 1000ms), logDone, logError), + Test(setupSleepingTask(2, secondSuccess, 5ms), logDone, logError), + OnGroupDone(groupDone(0)), + OnGroupError(groupError(0)) + }; + }; + + const Group root1 = setupRoot(true, true); + const Group root2 = setupRoot(true, false); + const Group root3 = setupRoot(false, true); + const Group root4 = setupRoot(false, false); + + const Log success { + {1, Handler::Setup}, + {2, Handler::Setup}, + {2, Handler::Done}, + {1, Handler::Error}, + {0, Handler::GroupDone} + }; + const Log failure { + {1, Handler::Setup}, + {2, Handler::Setup}, + {2, Handler::Error}, + {1, Handler::Error}, + {0, Handler::GroupError} + }; + + QTest::newRow("StopOnFinished1") << TestData{storage, root1, success, 2, OnDone::Success}; + QTest::newRow("StopOnFinished2") << TestData{storage, root2, failure, 2, OnDone::Failure}; + QTest::newRow("StopOnFinished3") << TestData{storage, root3, success, 2, OnDone::Success}; + QTest::newRow("StopOnFinished4") << TestData{storage, root4, failure, 2, OnDone::Failure}; + } + { const Group root { Storage(storage), @@ -837,14 +887,14 @@ void tst_Tasking::testTree_data() // Inside this test the task 2 should finish first, then synchonously: // - task 3 should exit setup with error - // - task 1 should be stopped as a consequence of error inside the group + // - task 1 should be stopped as a consequence of the error inside the group // - tasks 4 and 5 should be skipped const Group root2 { ParallelLimit(2), Storage(storage), Group { OnGroupSetup(groupSetup(1)), - Test(setupSleepingTask(1, 10ms)) + Test(setupSleepingTask(1, true, 10ms)) }, Group { OnGroupSetup(groupSetup(2)), @@ -879,11 +929,11 @@ void tst_Tasking::testTree_data() ParallelLimit(2), Group { OnGroupSetup(groupSetup(1)), - Test(setupSleepingTask(1, 20ms)) + Test(setupSleepingTask(1, true, 20ms)) }, Group { OnGroupSetup(groupSetup(2)), - Test(setupTask(2), [](const TestTask &) { QThread::msleep(10); }) + Test(setupSleepingTask(2, true, 10ms)) }, Group { OnGroupSetup(groupSetup(3)),