1// Copyright (C) 2024 Jarek Kobus
2// Copyright (C) 2024 The Qt Company Ltd.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4
5#include "tasktree.h"
6
7#include "barrier.h"
8
9#include <QtCore/QDebug>
10#include <QtCore/QEventLoop>
11#include <QtCore/QFutureWatcher>
12#include <QtCore/QHash>
13#include <QtCore/QMetaEnum>
14#include <QtCore/QMutex>
15#include <QtCore/QPointer>
16#include <QtCore/QPromise>
17#include <QtCore/QSet>
18#include <QtCore/QTime>
19#include <QtCore/QTimer>
20
21using namespace Qt::StringLiterals;
22using namespace std::chrono;
23
24QT_BEGIN_NAMESPACE
25
26namespace Tasking {
27
28// That's cut down qtcassert.{c,h} to avoid the dependency.
29#define QT_STRING(cond) qDebug("SOFT ASSERT: \"%s\" in %s: %s", cond, __FILE__, QT_STRINGIFY(__LINE__))
30#define QT_ASSERT(cond, action) if (Q_LIKELY(cond)) {} else { QT_STRING(#cond); action; } do {} while (0)
31#define QT_CHECK(cond) if (cond) {} else { QT_STRING(#cond); } do {} while (0)
32
33class Guard
34{
35 Q_DISABLE_COPY(Guard)
36public:
37 Guard() = default;
38 ~Guard() { QT_CHECK(m_lockCount == 0); }
39 bool isLocked() const { return m_lockCount; }
40private:
41 int m_lockCount = 0;
42 friend class GuardLocker;
43};
44
45class GuardLocker
46{
47 Q_DISABLE_COPY(GuardLocker)
48public:
49 GuardLocker(Guard &guard) : m_guard(guard) { ++m_guard.m_lockCount; }
50 ~GuardLocker() { --m_guard.m_lockCount; }
51private:
52 Guard &m_guard;
53};
54
55/*!
56 \module TaskingSolution
57 \title Tasking Solution
58 \ingroup solutions-modules
59 \brief Contains a general purpose Tasking solution.
60
61 The Tasking solution depends on Qt only, and doesn't depend on any \QC specific code.
62*/
63
64/*!
65 \namespace Tasking
66 \inmodule TaskingSolution
67 \brief The Tasking namespace encloses all classes and global functions of the Tasking solution.
68*/
69
70/*!
71 \class Tasking::TaskInterface
72 \inheaderfile solutions/tasking/tasktree.h
73 \inmodule TaskingSolution
74 \brief TaskInterface is the abstract base class for implementing custom task adapters.
75 \reentrant
76
77 To implement a custom task adapter, derive your adapter from the
78 \c TaskAdapter<Task> class template. TaskAdapter automatically creates and destroys
79 the custom task instance and associates the adapter with a given \c Task type.
80*/
81
82/*!
83 \fn virtual void TaskInterface::start()
84
85 This method is called by the running TaskTree for starting the \c Task instance.
86 Reimplement this method in \c TaskAdapter<Task>'s subclass in order to start the
87 associated task.
88
89 Use TaskAdapter::task() to access the associated \c Task instance.
90
91 \sa done(), TaskAdapter::task()
92*/
93
94/*!
95 \fn void TaskInterface::done(DoneResult result)
96
97 Emit this signal from the \c TaskAdapter<Task>'s subclass, when the \c Task is finished.
98 Pass DoneResult::Success as a \a result argument when the task finishes with success;
99 otherwise, when an error occurs, pass DoneResult::Error.
100*/
101
102/*!
103 \class Tasking::TaskAdapter
104 \inheaderfile solutions/tasking/tasktree.h
105 \inmodule TaskingSolution
106 \brief A class template for implementing custom task adapters.
107 \reentrant
108
109 The TaskAdapter class template is responsible for creating a task of the \c Task type,
110 starting it, and reporting success or an error when the task is finished.
111 It also associates the adapter with a given \c Task type.
112
113 Reimplement this class with the actual \c Task type to adapt the task's interface
114 into the general TaskTree's interface for managing the \c Task instances.
115
116 Each subclass needs to provide a public default constructor,
117 implement the start() method, and emit the done() signal when the task is finished.
118 Use task() to access the associated \c Task instance.
119
120 To use your task adapter inside the task tree, create an alias to the
121 Tasking::CustomTask template passing your task adapter as a template parameter:
122 \code
123 // Defines actual worker
124 class Worker {...};
125
126 // Adapts Worker's interface to work with task tree
127 class WorkerTaskAdapter : public TaskAdapter<Worker> {...};
128
129 // Defines WorkerTask as a new custom task type to be placed inside Group items
130 using WorkerTask = CustomTask<WorkerTaskAdapter>;
131 \endcode
132
133 Optionally, you may pass a custom \c Deleter for the associated \c Task
134 as a second template parameter of your \c TaskAdapter subclass.
135 When the \c Deleter parameter is omitted, the \c std::default_delete<Task> is used by default.
136 The custom \c Deleter is useful when the destructor of the running \c Task
137 may potentially block the caller thread. Instead of blocking, the custom deleter may move
138 the running task into a separate thread and implement the blocking destruction there.
139 In this way, the fast destruction (seen from the caller thread) of the running task
140 with a blocking destructor may be achieved.
141
142 For more information on implementing the custom task adapters, refer to \l {Task Adapters}.
143
144 \sa start(), done(), task()
145*/
146
147/*!
148 \fn template <typename Task, typename Deleter = std::default_delete<Task>> TaskAdapter<Task, Deleter>::TaskAdapter<Task, Deleter>()
149
150 Creates a task adapter for the given \c Task type.
151
152 Internally, it creates an instance of \c Task, which is accessible via the task() method.
153 The optionally provided \c Deleter is used instead of the \c Task destructor.
154 When \c Deleter is omitted, the \c std::default_delete<Task> is used by default.
155
156 \sa task()
157*/
158
159/*!
160 \fn template <typename Task, typename Deleter = std::default_delete<Task>> Task *TaskAdapter<Task, Deleter>::task()
161
162 Returns the pointer to the associated \c Task instance.
163*/
164
165/*!
166 \fn template <typename Task, typename Deleter = std::default_delete<Task>> Task *TaskAdapter<Task, Deleter>::task() const
167 \overload
168
169 Returns the \c const pointer to the associated \c Task instance.
170*/
171
172/*!
173 \class Tasking::Storage
174 \inheaderfile solutions/tasking/tasktree.h
175 \inmodule TaskingSolution
176 \brief A class template for custom data exchange in the running task tree.
177 \reentrant
178
179 The Storage class template is responsible for dynamically creating and destructing objects
180 of the custom \c StorageStruct type. The creation and destruction are managed by the
181 running task tree. If a Storage object is placed inside a \l {Tasking::Group} {Group} element,
182 the running task tree creates the \c StorageStruct object when the group is started and before
183 the group's setup handler is called. Later, whenever any handler inside this group is called,
184 the task tree activates the previously created instance of the \c StorageStruct object.
185 This includes all tasks' and groups' setup and done handlers inside the group where the
186 Storage object was placed, also within the nested groups.
187 When a copy of the Storage object is passed to the handler via the lambda capture,
188 the handler may access the instance activated by the running task tree via the
189 \l {Tasking::Storage::operator->()} {operator->()},
190 \l {Tasking::Storage::operator*()} {operator*()}, or activeStorage() method.
191 If two handlers capture the same Storage object, one of them may store a custom data there,
192 and the other may read it afterwards.
193 When the group is finished, the previously created instance of the \c StorageStruct
194 object is destroyed after the group's done handler is called.
195
196 An example of data exchange between tasks:
197
198 \code
199 const Storage<QString> storage;
200
201 const auto onFirstDone = [storage](const Task &task) {
202 // Assings QString, taken from the first task result, to the active QString instance
203 // of the Storage object.
204 *storage = task.getResultAsString();
205 };
206
207 const auto onSecondSetup = [storage](Task &task) {
208 // Reads QString from the active QString instance of the Storage object and use it to
209 // configure the second task before start.
210 task.configureWithString(*storage);
211 };
212
213 const Group root {
214 // The running task tree creates QString instance when root in entered
215 storage,
216 // The done handler of the first task stores the QString in the storage
217 TaskItem(..., onFirstDone),
218 // The setup handler of the second task reads the QString from the storage
219 TaskItem(onSecondSetup, ...)
220 };
221 \endcode
222
223 Since the root group executes its tasks sequentially, the \c onFirstDone handler
224 is always called before the \c onSecondSetup handler. This means that the QString data,
225 read from the \c storage inside the \c onSecondSetup handler's body,
226 has already been set by the \c onFirstDone handler.
227 You can always rely on it in \l {Tasking::sequential} {sequential} execution mode.
228
229 The Storage internals are shared between all of its copies. That is why the copies of the
230 Storage object inside the handlers' lambda captures still refer to the same Storage instance.
231 You may place multiple Storage objects inside one \l {Tasking::Group} {Group} element,
232 provided that they do not include copies of the same Storage object.
233 Otherwise, an assert is triggered at runtime that includes an error message.
234 However, you can place copies of the same Storage object in different
235 \l {Tasking::Group} {Group} elements of the same recipe. In this case, the running task
236 tree will create multiple instances of the \c StorageStruct objects (one for each copy)
237 and storage shadowing will take place. Storage shadowing works in a similar way
238 to C++ variable shadowing inside the nested blocks of code:
239
240 \code
241 Storage<QString> storage;
242
243 const Group root {
244 storage, // Top copy, 1st instance of StorageStruct
245 onGroupSetup([storage] { ... }), // Top copy is active
246 Group {
247 storage, // Nested copy, 2nd instance of StorageStruct,
248 // shadows Top copy
249 onGroupSetup([storage] { ... }), // Nested copy is active
250 },
251 Group {
252 onGroupSetup([storage] { ... }), // Top copy is active
253 }
254 };
255 \endcode
256
257 The Storage objects may also be used for passing the initial data to the executed task tree,
258 and for reading the final data out of the task tree before it finishes.
259 To do this, use \l {TaskTree::onStorageSetup()} {onStorageSetup()} or
260 \l {TaskTree::onStorageDone()} {onStorageDone()}, respectively.
261
262 \note If you use an unreachable Storage object inside the handler,
263 because you forgot to place the storage in the recipe,
264 or placed it, but not in any handler's ancestor group,
265 you may expect a crash, preceded by the following message:
266 \e {The referenced storage is not reachable in the running tree.
267 A nullptr will be returned which might lead to a crash in the calling code.
268 It is possible that no storage was added to the tree,
269 or the storage is not reachable from where it is referenced.}
270*/
271
272/*!
273 \fn template <typename StorageStruct> Storage<StorageStruct>::Storage<StorageStruct>()
274
275 Creates a storage for the given \c StorageStruct type.
276
277 \note All copies of \c this object are considered to be the same Storage instance.
278*/
279
280/*!
281 \fn template <typename StorageStruct> StorageStruct &Storage<StorageStruct>::operator*() const noexcept
282
283 Returns a \e reference to the active \c StorageStruct object, created by the running task tree.
284 Use this function only from inside the handler body of any GroupItem element placed
285 in the recipe, otherwise you may expect a crash.
286 Make sure that Storage is placed in any group ancestor of the handler's group item.
287
288 \note The returned reference is valid as long as the group that created this instance
289 is still running.
290
291 \sa activeStorage(), operator->()
292*/
293
294/*!
295 \fn template <typename StorageStruct> StorageStruct *Storage<StorageStruct>::operator->() const noexcept
296
297 Returns a \e pointer to the active \c StorageStruct object, created by the running task tree.
298 Use this function only from inside the handler body of any GroupItem element placed
299 in the recipe, otherwise you may expect a crash.
300 Make sure that Storage is placed in any group ancestor of the handler's group item.
301
302 \note The returned pointer is valid as long as the group that created this instance
303 is still running.
304
305 \sa activeStorage(), operator*()
306*/
307
308/*!
309 \fn template <typename StorageStruct> StorageStruct *Storage<StorageStruct>::activeStorage() const
310
311 Returns a \e pointer to the active \c StorageStruct object, created by the running task tree.
312 Use this function only from inside the handler body of any GroupItem element placed
313 in the recipe, otherwise you may expect a crash.
314 Make sure that Storage is placed in any group ancestor of the handler's group item.
315
316 \note The returned pointer is valid as long as the group that created this instance
317 is still running.
318
319 \sa operator->(), operator*()
320*/
321
322/*!
323 \class Tasking::GroupItem
324 \inheaderfile solutions/tasking/tasktree.h
325 \inmodule TaskingSolution
326 \brief GroupItem represents the basic element that may be a part of any Group.
327 \reentrant
328
329 GroupItem is a basic element that may be a part of any \l {Tasking::Group} {Group}.
330 It encapsulates the functionality provided by any GroupItem's subclass.
331 It is a value type and it is safe to copy the GroupItem instance,
332 even when it is originally created via the subclass' constructor.
333
334 There are four main kinds of GroupItem:
335 \table
336 \header
337 \li GroupItem Kind
338 \li Brief Description
339 \row
340 \li \l CustomTask
341 \li Defines asynchronous task type and task's start, done, and error handlers.
342 Aliased with a unique task name, such as, \c ConcurrentCallTask<ResultType>
343 or \c NetworkQueryTask. Asynchronous tasks are the main reason for using a task tree.
344 \row
345 \li \l {Tasking::Group} {Group}
346 \li A container for other group items. Since the group is of the GroupItem type,
347 it's possible to nest it inside another group. The group is seen by its parent
348 as a single asynchronous task.
349 \row
350 \li GroupItem containing \l {Tasking::Storage} {Storage}
351 \li Enables the child tasks of a group to exchange data. When GroupItem containing
352 \l {Tasking::Storage} {Storage} is placed inside a group, the task tree instantiates
353 the storage's data object just before the group is entered,
354 and destroys it just after the group is left.
355 \row
356 \li Other group control items
357 \li The items returned by \l {Tasking::parallelLimit()} {parallelLimit()} or
358 \l {Tasking::workflowPolicy()} {workflowPolicy()} influence the group's behavior.
359 The items returned by \l {Tasking::onGroupSetup()} {onGroupSetup()} or
360 \l {Tasking::onGroupDone()} {onGroupDone()} define custom handlers called when
361 the group starts or ends execution.
362 \endtable
363*/
364
365/*!
366 \fn template <typename StorageStruct> GroupItem::GroupItem(const Storage<StorageStruct> &storage)
367
368 Constructs a \c GroupItem element holding the \a storage object.
369
370 When the \l {Tasking::Group} {Group} element containing \e this GroupItem is entered
371 by the running task tree, an instance of the \c StorageStruct is created dynamically.
372
373 When that group is about to be left after its execution, the previously instantiated
374 \c StorageStruct is deleted.
375
376 The dynamically created instance of \c StorageStruct is accessible from inside any
377 handler body of the parent \l {Tasking::Group} {Group} element,
378 including nested groups and its tasks, via the
379 \l {Tasking::Storage::operator->()} {Storage::operator->()},
380 \l {Tasking::Storage::operator*()} {Storage::operator*()}, or Storage::activeStorage() method.
381
382 \sa {Tasking::Storage} {Storage}
383*/
384
385/*!
386 \fn GroupItem::GroupItem(const QList<GroupItem> &items)
387
388 Constructs a \c GroupItem element with a given list of \a items.
389
390 When this \c GroupItem element is parsed by the TaskTree, it is simply replaced with
391 its \a items.
392
393 This constructor is useful when constructing a \l {Tasking::Group} {Group} element with
394 lists of \c GroupItem elements:
395
396 \code
397 static QList<GroupItems> getItems();
398
399 ...
400
401 const Group root {
402 parallel,
403 finishAllAndSuccess,
404 getItems(), // OK, getItems() list is wrapped into a single GroupItem element
405 onGroupSetup(...),
406 onGroupDone(...)
407 };
408 \endcode
409
410 If you want to create a subtree, use \l {Tasking::Group} {Group} instead.
411
412 \note Don't confuse this \c GroupItem with the \l {Tasking::Group} {Group} element, as
413 \l {Tasking::Group} {Group} keeps its children nested
414 after being parsed by the task tree, while this \c GroupItem does not.
415
416 \sa {Tasking::Group} {Group}
417*/
418
419/*!
420 \fn GroupItem::GroupItem(std::initializer_list<GroupItem> items)
421 \overload
422 \sa GroupItem(const QList<Tasking::GroupItem> &items)
423*/
424
425/*!
426 \class Tasking::Group
427 \inheaderfile solutions/tasking/tasktree.h
428 \inmodule TaskingSolution
429 \brief Group represents the basic element for composing declarative recipes describing
430 how to execute and handle a nested tree of asynchronous tasks.
431 \reentrant
432
433 Group is a container for other group items. It encloses child tasks into one unit,
434 which is seen by the group's parent as a single, asynchronous task.
435 Since Group is of the GroupItem type, it may also be a child of Group.
436
437 Insert child tasks into the group by using aliased custom task names, such as,
438 \c ConcurrentCallTask<ResultType> or \c NetworkQueryTask:
439
440 \code
441 const Group group {
442 NetworkQueryTask(...),
443 ConcurrentCallTask<int>(...)
444 };
445 \endcode
446
447 The group's behavior may be customized by inserting the items returned by
448 \l {Tasking::parallelLimit()} {parallelLimit()} or
449 \l {Tasking::workflowPolicy()} {workflowPolicy()} functions:
450
451 \code
452 const Group group {
453 parallel,
454 continueOnError,
455 NetworkQueryTask(...),
456 NetworkQueryTask(...)
457 };
458 \endcode
459
460 The group may contain nested groups:
461
462 \code
463 const Group group {
464 finishAllAndSuccess,
465 NetworkQueryTask(...),
466 Group {
467 NetworkQueryTask(...),
468 Group {
469 parallel,
470 NetworkQueryTask(...),
471 NetworkQueryTask(...),
472 }
473 ConcurrentCallTask<QString>(...)
474 }
475 };
476 \endcode
477
478 The group may dynamically instantiate a custom storage structure, which may be used for
479 inter-task data exchange:
480
481 \code
482 struct MyCustomStruct { QByteArray data; };
483
484 Storage<MyCustomStruct> storage;
485
486 const auto onFirstSetup = [](NetworkQuery &task) { ... };
487 const auto onFirstDone = [storage](const NetworkQuery &task) {
488 // storage-> gives a pointer to MyCustomStruct instance,
489 // created dynamically by the running task tree.
490 storage->data = task.reply()->readAll();
491 };
492 const auto onSecondSetup = [storage](ConcurrentCall<QImage> &task) {
493 // storage-> gives a pointer to MyCustomStruct. Since the group is sequential,
494 // the stored MyCustomStruct was already updated inside the onFirstDone handler.
495 const QByteArray storedData = storage->data;
496 };
497
498 const Group group {
499 // When the group is entered by a running task tree, it creates MyCustomStruct
500 // instance dynamically. It is later accessible from all handlers via
501 // the *storage or storage-> operators.
502 sequential,
503 storage,
504 NetworkQueryTask(onFirstSetup, onFirstDone, CallDoneIf::Success),
505 ConcurrentCallTask<QImage>(onSecondSetup)
506 };
507 \endcode
508*/
509
510/*!
511 \fn Group::Group(const QList<GroupItem> &children)
512
513 Constructs a group with a given list of \a children.
514
515 This constructor is useful when the child items of the group are not known at compile time,
516 but later, at runtime:
517
518 \code
519 const QStringList sourceList = ...;
520
521 QList<GroupItem> groupItems { parallel };
522
523 for (const QString &source : sourceList) {
524 const NetworkQueryTask task(...); // use source for setup handler
525 groupItems << task;
526 }
527
528 const Group group(groupItems);
529 \endcode
530*/
531
532/*!
533 \fn Group::Group(std::initializer_list<GroupItem> children)
534
535 Constructs a group from \c std::initializer_list given by \a children.
536
537 This constructor is useful when all child items of the group are known at compile time:
538
539 \code
540 const Group group {
541 finishAllAndSuccess,
542 NetworkQueryTask(...),
543 Group {
544 NetworkQueryTask(...),
545 Group {
546 parallel,
547 NetworkQueryTask(...),
548 NetworkQueryTask(...),
549 }
550 ConcurrentCallTask<QString>(...)
551 }
552 };
553 \endcode
554*/
555
556/*!
557 \class Tasking::Sync
558 \inheaderfile solutions/tasking/tasktree.h
559 \inmodule TaskingSolution
560 \brief Synchronously executes a custom handler between other tasks.
561 \reentrant
562
563 \c Sync is useful when you want to execute an additional handler between other tasks.
564 \c Sync is seen by its parent \l {Tasking::Group} {Group} as any other task.
565 Avoid long-running execution of the \c Sync's handler body, since it is executed
566 synchronously from the caller thread. If that is unavoidable, consider using
567 \c ConcurrentCallTask instead.
568*/
569
570/*!
571 \fn template <typename Handler> Sync::Sync(Handler &&handler)
572
573 Constructs an element that executes a passed \a handler synchronously.
574 The \c Handler is of the \c std::function<DoneResult()> type.
575 The DoneResult value, returned by the \a handler, is considered during parent group's
576 \l {workflowPolicy} {workflow policy} resolution.
577 Optionally, the shortened form of \c std::function<void()> is also accepted.
578 In this case, it's assumed that the return value is DoneResult::Success.
579
580 The passed \a handler executes synchronously from the caller thread, so avoid a long-running
581 execution of the handler body. Otherwise, consider using \c ConcurrentCallTask.
582
583 \note The \c Sync element is not counted as a task when reporting task tree progress,
584 and is not included in TaskTree::taskCount() or TaskTree::progressMaximum().
585*/
586
587/*!
588 \class Tasking::CustomTask
589 \inheaderfile solutions/tasking/tasktree.h
590 \inmodule TaskingSolution
591 \brief A class template used for declaring custom task items and defining their setup
592 and done handlers.
593 \reentrant
594
595 Describes custom task items within task tree recipes.
596
597 Custom task names are aliased with unique names using the \c CustomTask template
598 with a given TaskAdapter subclass as a template parameter.
599 For example, \c ConcurrentCallTask<T> is an alias to the \c CustomTask that is defined
600 to work with \c ConcurrentCall<T> as an associated task class.
601 The following table contains example custom tasks and their associated task classes:
602
603 \table
604 \header
605 \li Aliased Task Name (Tasking Namespace)
606 \li Associated Task Class
607 \li Brief Description
608 \row
609 \li ConcurrentCallTask<ReturnType>
610 \li ConcurrentCall<ReturnType>
611 \li Starts an asynchronous task. Runs in a separate thread.
612 \row
613 \li NetworkQueryTask
614 \li NetworkQuery
615 \li Sends a network query.
616 \row
617 \li TaskTreeTask
618 \li TaskTree
619 \li Starts a nested task tree.
620 \row
621 \li TimeoutTask
622 \li \c std::chrono::milliseconds
623 \li Starts a timer.
624 \row
625 \li WaitForBarrierTask
626 \li MultiBarrier<Limit>
627 \li Starts an asynchronous task waiting for the barrier to pass.
628 \endtable
629*/
630
631/*!
632 \typealias Tasking::CustomTask::Task
633
634 Type alias for the task type associated with the custom task's \c Adapter.
635*/
636
637/*!
638 \typealias Tasking::CustomTask::Deleter
639
640 Type alias for the task's type deleter associated with the custom task's \c Adapter.
641*/
642
643/*!
644 \typealias Tasking::CustomTask::TaskSetupHandler
645
646 Type alias for \c std::function<SetupResult(Task &)>.
647
648 The \c TaskSetupHandler is an optional argument of a custom task element's constructor.
649 Any function with the above signature, when passed as a task setup handler,
650 will be called by the running task tree after the task is created and before it is started.
651
652 Inside the body of the handler, you may configure the task according to your needs.
653 The additional parameters, including storages, may be passed to the handler
654 via the lambda capture.
655 You can decide dynamically whether the task should be started or skipped with
656 success or an error.
657
658 \note Do not start the task inside the start handler by yourself. Leave it for TaskTree,
659 otherwise the behavior is undefined.
660
661 The return value of the handler instructs the running task tree on how to proceed
662 after the handler's invocation is finished. The return value of SetupResult::Continue
663 instructs the task tree to continue running, that is, to execute the associated \c Task.
664 The return value of SetupResult::StopWithSuccess or SetupResult::StopWithError
665 instructs the task tree to skip the task's execution and finish it immediately with
666 success or an error, respectively.
667
668 When the return type is either SetupResult::StopWithSuccess or SetupResult::StopWithError,
669 the task's done handler (if provided) isn't called afterwards.
670
671 The constructor of a custom task accepts also functions in the shortened form of
672 \c std::function<void(Task &)>, that is, the return value is \c void.
673 In this case, it's assumed that the return value is SetupResult::Continue.
674
675 \sa CustomTask(), TaskDoneHandler, GroupSetupHandler
676*/
677
678/*!
679 \typealias Tasking::CustomTask::TaskDoneHandler
680
681 Type alias for \c std::function<DoneResult(const Task &, DoneWith)> or DoneResult.
682
683 The \c TaskDoneHandler is an optional argument of a custom task element's constructor.
684 Any function with the above signature, when passed as a task done handler,
685 will be called by the running task tree after the task execution finished and before
686 the final result of the execution is reported back to the parent group.
687
688 Inside the body of the handler you may retrieve the final data from the finished task.
689 The additional parameters, including storages, may be passed to the handler
690 via the lambda capture.
691 It is also possible to decide dynamically whether the task should finish with its return
692 value, or the final result should be tweaked.
693
694 The DoneWith argument is optional and your done handler may omit it.
695 When provided, it holds the info about the final result of a task that will be
696 reported to its parent.
697
698 If you do not plan to read any data from the finished task,
699 you may omit the \c {const Task &} argument.
700
701 The returned DoneResult value is optional and your handler may return \c void instead.
702 In this case, the final result of the task will be equal to the value indicated by
703 the DoneWith argument. When the handler returns the DoneResult value,
704 the task's final result may be tweaked inside the done handler's body by the returned value.
705
706 For a \c TaskDoneHandler of the DoneResult type, no additional handling is executed,
707 and the task finishes unconditionally with the passed value of DoneResult.
708
709 \sa CustomTask(), TaskSetupHandler, GroupDoneHandler
710*/
711
712/*!
713 \fn template <typename Adapter> template <typename SetupHandler = TaskSetupHandler, typename DoneHandler = TaskDoneHandler> CustomTask<Adapter>::CustomTask(SetupHandler &&setup = TaskSetupHandler(), DoneHandler &&done = TaskDoneHandler(), CallDoneIf callDoneIf = CallDoneIf::SuccessOrError)
714
715 Constructs a \c CustomTask instance and attaches the \a setup and \a done handlers to the task.
716 When the running task tree is about to start the task,
717 it instantiates the associated \l Task object, invokes \a setup handler with a \e reference
718 to the created task, and starts it. When the running task finishes,
719 the task tree invokes a \a done handler, with a \c const \e reference to the created task.
720
721 The passed \a setup handler is of the \l TaskSetupHandler type. For example:
722
723 \code
724 static void parseAndLog(const QString &input);
725
726 ...
727
728 const QString input = ...;
729
730 const auto onFirstSetup = [input](ConcurrentCall<void> &task) {
731 if (input == "Skip")
732 return SetupResult::StopWithSuccess; // This task won't start, the next one will
733 if (input == "Error")
734 return SetupResult::StopWithError; // This task and the next one won't start
735 task.setConcurrentCallData(parseAndLog, input);
736 // This task will start, and the next one will start after this one finished with success
737 return SetupResult::Continue;
738 };
739
740 const auto onSecondSetup = [input](ConcurrentCall<void> &task) {
741 task.setConcurrentCallData(parseAndLog, input);
742 };
743
744 const Group group {
745 ConcurrentCallTask<void>(onFirstSetup),
746 ConcurrentCallTask<void>(onSecondSetup)
747 };
748 \endcode
749
750 The \a done handler is of the \l TaskDoneHandler type.
751 By default, the \a done handler is invoked whenever the task finishes.
752 Pass a non-default value for the \a callDoneIf argument when you want the handler to be called
753 only on a successful or failed execution.
754
755 \sa TaskSetupHandler, TaskDoneHandler
756*/
757
758/*!
759 \enum Tasking::WorkflowPolicy
760
761 This enum describes the possible behavior of the Group element when any group's child task
762 finishes its execution. It's also used when the running Group is canceled.
763
764 \value StopOnError
765 Default. Corresponds to the stopOnError global element.
766 If any child task finishes with an error, the group stops and finishes with an error.
767 If all child tasks finished with success, the group finishes with success.
768 If a group is empty, it finishes with success.
769 \value ContinueOnError
770 Corresponds to the continueOnError global element.
771 Similar to stopOnError, but in case any child finishes with an error,
772 the execution continues until all tasks finish, and the group reports an error
773 afterwards, even when some other tasks in the group finished with success.
774 If all child tasks finish successfully, the group finishes with success.
775 If a group is empty, it finishes with success.
776 \value StopOnSuccess
777 Corresponds to the stopOnSuccess global element.
778 If any child task finishes with success, the group stops and finishes with success.
779 If all child tasks finished with an error, the group finishes with an error.
780 If a group is empty, it finishes with an error.
781 \value ContinueOnSuccess
782 Corresponds to the continueOnSuccess global element.
783 Similar to stopOnSuccess, but in case any child finishes successfully,
784 the execution continues until all tasks finish, and the group reports success
785 afterwards, even when some other tasks in the group finished with an error.
786 If all child tasks finish with an error, the group finishes with an error.
787 If a group is empty, it finishes with an error.
788 \value StopOnSuccessOrError
789 Corresponds to the stopOnSuccessOrError global element.
790 The group starts as many tasks as it can. When any task finishes,
791 the group stops and reports the task's result.
792 Useful only in parallel mode.
793 In sequential mode, only the first task is started, and when finished,
794 the group finishes too, so the other tasks are always skipped.
795 If a group is empty, it finishes with an error.
796 \value FinishAllAndSuccess
797 Corresponds to the finishAllAndSuccess global element.
798 The group executes all tasks and ignores their return results. When all
799 tasks finished, the group finishes with success.
800 If a group is empty, it finishes with success.
801 \value FinishAllAndError
802 Corresponds to the finishAllAndError global element.
803 The group executes all tasks and ignores their return results. When all
804 tasks finished, the group finishes with an error.
805 If a group is empty, it finishes with an error.
806
807 Whenever a child task's result causes the Group to stop, that is,
808 in case of StopOnError, StopOnSuccess, or StopOnSuccessOrError policies,
809 the Group cancels the other running child tasks (if any - for example in parallel mode),
810 and skips executing tasks it has not started yet (for example, in the sequential mode -
811 those, that are placed after the failed task). Both canceling and skipping child tasks
812 may happen when parallelLimit() is used.
813
814 The table below summarizes the differences between various workflow policies:
815
816 \table
817 \header
818 \li \l WorkflowPolicy
819 \li Executes all child tasks
820 \li Result
821 \li Result when the group is empty
822 \row
823 \li StopOnError
824 \li Stops when any child task finished with an error and reports an error
825 \li An error when at least one child task failed, success otherwise
826 \li Success
827 \row
828 \li ContinueOnError
829 \li Yes
830 \li An error when at least one child task failed, success otherwise
831 \li Success
832 \row
833 \li StopOnSuccess
834 \li Stops when any child task finished with success and reports success
835 \li Success when at least one child task succeeded, an error otherwise
836 \li An error
837 \row
838 \li ContinueOnSuccess
839 \li Yes
840 \li Success when at least one child task succeeded, an error otherwise
841 \li An error
842 \row
843 \li StopOnSuccessOrError
844 \li Stops when any child task finished and reports child task's result
845 \li Success or an error, depending on the finished child task's result
846 \li An error
847 \row
848 \li FinishAllAndSuccess
849 \li Yes
850 \li Success
851 \li Success
852 \row
853 \li FinishAllAndError
854 \li Yes
855 \li An error
856 \li An error
857 \endtable
858
859 If a child of a group is also a group, the child group runs its tasks according to its own
860 workflow policy. When a parent group stops the running child group because
861 of parent group's workflow policy, that is, when the StopOnError, StopOnSuccess,
862 or StopOnSuccessOrError policy was used for the parent,
863 the child group's result is reported according to the
864 \b Result column and to the \b {child group's workflow policy} row in the table above.
865*/
866
867/*!
868 \variable Tasking::nullItem
869
870 A convenient global group's element indicating a no-op item.
871
872 This is useful in conditional expressions to indicate the absence of an optional element:
873
874 \code
875 const ExecutableItem task = ...;
876 const std::optional<ExecutableItem> optionalTask = ...;
877
878 Group group {
879 task,
880 optionalTask ? *optionalTask : nullItem
881 };
882 \endcode
883*/
884
885/*!
886 \variable Tasking::successItem
887
888 A convenient global executable element containing an empty, successful, synchronous task.
889
890 This is useful in if-statements to indicate that a branch ends with success:
891
892 \code
893 const ExecutableItem conditionalTask = ...;
894
895 Group group {
896 stopOnDone,
897 If (conditionalTask) >> Then {
898 ...
899 } >> Else {
900 successItem
901 },
902 nextTask
903 };
904 \endcode
905
906 In the above example, if the \c conditionalTask finishes with an error, the \c Else branch
907 is chosen, which finishes immediately with success. This causes the \c nextTask to be skipped
908 (because of the stopOnDone workflow policy of the \c group)
909 and the \c group finishes with success.
910
911 \sa errorItem
912*/
913
914/*!
915 \variable Tasking::errorItem
916
917 A convenient global executable element containing an empty, erroneous, synchronous task.
918
919 This is useful in if-statements to indicate that a branch ends with an error:
920
921 \code
922 const ExecutableItem conditionalTask = ...;
923
924 Group group {
925 stopOnError,
926 If (conditionalTask) >> Then {
927 ...
928 } >> Else {
929 errorItem
930 },
931 nextTask
932 };
933 \endcode
934
935 In the above example, if the \c conditionalTask finishes with an error, the \c Else branch
936 is chosen, which finishes immediately with an error. This causes the \c nextTask to be skipped
937 (because of the stopOnError workflow policy of the \c group)
938 and the \c group finishes with an error.
939
940 \sa successItem
941*/
942
943/*!
944 \variable Tasking::sequential
945 A convenient global group's element describing the sequential execution mode.
946
947 This is the default execution mode of the Group element.
948
949 When a Group has no execution mode, it runs in the sequential mode.
950 All the direct child tasks of a group are started in a chain, so that when one task finishes,
951 the next one starts. This enables you to pass the results from the previous task
952 as input to the next task before it starts. This mode guarantees that the next task
953 is started only after the previous task finishes.
954
955 \sa parallel, parallelLimit()
956*/
957
958/*!
959 \variable Tasking::parallel
960 A convenient global group's element describing the parallel execution mode.
961
962 All the direct child tasks of a group are started after the group is started,
963 without waiting for the previous child tasks to finish.
964 In this mode, all child tasks run simultaneously.
965
966 \sa sequential, parallelLimit()
967*/
968
969/*!
970 \variable Tasking::parallelIdealThreadCountLimit
971 A convenient global group's element describing the parallel execution mode with a limited
972 number of tasks running simultanously. The limit is equal to the ideal number of threads
973 excluding the calling thread.
974
975 This is a shortcut to:
976 \code
977 parallelLimit(qMax(QThread::idealThreadCount() - 1, 1))
978 \endcode
979
980 \sa parallel, parallelLimit()
981*/
982
983/*!
984 \variable Tasking::stopOnError
985 A convenient global group's element describing the StopOnError workflow policy.
986
987 This is the default workflow policy of the Group element.
988*/
989
990/*!
991 \variable Tasking::continueOnError
992 A convenient global group's element describing the ContinueOnError workflow policy.
993*/
994
995/*!
996 \variable Tasking::stopOnSuccess
997 A convenient global group's element describing the StopOnSuccess workflow policy.
998*/
999
1000/*!
1001 \variable Tasking::continueOnSuccess
1002 A convenient global group's element describing the ContinueOnSuccess workflow policy.
1003*/
1004
1005/*!
1006 \variable Tasking::stopOnSuccessOrError
1007 A convenient global group's element describing the StopOnSuccessOrError workflow policy.
1008*/
1009
1010/*!
1011 \variable Tasking::finishAllAndSuccess
1012 A convenient global group's element describing the FinishAllAndSuccess workflow policy.
1013*/
1014
1015/*!
1016 \variable Tasking::finishAllAndError
1017 A convenient global group's element describing the FinishAllAndError workflow policy.
1018*/
1019
1020/*!
1021 \enum Tasking::SetupResult
1022
1023 This enum is optionally returned from the group's or task's setup handler function.
1024 It instructs the running task tree on how to proceed after the setup handler's execution
1025 finished.
1026 \value Continue
1027 Default. The group's or task's execution continues normally.
1028 When a group's or task's setup handler returns void, it's assumed that
1029 it returned Continue.
1030 \value StopWithSuccess
1031 The group's or task's execution stops immediately with success.
1032 When returned from the group's setup handler, all child tasks are skipped,
1033 and the group's onGroupDone() handler is invoked with DoneWith::Success.
1034 The group reports success to its parent. The group's workflow policy is ignored.
1035 When returned from the task's setup handler, the task isn't started,
1036 its done handler isn't invoked, and the task reports success to its parent.
1037 \value StopWithError
1038 The group's or task's execution stops immediately with an error.
1039 When returned from the group's setup handler, all child tasks are skipped,
1040 and the group's onGroupDone() handler is invoked with DoneWith::Error.
1041 The group reports an error to its parent. The group's workflow policy is ignored.
1042 When returned from the task's setup handler, the task isn't started,
1043 its error handler isn't invoked, and the task reports an error to its parent.
1044*/
1045
1046/*!
1047 \enum Tasking::DoneResult
1048
1049 This enum is optionally returned from the group's or task's done handler function.
1050 When the done handler doesn't return any value, that is, its return type is \c void,
1051 its final return value is automatically deduced by the running task tree and reported
1052 to its parent group.
1053
1054 When the done handler returns the DoneResult, you can tweak the final return value
1055 inside the handler.
1056
1057 When the DoneResult is returned by the group's done handler, the group's workflow policy
1058 is ignored.
1059
1060 This enum is also used inside the TaskInterface::done() signal and it indicates whether
1061 the task finished with success or an error.
1062
1063 \value Success
1064 The group's or task's execution ends with success.
1065 \value Error
1066 The group's or task's execution ends with an error.
1067*/
1068
1069/*!
1070 \enum Tasking::DoneWith
1071
1072 This enum is an optional argument for the group's or task's done handler.
1073 It indicates whether the group or task finished with success or an error, or it was canceled.
1074
1075 It is also used as an argument inside the TaskTree::done() signal,
1076 indicating the final result of the TaskTree execution.
1077
1078 \value Success
1079 The group's or task's execution ended with success.
1080 \value Error
1081 The group's or task's execution ended with an error.
1082 \value Cancel
1083 The group's or task's execution was canceled. This happens when the user calls
1084 TaskTree::cancel() for the running task tree or when the group's workflow policy
1085 results in canceling some of its running children.
1086 Tweaking the done handler's final result by returning Tasking::DoneResult from
1087 the handler is no-op when the group's or task's execution was canceled.
1088*/
1089
1090/*!
1091 \enum Tasking::CallDoneIf
1092
1093 This enum is an optional argument for the \l onGroupDone() element or custom task's constructor.
1094 It instructs the task tree on when the group's or task's done handler should be invoked.
1095
1096 \value SuccessOrError
1097 The done handler is always invoked.
1098 \value Success
1099 The done handler is invoked only after successful execution,
1100 that is, when DoneWith::Success.
1101 \value Error
1102 The done handler is invoked only after failed execution,
1103 that is, when DoneWith::Error or when DoneWith::Cancel.
1104*/
1105
1106/*!
1107 \typealias Tasking::GroupItem::GroupSetupHandler
1108
1109 Type alias for \c std::function<SetupResult()>.
1110
1111 The \c GroupSetupHandler is an argument of the onGroupSetup() element.
1112 Any function with the above signature, when passed as a group setup handler,
1113 will be called by the running task tree when the group execution starts.
1114
1115 The return value of the handler instructs the running group on how to proceed
1116 after the handler's invocation is finished. The default return value of SetupResult::Continue
1117 instructs the group to continue running, that is, to start executing its child tasks.
1118 The return value of SetupResult::StopWithSuccess or SetupResult::StopWithError
1119 instructs the group to skip the child tasks' execution and finish immediately with
1120 success or an error, respectively.
1121
1122 When the return type is either SetupResult::StopWithSuccess or SetupResult::StopWithError,
1123 the group's done handler (if provided) is called synchronously immediately afterwards.
1124
1125 \note Even if the group setup handler returns StopWithSuccess or StopWithError,
1126 the group's done handler is invoked. This behavior differs from that of task done handler
1127 and might change in the future.
1128
1129 The onGroupSetup() element accepts also functions in the shortened form of
1130 \c std::function<void()>, that is, the return value is \c void.
1131 In this case, it's assumed that the return value is SetupResult::Continue.
1132
1133 \sa onGroupSetup(), GroupDoneHandler, CustomTask::TaskSetupHandler
1134*/
1135
1136/*!
1137 \typealias Tasking::GroupItem::GroupDoneHandler
1138
1139 Type alias for \c std::function<DoneResult(DoneWith)> or DoneResult.
1140
1141 The \c GroupDoneHandler is an argument of the onGroupDone() element.
1142 Any function with the above signature, when passed as a group done handler,
1143 will be called by the running task tree when the group execution ends.
1144
1145 The DoneWith argument is optional and your done handler may omit it.
1146 When provided, it holds the info about the final result of a group that will be
1147 reported to its parent.
1148
1149 The returned DoneResult value is optional and your handler may return \c void instead.
1150 In this case, the final result of the group will be equal to the value indicated by
1151 the DoneWith argument. When the handler returns the DoneResult value,
1152 the group's final result may be tweaked inside the done handler's body by the returned value.
1153
1154 For a \c GroupDoneHandler of the DoneResult type, no additional handling is executed,
1155 and the group finishes unconditionally with the passed value of DoneResult,
1156 ignoring the group's workflow policy.
1157
1158 \sa onGroupDone(), GroupSetupHandler, CustomTask::TaskDoneHandler
1159*/
1160
1161/*!
1162 \fn template <typename Handler> GroupItem onGroupSetup(Handler &&handler)
1163
1164 Constructs a group's element holding the group setup handler.
1165 The \a handler is invoked whenever the group starts.
1166
1167 The passed \a handler is either of the \c std::function<SetupResult()> or the
1168 \c std::function<void()> type. For more information on a possible handler type, refer to
1169 \l {GroupItem::GroupSetupHandler}.
1170
1171 When the \a handler is invoked, none of the group's child tasks are running yet.
1172
1173 If a group contains the Storage elements, the \a handler is invoked
1174 after the storages are constructed, so that the \a handler may already
1175 perform some initial modifications to the active storages.
1176
1177 \sa GroupItem::GroupSetupHandler, onGroupDone()
1178*/
1179
1180/*!
1181 \fn template <typename Handler> GroupItem onGroupDone(Handler &&handler, CallDoneIf callDoneIf = CallDoneIf::SuccessOrError)
1182
1183 Constructs a group's element holding the group done handler.
1184 By default, the \a handler is invoked whenever the group finishes.
1185 Pass a non-default value for the \a callDoneIf argument when you want the handler to be called
1186 only on a successful or failed execution.
1187 Depending on the group's workflow policy, this handler may also be called
1188 when the running group is canceled (e.g. when stopOnError element was used).
1189
1190 The passed \a handler is of the \c std::function<DoneResult(DoneWith)> type.
1191 Optionally, each of the return DoneResult type or the argument DoneWith type may be omitted
1192 (that is, its return type may be \c void). For more information on a possible handler type,
1193 refer to \l {GroupItem::GroupDoneHandler}.
1194
1195 When the \a handler is invoked, all of the group's child tasks are already finished.
1196
1197 If a group contains the Storage elements, the \a handler is invoked
1198 before the storages are destructed, so that the \a handler may still
1199 perform a last read of the active storages' data.
1200
1201 \sa GroupItem::GroupDoneHandler, onGroupSetup()
1202*/
1203
1204/*!
1205 Constructs a group's element describing the \l{Execution Mode}{execution mode}.
1206
1207 The execution mode element in a Group specifies how the direct child tasks of
1208 the Group are started.
1209
1210 For convenience, when appropriate, the \l sequential or \l parallel global elements
1211 may be used instead.
1212
1213 The \a limit defines the maximum number of direct child tasks running in parallel:
1214
1215 \list
1216 \li When \a limit equals to 0, there is no limit, and all direct child tasks are started
1217 together, in the oder in which they appear in a group. This means the fully parallel
1218 execution, and the \l parallel element may be used instead.
1219
1220 \li When \a limit equals to 1, it means that only one child task may run at the time.
1221 This means the sequential execution, and the \l sequential element may be used instead.
1222 In this case, child tasks run in chain, so the next child task starts after
1223 the previous child task has finished.
1224
1225 \li When other positive number is passed as \a limit, the group's child tasks run
1226 in parallel, but with a limited number of tasks running simultanously.
1227 The \e limit defines the maximum number of tasks running in parallel in a group.
1228 When the group is started, the first batch of tasks is started
1229 (the number of tasks in a batch equals to the passed \a limit, at most),
1230 while the others are kept waiting. When any running task finishes,
1231 the group starts the next remaining one, so that the \e limit of simultaneously
1232 running tasks inside a group isn't exceeded. This repeats on every child task's
1233 finish until all child tasks are started. This enables you to limit the maximum
1234 number of tasks that run simultaneously, for example if running too many processes might
1235 block the machine for a long time.
1236 \endlist
1237
1238 In all execution modes, a group starts tasks in the oder in which they appear.
1239
1240 If a child of a group is also a group, the child group runs its tasks according
1241 to its own execution mode.
1242
1243 \sa sequential, parallel
1244*/
1245GroupItem ParallelLimitFunctor::operator()(int limit) const
1246{
1247 return GroupItem({.m_groupHandler: {}, .m_parallelLimit: limit});
1248}
1249
1250/*!
1251 Constructs a group's \l {Workflow Policy} {workflow policy} element for a given \a policy.
1252
1253 For convenience, global elements may be used instead.
1254
1255 \sa stopOnError, continueOnError, stopOnSuccess, continueOnSuccess, stopOnSuccessOrError,
1256 finishAllAndSuccess, finishAllAndError, WorkflowPolicy
1257*/
1258GroupItem WorkflowPolicyFunctor::operator()(WorkflowPolicy policy) const
1259{
1260 return GroupItem({.m_groupHandler: {}, .m_parallelLimit: {}, .m_workflowPolicy: policy});
1261}
1262
1263const ParallelLimitFunctor parallelLimit = ParallelLimitFunctor();
1264const WorkflowPolicyFunctor workflowPolicy = WorkflowPolicyFunctor();
1265
1266const GroupItem sequential = parallelLimit(1);
1267const GroupItem parallel = parallelLimit(0);
1268const GroupItem parallelIdealThreadCountLimit = parallelLimit(qMax(a: QThread::idealThreadCount() - 1, b: 1));
1269
1270const GroupItem stopOnError = workflowPolicy(WorkflowPolicy::StopOnError);
1271const GroupItem continueOnError = workflowPolicy(WorkflowPolicy::ContinueOnError);
1272const GroupItem stopOnSuccess = workflowPolicy(WorkflowPolicy::StopOnSuccess);
1273const GroupItem continueOnSuccess = workflowPolicy(WorkflowPolicy::ContinueOnSuccess);
1274const GroupItem stopOnSuccessOrError = workflowPolicy(WorkflowPolicy::StopOnSuccessOrError);
1275const GroupItem finishAllAndSuccess = workflowPolicy(WorkflowPolicy::FinishAllAndSuccess);
1276const GroupItem finishAllAndError = workflowPolicy(WorkflowPolicy::FinishAllAndError);
1277
1278// Keep below the above in order to avoid static initialization fiasco.
1279const GroupItem nullItem = GroupItem({});
1280const ExecutableItem successItem = Group { finishAllAndSuccess };
1281const ExecutableItem errorItem = Group { finishAllAndError };
1282
1283// Please note the thread_local keyword below guarantees a separate instance per thread.
1284// The s_activeTaskTrees is currently used internally only and is not exposed in the public API.
1285// It serves for withLog() implementation now. Add a note here when a new usage is introduced.
1286static thread_local QList<TaskTree *> s_activeTaskTrees = {};
1287
1288static TaskTree *activeTaskTree()
1289{
1290 QT_ASSERT(s_activeTaskTrees.size(), return nullptr);
1291 return s_activeTaskTrees.back();
1292}
1293
1294DoneResult toDoneResult(bool success)
1295{
1296 return success ? DoneResult::Success : DoneResult::Error;
1297}
1298
1299static SetupResult toSetupResult(bool success)
1300{
1301 return success ? SetupResult::StopWithSuccess : SetupResult::StopWithError;
1302}
1303
1304static DoneResult toDoneResult(DoneWith doneWith)
1305{
1306 return doneWith == DoneWith::Success ? DoneResult::Success : DoneResult::Error;
1307}
1308
1309static DoneWith toDoneWith(DoneResult result)
1310{
1311 return result == DoneResult::Success ? DoneWith::Success : DoneWith::Error;
1312}
1313
1314class LoopThreadData
1315{
1316 Q_DISABLE_COPY_MOVE(LoopThreadData)
1317
1318public:
1319 LoopThreadData() = default;
1320 void pushIteration(int index)
1321 {
1322 m_activeLoopStack.push_back(t: index);
1323 }
1324 void popIteration()
1325 {
1326 QT_ASSERT(m_activeLoopStack.size(), return);
1327 m_activeLoopStack.pop_back();
1328 }
1329 int iteration() const
1330 {
1331 QT_ASSERT(m_activeLoopStack.size(), qWarning(
1332 "The referenced loop is not reachable in the running tree. "
1333 "A -1 will be returned which might lead to a crash in the calling code. "
1334 "It is possible that no loop was added to the tree, "
1335 "or the loop is not reachable from where it is referenced."); return -1);
1336 return m_activeLoopStack.last();
1337 }
1338
1339private:
1340 QList<int> m_activeLoopStack;
1341};
1342
1343class LoopData
1344{
1345public:
1346 LoopThreadData &threadData() {
1347 QMutexLocker lock(&m_threadDataMutex);
1348 return m_threadDataMap.try_emplace(k: QThread::currentThread()).first->second;
1349 }
1350
1351 const std::optional<int> m_loopCount = {};
1352 const Loop::ValueGetter m_valueGetter = {};
1353 const Loop::Condition m_condition = {};
1354 QMutex m_threadDataMutex = {};
1355 // Use std::map on purpose, so that it doesn't invalidate references on modifications.
1356 // Don't optimize it by using std::unordered_map.
1357 std::map<QThread *, LoopThreadData> m_threadDataMap = {};
1358};
1359
1360Loop::Loop()
1361 : m_loopData(new LoopData)
1362{}
1363
1364Loop::Loop(int count, const ValueGetter &valueGetter)
1365 : m_loopData(new LoopData{.m_loopCount: count, .m_valueGetter: valueGetter})
1366{}
1367
1368Loop::Loop(const Condition &condition)
1369 : m_loopData(new LoopData{.m_loopCount: {}, .m_valueGetter: {}, .m_condition: condition})
1370{}
1371
1372int Loop::iteration() const
1373{
1374 return m_loopData->threadData().iteration();
1375}
1376
1377const void *Loop::valuePtr() const
1378{
1379 return m_loopData->m_valueGetter(iteration());
1380}
1381
1382using StoragePtr = void *;
1383
1384static constexpr QLatin1StringView s_activeStorageWarning =
1385 "The referenced storage is not reachable in the running tree. "
1386 "A nullptr will be returned which might lead to a crash in the calling code. "
1387 "It is possible that no storage was added to the tree, "
1388 "or the storage is not reachable from where it is referenced."_L1;
1389
1390class StorageThreadData
1391{
1392 Q_DISABLE_COPY_MOVE(StorageThreadData)
1393
1394public:
1395 StorageThreadData() = default;
1396 void pushStorage(StoragePtr storagePtr)
1397 {
1398 m_activeStorageStack.push_back(t: {storagePtr, activeTaskTree()});
1399 }
1400 void popStorage()
1401 {
1402 QT_ASSERT(m_activeStorageStack.size(), return);
1403 m_activeStorageStack.pop_back();
1404 }
1405 StoragePtr activeStorage() const
1406 {
1407 QT_ASSERT(m_activeStorageStack.size(),
1408 qWarning().noquote() << s_activeStorageWarning; return nullptr);
1409 const QPair<StoragePtr, TaskTree *> &top = m_activeStorageStack.last();
1410 QT_ASSERT(top.second == activeTaskTree(),
1411 qWarning().noquote() << s_activeStorageWarning; return nullptr);
1412 return top.first;
1413 }
1414
1415private:
1416 QList<QPair<StoragePtr, TaskTree *>> m_activeStorageStack;
1417};
1418
1419class StorageData
1420{
1421public:
1422 StorageThreadData &threadData() {
1423 QMutexLocker lock(&m_threadDataMutex);
1424 return m_threadDataMap.try_emplace(k: QThread::currentThread()).first->second;
1425 }
1426
1427 const StorageBase::StorageConstructor m_constructor = {};
1428 const StorageBase::StorageDestructor m_destructor = {};
1429 QMutex m_threadDataMutex = {};
1430 // Use std::map on purpose, so that it doesn't invalidate references on modifications.
1431 // Don't optimize it by using std::unordered_map.
1432 std::map<QThread *, StorageThreadData> m_threadDataMap = {};
1433};
1434
1435StorageBase::StorageBase(const StorageConstructor &ctor, const StorageDestructor &dtor)
1436 : m_storageData(new StorageData{.m_constructor: ctor, .m_destructor: dtor})
1437{}
1438
1439void *StorageBase::activeStorageVoid() const
1440{
1441 return m_storageData->threadData().activeStorage();
1442}
1443
1444void GroupItem::addChildren(const QList<GroupItem> &children)
1445{
1446 QT_ASSERT(m_type == Type::Group || m_type == Type::List,
1447 qWarning("Only Group or List may have children, skipping..."); return);
1448 if (m_type == Type::List) {
1449 m_children.append(l: children);
1450 return;
1451 }
1452 for (const GroupItem &child : children) {
1453 switch (child.m_type) {
1454 case Type::List:
1455 addChildren(children: child.m_children);
1456 break;
1457 case Type::Group:
1458 m_children.append(t: child);
1459 break;
1460 case Type::GroupData:
1461 if (child.m_groupData.m_groupHandler.m_setupHandler) {
1462 QT_ASSERT(!m_groupData.m_groupHandler.m_setupHandler,
1463 qWarning("Group setup handler redefinition, overriding..."));
1464 m_groupData.m_groupHandler.m_setupHandler
1465 = child.m_groupData.m_groupHandler.m_setupHandler;
1466 }
1467 if (child.m_groupData.m_groupHandler.m_doneHandler) {
1468 QT_ASSERT(!m_groupData.m_groupHandler.m_doneHandler,
1469 qWarning("Group done handler redefinition, overriding..."));
1470 m_groupData.m_groupHandler.m_doneHandler
1471 = child.m_groupData.m_groupHandler.m_doneHandler;
1472 m_groupData.m_groupHandler.m_callDoneIf
1473 = child.m_groupData.m_groupHandler.m_callDoneIf;
1474 }
1475 if (child.m_groupData.m_parallelLimit) {
1476 QT_ASSERT(!m_groupData.m_parallelLimit,
1477 qWarning("Group execution mode redefinition, overriding..."));
1478 m_groupData.m_parallelLimit = child.m_groupData.m_parallelLimit;
1479 }
1480 if (child.m_groupData.m_workflowPolicy) {
1481 QT_ASSERT(!m_groupData.m_workflowPolicy,
1482 qWarning("Group workflow policy redefinition, overriding..."));
1483 m_groupData.m_workflowPolicy = child.m_groupData.m_workflowPolicy;
1484 }
1485 if (child.m_groupData.m_loop) {
1486 QT_ASSERT(!m_groupData.m_loop,
1487 qWarning("Group loop redefinition, overriding..."));
1488 m_groupData.m_loop = child.m_groupData.m_loop;
1489 }
1490 break;
1491 case Type::TaskHandler:
1492 QT_ASSERT(child.m_taskHandler.m_createHandler,
1493 qWarning("Task create handler can't be null, skipping..."); return);
1494 m_children.append(t: child);
1495 break;
1496 case Type::Storage:
1497 // Check for duplicates, as can't have the same storage twice on the same level.
1498 for (const StorageBase &storage : child.m_storageList) {
1499 if (m_storageList.contains(t: storage)) {
1500 QT_ASSERT(false, qWarning("Can't add the same storage into one Group twice, "
1501 "skipping..."));
1502 continue;
1503 }
1504 m_storageList.append(t: storage);
1505 }
1506 break;
1507 }
1508 }
1509}
1510
1511/*!
1512 \class Tasking::ExecutableItem
1513 \inheaderfile solutions/tasking/tasktree.h
1514 \inmodule TaskingSolution
1515 \brief Base class for executable task items.
1516 \reentrant
1517
1518 \c ExecutableItem provides an additional interface for items containing executable tasks.
1519 Use withTimeout() to attach a timeout to a task.
1520 Use withLog() to include debugging information about the task startup and the execution result.
1521*/
1522
1523/*!
1524 Attaches \c TimeoutTask to a copy of \c this ExecutableItem, elapsing after \a timeout
1525 in milliseconds, with an optionally provided timeout \a handler, and returns the coupled item.
1526
1527 When the ExecutableItem finishes before \a timeout passes, the returned item finishes
1528 immediately with the task's result. Otherwise, \a handler is invoked (if provided),
1529 the task is canceled, and the returned item finishes with an error.
1530*/
1531ExecutableItem ExecutableItem::withTimeout(milliseconds timeout,
1532 const std::function<void()> &handler) const
1533{
1534 const auto onSetup = [timeout](milliseconds &timeoutData) { timeoutData = timeout; };
1535 return Group {
1536 parallel,
1537 stopOnSuccessOrError,
1538 Group {
1539 finishAllAndError,
1540 handler ? TimeoutTask(onSetup, [handler] { handler(); }, CallDoneIf::Success)
1541 : TimeoutTask(onSetup)
1542 },
1543 *this
1544 };
1545}
1546
1547static QString currentTime() { return QTime::currentTime().toString(f: Qt::ISODateWithMs); }
1548
1549static QString logHeader(const QString &logName)
1550{
1551 return QString::fromLatin1(ba: "TASK TREE LOG [%1] \"%2\"").arg(args: currentTime(), args: logName);
1552};
1553
1554/*!
1555 Attaches a custom debug printout to a copy of \c this ExecutableItem,
1556 issued on task startup and after the task is finished, and returns the coupled item.
1557
1558 The debug printout includes a timestamp of the event (start or finish)
1559 and \a logName to identify the specific task in the debug log.
1560
1561 The finish printout contains the additional information whether the execution was
1562 synchronous or asynchronous, its result (the value described by the DoneWith enum),
1563 and the total execution time in milliseconds.
1564*/
1565ExecutableItem ExecutableItem::withLog(const QString &logName) const
1566{
1567 struct LogStorage
1568 {
1569 time_point<system_clock, nanoseconds> start;
1570 int asyncCount = 0;
1571 };
1572 const Storage<LogStorage> storage;
1573 return Group {
1574 storage,
1575 onGroupSetup(handler: [storage, logName] {
1576 storage->start = system_clock::now();
1577 storage->asyncCount = activeTaskTree()->asyncCount();
1578 qDebug().noquote().nospace() << logHeader(logName) << " started.";
1579 }),
1580 *this,
1581 onGroupDone(handler: [storage, logName](DoneWith result) {
1582 const auto elapsed = duration_cast<milliseconds>(d: system_clock::now() - storage->start);
1583 const int asyncCountDiff = activeTaskTree()->asyncCount() - storage->asyncCount;
1584 QT_CHECK(asyncCountDiff >= 0);
1585 const QMetaEnum doneWithEnum = QMetaEnum::fromType<DoneWith>();
1586 const QString syncType = asyncCountDiff ? QString::fromLatin1(ba: "asynchronously")
1587 : QString::fromLatin1(ba: "synchronously");
1588 qDebug().noquote().nospace() << logHeader(logName) << " finished " << syncType
1589 << " with " << doneWithEnum.valueToKey(value: int(result))
1590 << " within " << elapsed.count() << "ms.";
1591 })
1592 };
1593}
1594
1595/*!
1596 \fn ExecutableItem ExecutableItem::operator!(const ExecutableItem &item)
1597
1598 Returns an ExecutableItem with the DoneResult of \a item negated.
1599
1600 If \a item reports DoneResult::Success, the returned item reports DoneResult::Error.
1601 If \a item reports DoneResult::Error, the returned item reports DoneResult::Success.
1602
1603 The returned item is equivalent to:
1604 \code
1605 Group {
1606 item,
1607 onGroupDone([](DoneWith doneWith) { return toDoneResult(doneWith == DoneWith::Error); })
1608 }
1609 \endcode
1610
1611 \sa operator&&(), operator||()
1612*/
1613ExecutableItem operator!(const ExecutableItem &item)
1614{
1615 return Group {
1616 item,
1617 onGroupDone(handler: [](DoneWith doneWith) { return toDoneResult(success: doneWith == DoneWith::Error); })
1618 };
1619}
1620
1621/*!
1622 \fn ExecutableItem ExecutableItem::operator&&(const ExecutableItem &first, const ExecutableItem &second)
1623
1624 Returns an ExecutableItem with \a first and \a second tasks merged with conjunction.
1625
1626 Both \a first and \a second tasks execute in sequence.
1627 If both tasks report DoneResult::Success, the returned item reports DoneResult::Success.
1628 Otherwise, the returned item reports DoneResult::Error.
1629
1630 The returned item is
1631 \l {https://en.wikipedia.org/wiki/Short-circuit_evaluation}{short-circuiting}:
1632 if the \a first task reports DoneResult::Error, the \a second task is skipped,
1633 and the returned item reports DoneResult::Error immediately.
1634
1635 The returned item is equivalent to:
1636 \code
1637 Group { stopOnError, first, second }
1638 \endcode
1639
1640 \note Parallel execution of conjunction in a short-circuit manner can be achieved with the
1641 following code: \c {Group { parallel, stopOnError, first, second }}. In this case:
1642 if the \e {first finished} task reports DoneResult::Error,
1643 the \e other task is canceled, and the group reports DoneResult::Error immediately.
1644
1645 \sa operator||(), operator!()
1646*/
1647ExecutableItem operator&&(const ExecutableItem &first, const ExecutableItem &second)
1648{
1649 return Group { stopOnError, first, second };
1650}
1651
1652/*!
1653 \fn ExecutableItem ExecutableItem::operator||(const ExecutableItem &first, const ExecutableItem &second)
1654
1655 Returns an ExecutableItem with \a first and \a second tasks merged with disjunction.
1656
1657 Both \a first and \a second tasks execute in sequence.
1658 If both tasks report DoneResult::Error, the returned item reports DoneResult::Error.
1659 Otherwise, the returned item reports DoneResult::Success.
1660
1661 The returned item is
1662 \l {https://en.wikipedia.org/wiki/Short-circuit_evaluation}{short-circuiting}:
1663 if the \a first task reports DoneResult::Success, the \a second task is skipped,
1664 and the returned item reports DoneResult::Success immediately.
1665
1666 The returned item is equivalent to:
1667 \code
1668 Group { stopOnSuccess, first, second }
1669 \endcode
1670
1671 \note Parallel execution of disjunction in a short-circuit manner can be achieved with the
1672 following code: \c {Group { parallel, stopOnSuccess, first, second }}. In this case:
1673 if the \e {first finished} task reports DoneResult::Success,
1674 the \e other task is canceled, and the group reports DoneResult::Success immediately.
1675
1676 \sa operator&&(), operator!()
1677*/
1678ExecutableItem operator||(const ExecutableItem &first, const ExecutableItem &second)
1679{
1680 return Group { stopOnSuccess, first, second };
1681}
1682
1683/*!
1684 \fn ExecutableItem ExecutableItem::operator&&(const ExecutableItem &item, DoneResult result)
1685 \overload ExecutableItem::operator&&()
1686
1687 Returns the \a item task if the \a result is DoneResult::Success; otherwise returns
1688 the \a item task with its done result tweaked to DoneResult::Error.
1689
1690 The \c {task && DoneResult::Error} is an eqivalent to tweaking the task's done result
1691 into DoneResult::Error unconditionally.
1692*/
1693ExecutableItem operator&&(const ExecutableItem &item, DoneResult result)
1694{
1695 if (result == DoneResult::Success)
1696 return item;
1697 return Group { finishAllAndError, item };
1698}
1699
1700/*!
1701 \fn ExecutableItem ExecutableItem::operator||(const ExecutableItem &item, DoneResult result)
1702 \overload ExecutableItem::operator||()
1703
1704 Returns the \a item task if the \a result is DoneResult::Error; otherwise returns
1705 the \a item task with its done result tweaked to DoneResult::Success.
1706
1707 The \c {task || DoneResult::Success} is an eqivalent to tweaking the task's done result
1708 into DoneResult::Success unconditionally.
1709*/
1710ExecutableItem operator||(const ExecutableItem &item, DoneResult result)
1711{
1712 if (result == DoneResult::Error)
1713 return item;
1714 return Group { finishAllAndSuccess, item };
1715}
1716
1717ExecutableItem ExecutableItem::withCancelImpl(
1718 const std::function<void(QObject *, const std::function<void()> &)> &connectWrapper) const
1719{
1720 const auto onSetup = [connectWrapper](Barrier &barrier) {
1721 connectWrapper(&barrier, [barrierPtr = &barrier] { barrierPtr->advance(); });
1722 };
1723 return Group {
1724 parallel,
1725 stopOnSuccessOrError,
1726 Group {
1727 finishAllAndError,
1728 BarrierTask(onSetup)
1729 },
1730 *this
1731 };
1732}
1733
1734class TaskTreePrivate;
1735class TaskNode;
1736class RuntimeContainer;
1737class RuntimeIteration;
1738class RuntimeTask;
1739
1740class ExecutionContextActivator
1741{
1742public:
1743 ExecutionContextActivator(RuntimeIteration *iteration) {
1744 activateTaskTree(iteration);
1745 activateContext(iteration);
1746 }
1747 ExecutionContextActivator(RuntimeContainer *container) {
1748 activateTaskTree(container);
1749 activateContext(container);
1750 }
1751 ~ExecutionContextActivator() {
1752 for (int i = m_activeStorages.size() - 1; i >= 0; --i) // iterate in reverse order
1753 m_activeStorages[i].m_storageData->threadData().popStorage();
1754 for (int i = m_activeLoops.size() - 1; i >= 0; --i) // iterate in reverse order
1755 m_activeLoops[i].m_loopData->threadData().popIteration();
1756 QT_ASSERT(s_activeTaskTrees.size(), return);
1757 s_activeTaskTrees.pop_back();
1758 }
1759
1760private:
1761 void activateTaskTree(RuntimeIteration *iteration);
1762 void activateTaskTree(RuntimeContainer *container);
1763 void activateContext(RuntimeIteration *iteration);
1764 void activateContext(RuntimeContainer *container);
1765 QList<Loop> m_activeLoops;
1766 QList<StorageBase> m_activeStorages;
1767};
1768
1769class ContainerNode
1770{
1771 Q_DISABLE_COPY(ContainerNode)
1772
1773public:
1774 ContainerNode(ContainerNode &&other) = default;
1775 ContainerNode(TaskTreePrivate *taskTreePrivate, const GroupItem &task);
1776
1777 TaskTreePrivate *const m_taskTreePrivate = nullptr;
1778
1779 const GroupItem::GroupHandler m_groupHandler;
1780 const int m_parallelLimit = 1;
1781 const WorkflowPolicy m_workflowPolicy = WorkflowPolicy::StopOnError;
1782 const std::optional<Loop> m_loop;
1783 const QList<StorageBase> m_storageList;
1784 std::vector<TaskNode> m_children;
1785 const int m_taskCount = 0;
1786};
1787
1788class TaskNode
1789{
1790 Q_DISABLE_COPY(TaskNode)
1791
1792public:
1793 TaskNode(TaskNode &&other) = default;
1794 TaskNode(TaskTreePrivate *taskTreePrivate, const GroupItem &task)
1795 : m_taskHandler(task.m_taskHandler)
1796 , m_container(taskTreePrivate, task)
1797 {}
1798
1799 bool isTask() const { return bool(m_taskHandler.m_createHandler); }
1800 int taskCount() const { return isTask() ? 1 : m_container.m_taskCount; }
1801
1802 const GroupItem::TaskHandler m_taskHandler;
1803 ContainerNode m_container;
1804};
1805
1806class TaskTreePrivate
1807{
1808 Q_DISABLE_COPY_MOVE(TaskTreePrivate)
1809
1810public:
1811 explicit TaskTreePrivate(TaskTree *taskTree);
1812 ~TaskTreePrivate();
1813
1814 void start();
1815 void stop();
1816 void bumpAsyncCount();
1817 void advanceProgress(int byValue);
1818 void emitDone(DoneWith result);
1819 void callSetupHandler(StorageBase storage, StoragePtr storagePtr) {
1820 callStorageHandler(storage, storagePtr, ptr: &StorageHandler::m_setupHandler);
1821 }
1822 void callDoneHandler(StorageBase storage, StoragePtr storagePtr) {
1823 callStorageHandler(storage, storagePtr, ptr: &StorageHandler::m_doneHandler);
1824 }
1825 struct StorageHandler {
1826 StorageBase::StorageHandler m_setupHandler = {};
1827 StorageBase::StorageHandler m_doneHandler = {};
1828 };
1829 typedef StorageBase::StorageHandler StorageHandler::*HandlerPtr; // ptr to class member
1830 void callStorageHandler(StorageBase storage, StoragePtr storagePtr, HandlerPtr ptr)
1831 {
1832 const auto it = m_storageHandlers.constFind(key: storage);
1833 if (it == m_storageHandlers.constEnd())
1834 return;
1835 const StorageHandler storageHandler = *it;
1836 if (storageHandler.*ptr) {
1837 GuardLocker locker(m_guard);
1838 (storageHandler.*ptr)(storagePtr);
1839 }
1840 }
1841
1842 // Node related methods
1843
1844 // If returned value != Continue, childDone() needs to be called in parent container (in caller)
1845 // in order to unwind properly.
1846 SetupResult start(RuntimeTask *node);
1847 void stop(RuntimeTask *node);
1848 bool invokeDoneHandler(RuntimeTask *node, DoneWith doneWith);
1849
1850 // Container related methods
1851
1852 SetupResult start(RuntimeContainer *container);
1853 SetupResult continueStart(RuntimeContainer *container, SetupResult startAction);
1854 SetupResult startChildren(RuntimeContainer *container);
1855 SetupResult childDone(RuntimeIteration *iteration, bool success);
1856 void stop(RuntimeContainer *container);
1857 bool invokeDoneHandler(RuntimeContainer *container, DoneWith doneWith);
1858 bool invokeLoopHandler(RuntimeContainer *container);
1859
1860 template <typename Container, typename Handler, typename ...Args,
1861 typename ReturnType = std::invoke_result_t<Handler, Args...>>
1862 ReturnType invokeHandler(Container *container, Handler &&handler, Args &&...args)
1863 {
1864 ExecutionContextActivator activator(container);
1865 GuardLocker locker(m_guard);
1866 return std::invoke(std::forward<Handler>(handler), std::forward<Args>(args)...);
1867 }
1868
1869 static int effectiveLoopCount(const std::optional<Loop> &loop)
1870 {
1871 return loop && loop->m_loopData->m_loopCount ? *loop->m_loopData->m_loopCount : 1;
1872 }
1873
1874 TaskTree *q = nullptr;
1875 Guard m_guard;
1876 int m_progressValue = 0;
1877 int m_asyncCount = 0;
1878 QSet<StorageBase> m_storages;
1879 QHash<StorageBase, StorageHandler> m_storageHandlers;
1880 std::optional<TaskNode> m_root;
1881 std::unique_ptr<RuntimeTask> m_runtimeRoot; // Keep me last in order to destruct first
1882};
1883
1884static bool initialSuccessBit(WorkflowPolicy workflowPolicy)
1885{
1886 switch (workflowPolicy) {
1887 case WorkflowPolicy::StopOnError:
1888 case WorkflowPolicy::ContinueOnError:
1889 case WorkflowPolicy::FinishAllAndSuccess:
1890 return true;
1891 case WorkflowPolicy::StopOnSuccess:
1892 case WorkflowPolicy::ContinueOnSuccess:
1893 case WorkflowPolicy::StopOnSuccessOrError:
1894 case WorkflowPolicy::FinishAllAndError:
1895 return false;
1896 }
1897 QT_CHECK(false);
1898 return false;
1899}
1900
1901static bool isProgressive(RuntimeContainer *container);
1902
1903class RuntimeIteration
1904{
1905 Q_DISABLE_COPY(RuntimeIteration)
1906
1907public:
1908 RuntimeIteration(int index, RuntimeContainer *container);
1909 ~RuntimeIteration();
1910 std::optional<Loop> loop() const;
1911 void deleteChild(RuntimeTask *node);
1912
1913 const int m_iterationIndex = 0;
1914 const bool m_isProgressive = true;
1915 RuntimeContainer *m_container = nullptr;
1916 int m_doneCount = 0;
1917 std::vector<std::unique_ptr<RuntimeTask>> m_children = {}; // Owning.
1918};
1919
1920class RuntimeContainer
1921{
1922 Q_DISABLE_COPY(RuntimeContainer)
1923
1924public:
1925 RuntimeContainer(const ContainerNode &taskContainer, RuntimeTask *parentTask)
1926 : m_containerNode(taskContainer)
1927 , m_parentTask(parentTask)
1928 , m_storages(createStorages(container: taskContainer))
1929 , m_successBit(initialSuccessBit(workflowPolicy: taskContainer.m_workflowPolicy))
1930 , m_shouldIterate(taskContainer.m_loop)
1931 {}
1932
1933 ~RuntimeContainer()
1934 {
1935 for (int i = m_containerNode.m_storageList.size() - 1; i >= 0; --i) { // iterate in reverse order
1936 const StorageBase storage = m_containerNode.m_storageList[i];
1937 StoragePtr storagePtr = m_storages.value(i);
1938 if (m_callStorageDoneHandlersOnDestruction)
1939 m_containerNode.m_taskTreePrivate->callDoneHandler(storage, storagePtr);
1940 storage.m_storageData->m_destructor(storagePtr);
1941 }
1942 }
1943
1944 static QList<StoragePtr> createStorages(const ContainerNode &container);
1945 bool isStarting() const { return m_startGuard.isLocked(); }
1946 RuntimeIteration *parentIteration() const;
1947 bool updateSuccessBit(bool success);
1948 void deleteFinishedIterations();
1949 int progressiveLoopCount() const
1950 {
1951 return m_containerNode.m_taskTreePrivate->effectiveLoopCount(loop: m_containerNode.m_loop);
1952 }
1953
1954 const ContainerNode &m_containerNode; // Not owning.
1955 RuntimeTask *m_parentTask = nullptr; // Not owning.
1956 const QList<StoragePtr> m_storages; // Owning.
1957
1958 bool m_successBit = true;
1959 bool m_callStorageDoneHandlersOnDestruction = false;
1960 Guard m_startGuard;
1961
1962 int m_iterationCount = 0;
1963 int m_nextToStart = 0;
1964 int m_runningChildren = 0;
1965 bool m_shouldIterate = true;
1966 std::vector<std::unique_ptr<RuntimeIteration>> m_iterations; // Owning.
1967};
1968
1969class RuntimeTask
1970{
1971public:
1972 ~RuntimeTask()
1973 {
1974 if (m_task) {
1975 // Ensures the running task's d'tor doesn't emit done() signal. QTCREATORBUG-30204.
1976 QObject::disconnect(sender: m_task.get(), signal: &TaskInterface::done,
1977 receiver: m_taskNode.m_container.m_taskTreePrivate->q, zero: nullptr);
1978 }
1979 }
1980
1981 const TaskNode &m_taskNode; // Not owning.
1982 RuntimeIteration *m_parentIteration = nullptr; // Not owning.
1983 std::optional<RuntimeContainer> m_container = {}; // Owning.
1984 std::unique_ptr<TaskInterface> m_task = {}; // Owning.
1985};
1986
1987RuntimeIteration::~RuntimeIteration() = default;
1988
1989TaskTreePrivate::TaskTreePrivate(TaskTree *taskTree)
1990 : q(taskTree) {}
1991TaskTreePrivate::~TaskTreePrivate() = default;
1992
1993static bool isProgressive(RuntimeContainer *container)
1994{
1995 RuntimeIteration *iteration = container->m_parentTask->m_parentIteration;
1996 return iteration ? iteration->m_isProgressive : true;
1997}
1998
1999void ExecutionContextActivator::activateTaskTree(RuntimeIteration *iteration)
2000{
2001 activateTaskTree(container: iteration->m_container);
2002}
2003
2004void ExecutionContextActivator::activateTaskTree(RuntimeContainer *container)
2005{
2006 s_activeTaskTrees.push_back(t: container->m_containerNode.m_taskTreePrivate->q);
2007}
2008
2009void ExecutionContextActivator::activateContext(RuntimeIteration *iteration)
2010{
2011 std::optional<Loop> loop = iteration->loop();
2012 if (loop) {
2013 loop->m_loopData->threadData().pushIteration(index: iteration->m_iterationIndex);
2014 m_activeLoops.append(t: *loop);
2015 }
2016 activateContext(container: iteration->m_container);
2017}
2018
2019void ExecutionContextActivator::activateContext(RuntimeContainer *container)
2020{
2021 const ContainerNode &containerNode = container->m_containerNode;
2022 for (int i = 0; i < containerNode.m_storageList.size(); ++i) {
2023 const StorageBase &storage = containerNode.m_storageList[i];
2024 if (m_activeStorages.contains(t: storage))
2025 continue; // Storage shadowing: The storage is already active, skipping it...
2026 m_activeStorages.append(t: storage);
2027 storage.m_storageData->threadData().pushStorage(storagePtr: container->m_storages.value(i));
2028 }
2029 // Go to the parent after activating this storages so that storage shadowing works
2030 // in the direction from child to parent root.
2031 if (container->parentIteration())
2032 activateContext(iteration: container->parentIteration());
2033}
2034
2035void TaskTreePrivate::start()
2036{
2037 QT_ASSERT(m_root, return);
2038 QT_ASSERT(!m_runtimeRoot, return);
2039 m_asyncCount = 0;
2040 m_progressValue = 0;
2041 {
2042 GuardLocker locker(m_guard);
2043 emit q->started();
2044 emit q->asyncCountChanged(count: m_asyncCount);
2045 emit q->progressValueChanged(value: m_progressValue);
2046 }
2047 // TODO: check storage handlers for not existing storages in tree
2048 for (auto it = m_storageHandlers.cbegin(); it != m_storageHandlers.cend(); ++it) {
2049 QT_ASSERT(m_storages.contains(it.key()), qWarning("The registered storage doesn't "
2050 "exist in task tree. Its handlers will never be called."));
2051 }
2052 m_runtimeRoot.reset(p: new RuntimeTask{.m_taskNode: *m_root});
2053 start(node: m_runtimeRoot.get());
2054 bumpAsyncCount();
2055}
2056
2057void TaskTreePrivate::stop()
2058{
2059 QT_ASSERT(m_root, return);
2060 if (!m_runtimeRoot)
2061 return;
2062 stop(node: m_runtimeRoot.get());
2063 m_runtimeRoot.reset();
2064 emitDone(result: DoneWith::Cancel);
2065}
2066
2067void TaskTreePrivate::bumpAsyncCount()
2068{
2069 if (!m_runtimeRoot)
2070 return;
2071 ++m_asyncCount;
2072 GuardLocker locker(m_guard);
2073 emit q->asyncCountChanged(count: m_asyncCount);
2074}
2075
2076void TaskTreePrivate::advanceProgress(int byValue)
2077{
2078 if (byValue == 0)
2079 return;
2080 QT_CHECK(byValue > 0);
2081 QT_CHECK(m_progressValue + byValue <= m_root->taskCount());
2082 m_progressValue += byValue;
2083 GuardLocker locker(m_guard);
2084 emit q->progressValueChanged(value: m_progressValue);
2085}
2086
2087void TaskTreePrivate::emitDone(DoneWith result)
2088{
2089 QT_CHECK(m_progressValue == m_root->taskCount());
2090 GuardLocker locker(m_guard);
2091 emit q->done(result);
2092}
2093
2094RuntimeIteration::RuntimeIteration(int index, RuntimeContainer *container)
2095 : m_iterationIndex(index)
2096 , m_isProgressive(index < container->progressiveLoopCount() && isProgressive(container))
2097 , m_container(container)
2098{}
2099
2100std::optional<Loop> RuntimeIteration::loop() const
2101{
2102 return m_container->m_containerNode.m_loop;
2103}
2104
2105void RuntimeIteration::deleteChild(RuntimeTask *task)
2106{
2107 const auto it = std::find_if(first: m_children.cbegin(), last: m_children.cend(), pred: [task](const auto &ptr) {
2108 return ptr.get() == task;
2109 });
2110 if (it != m_children.cend())
2111 m_children.erase(position: it);
2112}
2113
2114static std::vector<TaskNode> createChildren(TaskTreePrivate *taskTreePrivate,
2115 const QList<GroupItem> &children)
2116{
2117 std::vector<TaskNode> result;
2118 result.reserve(n: children.size());
2119 for (const GroupItem &child : children)
2120 result.emplace_back(args&: taskTreePrivate, args: child);
2121 return result;
2122}
2123
2124ContainerNode::ContainerNode(TaskTreePrivate *taskTreePrivate, const GroupItem &task)
2125 : m_taskTreePrivate(taskTreePrivate)
2126 , m_groupHandler(task.m_groupData.m_groupHandler)
2127 , m_parallelLimit(task.m_groupData.m_parallelLimit.value_or(u: 1))
2128 , m_workflowPolicy(task.m_groupData.m_workflowPolicy.value_or(u: WorkflowPolicy::StopOnError))
2129 , m_loop(task.m_groupData.m_loop)
2130 , m_storageList(task.m_storageList)
2131 , m_children(createChildren(taskTreePrivate, children: task.m_children))
2132 , m_taskCount(std::accumulate(first: m_children.cbegin(), last: m_children.cend(), init: 0,
2133 binary_op: [](int r, const TaskNode &n) { return r + n.taskCount(); })
2134 * taskTreePrivate->effectiveLoopCount(loop: m_loop))
2135{
2136 for (const StorageBase &storage : m_storageList)
2137 m_taskTreePrivate->m_storages << storage;
2138}
2139
2140QList<StoragePtr> RuntimeContainer::createStorages(const ContainerNode &container)
2141{
2142 QList<StoragePtr> storages;
2143 for (const StorageBase &storage : container.m_storageList) {
2144 StoragePtr storagePtr = storage.m_storageData->m_constructor();
2145 storages.append(t: storagePtr);
2146 container.m_taskTreePrivate->callSetupHandler(storage, storagePtr);
2147 }
2148 return storages;
2149}
2150
2151RuntimeIteration *RuntimeContainer::parentIteration() const
2152{
2153 return m_parentTask->m_parentIteration;
2154}
2155
2156bool RuntimeContainer::updateSuccessBit(bool success)
2157{
2158 if (m_containerNode.m_workflowPolicy == WorkflowPolicy::FinishAllAndSuccess
2159 || m_containerNode.m_workflowPolicy == WorkflowPolicy::FinishAllAndError
2160 || m_containerNode.m_workflowPolicy == WorkflowPolicy::StopOnSuccessOrError) {
2161 if (m_containerNode.m_workflowPolicy == WorkflowPolicy::StopOnSuccessOrError)
2162 m_successBit = success;
2163 return m_successBit;
2164 }
2165
2166 const bool donePolicy = m_containerNode.m_workflowPolicy == WorkflowPolicy::StopOnSuccess
2167 || m_containerNode.m_workflowPolicy == WorkflowPolicy::ContinueOnSuccess;
2168 m_successBit = donePolicy ? (m_successBit || success) : (m_successBit && success);
2169 return m_successBit;
2170}
2171
2172void RuntimeContainer::deleteFinishedIterations()
2173{
2174 for (auto it = m_iterations.cbegin(); it != m_iterations.cend(); ) {
2175 if (it->get()->m_doneCount == int(m_containerNode.m_children.size()))
2176 it = m_iterations.erase(position: it);
2177 else
2178 ++it;
2179 }
2180}
2181
2182SetupResult TaskTreePrivate::start(RuntimeContainer *container)
2183{
2184 const ContainerNode &containerNode = container->m_containerNode;
2185 SetupResult startAction = SetupResult::Continue;
2186 if (containerNode.m_groupHandler.m_setupHandler) {
2187 startAction = invokeHandler(container, handler: containerNode.m_groupHandler.m_setupHandler);
2188 if (startAction != SetupResult::Continue) {
2189 if (isProgressive(container))
2190 advanceProgress(byValue: containerNode.m_taskCount);
2191 // Non-Continue SetupResult takes precedence over the workflow policy.
2192 container->m_successBit = startAction == SetupResult::StopWithSuccess;
2193 }
2194 }
2195 return continueStart(container, startAction);
2196}
2197
2198SetupResult TaskTreePrivate::continueStart(RuntimeContainer *container, SetupResult startAction)
2199{
2200 const SetupResult groupAction = startAction == SetupResult::Continue ? startChildren(container)
2201 : startAction;
2202 if (groupAction == SetupResult::Continue)
2203 return groupAction;
2204
2205 const bool bit = container->updateSuccessBit(success: groupAction == SetupResult::StopWithSuccess);
2206 RuntimeIteration *parentIteration = container->parentIteration();
2207 RuntimeTask *parentTask = container->m_parentTask;
2208 QT_CHECK(parentTask);
2209 const bool result = invokeDoneHandler(container, doneWith: bit ? DoneWith::Success : DoneWith::Error);
2210 if (parentIteration) {
2211 parentIteration->deleteChild(task: parentTask);
2212 if (!parentIteration->m_container->isStarting())
2213 childDone(iteration: parentIteration, success: result);
2214 } else {
2215 QT_CHECK(m_runtimeRoot.get() == parentTask);
2216 m_runtimeRoot.reset();
2217 emitDone(result: result ? DoneWith::Success : DoneWith::Error);
2218 }
2219 return toSetupResult(success: result);
2220}
2221
2222SetupResult TaskTreePrivate::startChildren(RuntimeContainer *container)
2223{
2224 const ContainerNode &containerNode = container->m_containerNode;
2225 const int childCount = int(containerNode.m_children.size());
2226
2227 if (container->m_iterationCount == 0) {
2228 if (container->m_shouldIterate && !invokeLoopHandler(container)) {
2229 if (isProgressive(container))
2230 advanceProgress(byValue: containerNode.m_taskCount);
2231 return toSetupResult(success: container->m_successBit);
2232 }
2233 container->m_iterations.emplace_back(
2234 args: std::make_unique<RuntimeIteration>(args&: container->m_iterationCount, args&: container));
2235 ++container->m_iterationCount;
2236 }
2237
2238 GuardLocker locker(container->m_startGuard);
2239
2240 while (containerNode.m_parallelLimit == 0
2241 || container->m_runningChildren < containerNode.m_parallelLimit) {
2242 container->deleteFinishedIterations();
2243 if (container->m_nextToStart == childCount) {
2244 if (invokeLoopHandler(container)) {
2245 container->m_nextToStart = 0;
2246 container->m_iterations.emplace_back(
2247 args: std::make_unique<RuntimeIteration>(args&: container->m_iterationCount, args&: container));
2248 ++container->m_iterationCount;
2249 } else if (container->m_iterations.empty()) {
2250 return toSetupResult(success: container->m_successBit);
2251 } else {
2252 return SetupResult::Continue;
2253 }
2254 }
2255 if (containerNode.m_children.size() == 0) // Empty loop body.
2256 continue;
2257
2258 RuntimeIteration *iteration = container->m_iterations.back().get();
2259 RuntimeTask *newTask = new RuntimeTask{.m_taskNode: containerNode.m_children.at(n: container->m_nextToStart),
2260 .m_parentIteration: iteration};
2261 iteration->m_children.emplace_back(args&: newTask);
2262 ++container->m_runningChildren;
2263 ++container->m_nextToStart;
2264
2265 const SetupResult startAction = start(node: newTask);
2266 if (startAction == SetupResult::Continue)
2267 continue;
2268
2269 const SetupResult finalizeAction = childDone(iteration,
2270 success: startAction == SetupResult::StopWithSuccess);
2271 if (finalizeAction != SetupResult::Continue)
2272 return finalizeAction;
2273 }
2274 return SetupResult::Continue;
2275}
2276
2277SetupResult TaskTreePrivate::childDone(RuntimeIteration *iteration, bool success)
2278{
2279 RuntimeContainer *container = iteration->m_container;
2280 const WorkflowPolicy &workflowPolicy = container->m_containerNode.m_workflowPolicy;
2281 const bool shouldStop = workflowPolicy == WorkflowPolicy::StopOnSuccessOrError
2282 || (workflowPolicy == WorkflowPolicy::StopOnSuccess && success)
2283 || (workflowPolicy == WorkflowPolicy::StopOnError && !success);
2284 ++iteration->m_doneCount;
2285 --container->m_runningChildren;
2286 if (shouldStop)
2287 stop(container);
2288
2289 const bool updatedSuccess = container->updateSuccessBit(success);
2290 const SetupResult startAction = shouldStop ? toSetupResult(success: updatedSuccess)
2291 : SetupResult::Continue;
2292
2293 if (container->isStarting())
2294 return startAction;
2295 return continueStart(container, startAction);
2296}
2297
2298void TaskTreePrivate::stop(RuntimeContainer *container)
2299{
2300 const ContainerNode &containerNode = container->m_containerNode;
2301 for (auto &iteration : container->m_iterations) {
2302 for (auto &child : iteration->m_children) {
2303 ++iteration->m_doneCount;
2304 stop(node: child.get());
2305 }
2306
2307 if (iteration->m_isProgressive) {
2308 int skippedTaskCount = 0;
2309 for (int i = iteration->m_doneCount; i < int(containerNode.m_children.size()); ++i)
2310 skippedTaskCount += containerNode.m_children.at(n: i).taskCount();
2311 advanceProgress(byValue: skippedTaskCount);
2312 }
2313 }
2314 const int skippedIterations = container->progressiveLoopCount() - container->m_iterationCount;
2315 if (skippedIterations > 0) {
2316 advanceProgress(byValue: container->m_containerNode.m_taskCount / container->progressiveLoopCount()
2317 * skippedIterations);
2318 }
2319}
2320
2321static bool shouldCall(CallDoneIf callDoneIf, DoneWith result)
2322{
2323 if (result == DoneWith::Success)
2324 return callDoneIf != CallDoneIf::Error;
2325 return callDoneIf != CallDoneIf::Success;
2326}
2327
2328bool TaskTreePrivate::invokeDoneHandler(RuntimeContainer *container, DoneWith doneWith)
2329{
2330 DoneResult result = toDoneResult(doneWith);
2331 const GroupItem::GroupHandler &groupHandler = container->m_containerNode.m_groupHandler;
2332 if (groupHandler.m_doneHandler && shouldCall(callDoneIf: groupHandler.m_callDoneIf, result: doneWith))
2333 result = invokeHandler(container, handler: groupHandler.m_doneHandler, args&: doneWith);
2334 container->m_callStorageDoneHandlersOnDestruction = true;
2335 // TODO: is it needed?
2336 container->m_parentTask->m_container.reset();
2337 return result == DoneResult::Success;
2338}
2339
2340bool TaskTreePrivate::invokeLoopHandler(RuntimeContainer *container)
2341{
2342 if (container->m_shouldIterate) {
2343 const LoopData *loopData = container->m_containerNode.m_loop->m_loopData.get();
2344 if (loopData->m_loopCount) {
2345 container->m_shouldIterate = container->m_iterationCount < loopData->m_loopCount;
2346 } else if (loopData->m_condition) {
2347 container->m_shouldIterate = invokeHandler(container, handler: loopData->m_condition,
2348 args&: container->m_iterationCount);
2349 }
2350 }
2351 return container->m_shouldIterate;
2352}
2353
2354SetupResult TaskTreePrivate::start(RuntimeTask *node)
2355{
2356 if (!node->m_taskNode.isTask()) {
2357 node->m_container.emplace(args: node->m_taskNode.m_container, args&: node);
2358 return start(container: &*node->m_container);
2359 }
2360
2361 const GroupItem::TaskHandler &handler = node->m_taskNode.m_taskHandler;
2362 node->m_task.reset(p: handler.m_createHandler());
2363 const SetupResult startAction = handler.m_setupHandler
2364 ? invokeHandler(container: node->m_parentIteration, handler: handler.m_setupHandler, args&: *node->m_task.get())
2365 : SetupResult::Continue;
2366 if (startAction != SetupResult::Continue) {
2367 if (node->m_parentIteration->m_isProgressive)
2368 advanceProgress(byValue: 1);
2369 node->m_parentIteration->deleteChild(task: node);
2370 return startAction;
2371 }
2372 const std::shared_ptr<SetupResult> unwindAction
2373 = std::make_shared<SetupResult>(args: SetupResult::Continue);
2374 QObject::connect(sender: node->m_task.get(), signal: &TaskInterface::done,
2375 context: q, slot: [this, node, unwindAction](DoneResult doneResult) {
2376 const bool result = invokeDoneHandler(node, doneWith: toDoneWith(result: doneResult));
2377 QObject::disconnect(sender: node->m_task.get(), signal: &TaskInterface::done, receiver: q, zero: nullptr);
2378 node->m_task.release()->deleteLater();
2379 RuntimeIteration *parentIteration = node->m_parentIteration;
2380 parentIteration->deleteChild(task: node);
2381 if (parentIteration->m_container->isStarting()) {
2382 *unwindAction = toSetupResult(success: result);
2383 } else {
2384 childDone(iteration: parentIteration, success: result);
2385 bumpAsyncCount();
2386 }
2387 });
2388
2389 node->m_task->start();
2390 return *unwindAction;
2391}
2392
2393void TaskTreePrivate::stop(RuntimeTask *node)
2394{
2395 if (!node->m_task) {
2396 if (!node->m_container)
2397 return;
2398 stop(container: &*node->m_container);
2399 node->m_container->updateSuccessBit(success: false);
2400 invokeDoneHandler(container: &*node->m_container, doneWith: DoneWith::Cancel);
2401 return;
2402 }
2403
2404 invokeDoneHandler(node, doneWith: DoneWith::Cancel);
2405 node->m_task.reset();
2406}
2407
2408bool TaskTreePrivate::invokeDoneHandler(RuntimeTask *node, DoneWith doneWith)
2409{
2410 DoneResult result = toDoneResult(doneWith);
2411 const GroupItem::TaskHandler &handler = node->m_taskNode.m_taskHandler;
2412 if (handler.m_doneHandler && shouldCall(callDoneIf: handler.m_callDoneIf, result: doneWith)) {
2413 result = invokeHandler(container: node->m_parentIteration,
2414 handler: handler.m_doneHandler, args&: *node->m_task.get(), args&: doneWith);
2415 }
2416 if (node->m_parentIteration->m_isProgressive)
2417 advanceProgress(byValue: 1);
2418 return result == DoneResult::Success;
2419}
2420
2421/*!
2422 \class Tasking::TaskTree
2423 \inheaderfile solutions/tasking/tasktree.h
2424 \inmodule TaskingSolution
2425 \brief The TaskTree class runs an async task tree structure defined in a declarative way.
2426 \reentrant
2427
2428 Use the Tasking namespace to build extensible, declarative task tree
2429 structures that contain possibly asynchronous tasks, such as QProcess,
2430 NetworkQuery, or ConcurrentCall<ReturnType>. TaskTree structures enable you
2431 to create a sophisticated mixture of a parallel or sequential flow of tasks
2432 in the form of a tree and to run it any time later.
2433
2434 \section1 Root Element and Tasks
2435
2436 The TaskTree has a mandatory Group root element, which may contain
2437 any number of tasks of various types, such as QProcessTask, NetworkQueryTask,
2438 or ConcurrentCallTask<ReturnType>:
2439
2440 \code
2441 using namespace Tasking;
2442
2443 const Group root {
2444 QProcessTask(...),
2445 NetworkQueryTask(...),
2446 ConcurrentCallTask<int>(...)
2447 };
2448
2449 TaskTree *taskTree = new TaskTree(root);
2450 connect(taskTree, &TaskTree::done, ...); // finish handler
2451 taskTree->start();
2452 \endcode
2453
2454 The task tree above has a top level element of the Group type that contains
2455 tasks of the QProcessTask, NetworkQueryTask, and ConcurrentCallTask<int> type.
2456 After taskTree->start() is called, the tasks are run in a chain, starting
2457 with QProcessTask. When the QProcessTask finishes successfully, the NetworkQueryTask
2458 task is started. Finally, when the network task finishes successfully, the
2459 ConcurrentCallTask<int> task is started.
2460
2461 When the last running task finishes with success, the task tree is considered
2462 to have run successfully and the done() signal is emitted with DoneWith::Success.
2463 When a task finishes with an error, the execution of the task tree is stopped
2464 and the remaining tasks are skipped. The task tree finishes with an error and
2465 sends the TaskTree::done() signal with DoneWith::Error.
2466
2467 \section1 Groups
2468
2469 The parent of the Group sees it as a single task. Like other tasks,
2470 the group can be started and it can finish with success or an error.
2471 The Group elements can be nested to create a tree structure:
2472
2473 \code
2474 const Group root {
2475 Group {
2476 parallel,
2477 QProcessTask(...),
2478 ConcurrentCallTask<int>(...)
2479 },
2480 NetworkQueryTask(...)
2481 };
2482 \endcode
2483
2484 The example above differs from the first example in that the root element has
2485 a subgroup that contains the QProcessTask and ConcurrentCallTask<int>. The subgroup is a
2486 sibling element of the NetworkQueryTask in the root. The subgroup contains an
2487 additional \e parallel element that instructs its Group to execute its tasks
2488 in parallel.
2489
2490 So, when the tree above is started, the QProcessTask and ConcurrentCallTask<int> start
2491 immediately and run in parallel. Since the root group doesn't contain a
2492 \e parallel element, its direct child tasks are run in sequence. Thus, the
2493 NetworkQueryTask starts when the whole subgroup finishes. The group is
2494 considered as finished when all its tasks have finished. The order in which
2495 the tasks finish is not relevant.
2496
2497 So, depending on which task lasts longer (QProcessTask or ConcurrentCallTask<int>), the
2498 following scenarios can take place:
2499
2500 \table
2501 \header
2502 \li Scenario 1
2503 \li Scenario 2
2504 \row
2505 \li Root Group starts
2506 \li Root Group starts
2507 \row
2508 \li Sub Group starts
2509 \li Sub Group starts
2510 \row
2511 \li QProcessTask starts
2512 \li QProcessTask starts
2513 \row
2514 \li ConcurrentCallTask<int> starts
2515 \li ConcurrentCallTask<int> starts
2516 \row
2517 \li ...
2518 \li ...
2519 \row
2520 \li \b {QProcessTask finishes}
2521 \li \b {ConcurrentCallTask<int> finishes}
2522 \row
2523 \li ...
2524 \li ...
2525 \row
2526 \li \b {ConcurrentCallTask<int> finishes}
2527 \li \b {QProcessTask finishes}
2528 \row
2529 \li Sub Group finishes
2530 \li Sub Group finishes
2531 \row
2532 \li NetworkQueryTask starts
2533 \li NetworkQueryTask starts
2534 \row
2535 \li ...
2536 \li ...
2537 \row
2538 \li NetworkQueryTask finishes
2539 \li NetworkQueryTask finishes
2540 \row
2541 \li Root Group finishes
2542 \li Root Group finishes
2543 \endtable
2544
2545 The differences between the scenarios are marked with bold. Three dots mean
2546 that an unspecified amount of time passes between previous and next events
2547 (a task or tasks continue to run). No dots between events
2548 means that they occur synchronously.
2549
2550 The presented scenarios assume that all tasks run successfully. If a task
2551 fails during execution, the task tree finishes with an error. In particular,
2552 when QProcessTask finishes with an error while ConcurrentCallTask<int> is still being executed,
2553 the ConcurrentCallTask<int> is automatically canceled, the subgroup finishes with an error,
2554 the NetworkQueryTask is skipped, and the tree finishes with an error.
2555
2556 \section1 Task Types
2557
2558 Each task type is associated with its corresponding task class that executes
2559 the task. For example, a QProcessTask inside a task tree is associated with
2560 the QProcess class that executes the process. The associated objects are
2561 automatically created, started, and destructed exclusively by the task tree
2562 at the appropriate time.
2563
2564 If a root group consists of five sequential QProcessTask tasks, and the task tree
2565 executes the group, it creates an instance of QProcess for the first
2566 QProcessTask and starts it. If the QProcess instance finishes successfully,
2567 the task tree destructs it and creates a new QProcess instance for the
2568 second QProcessTask, and so on. If the first task finishes with an error, the task
2569 tree stops creating QProcess instances, and the root group finishes with an
2570 error.
2571
2572 The following table shows examples of task types and their corresponding task
2573 classes:
2574
2575 \table
2576 \header
2577 \li Task Type (Tasking Namespace)
2578 \li Associated Task Class
2579 \li Brief Description
2580 \row
2581 \li QProcessTask
2582 \li QProcess
2583 \li Starts process.
2584 \row
2585 \li ConcurrentCallTask<ReturnType>
2586 \li Tasking::ConcurrentCall<ReturnType>
2587 \li Starts asynchronous task, runs in separate thread.
2588 \row
2589 \li TaskTreeTask
2590 \li Tasking::TaskTree
2591 \li Starts nested task tree.
2592 \row
2593 \li NetworkQueryTask
2594 \li NetworkQuery
2595 \li Starts network download.
2596 \endtable
2597
2598 \section1 Task Handlers
2599
2600 Use Task handlers to set up a task for execution and to enable reading
2601 the output data from the task when it finishes with success or an error.
2602
2603 \section2 Task's Start Handler
2604
2605 When a corresponding task class object is created and before it's started,
2606 the task tree invokes an optionally user-provided setup handler. The setup
2607 handler should always take a \e reference to the associated task class object:
2608
2609 \code
2610 const auto onSetup = [](QProcess &process) {
2611 process.setCommand({"sleep", {"3"}});
2612 };
2613 const Group root {
2614 QProcessTask(onSetup)
2615 };
2616 \endcode
2617
2618 You can modify the passed QProcess in the setup handler, so that the task
2619 tree can start the process according to your configuration.
2620 You should not call \c {process.start();} in the setup handler,
2621 as the task tree calls it when needed. The setup handler is optional. When used,
2622 it must be the first argument of the task's constructor.
2623
2624 Optionally, the setup handler may return a SetupResult. The returned
2625 SetupResult influences the further start behavior of a given task. The
2626 possible values are:
2627
2628 \table
2629 \header
2630 \li SetupResult Value
2631 \li Brief Description
2632 \row
2633 \li Continue
2634 \li The task will be started normally. This is the default behavior when the
2635 setup handler doesn't return SetupResult (that is, its return type is
2636 void).
2637 \row
2638 \li StopWithSuccess
2639 \li The task won't be started and it will report success to its parent.
2640 \row
2641 \li StopWithError
2642 \li The task won't be started and it will report an error to its parent.
2643 \endtable
2644
2645 This is useful for running a task only when a condition is met and the data
2646 needed to evaluate this condition is not known until previously started tasks
2647 finish. In this way, the setup handler dynamically decides whether to start the
2648 corresponding task normally or skip it and report success or an error.
2649 For more information about inter-task data exchange, see \l Storage.
2650
2651 \section2 Task's Done Handler
2652
2653 When a running task finishes, the task tree invokes an optionally provided done handler.
2654 The handler should take a \c const \e reference to the associated task class object:
2655
2656 \code
2657 const auto onSetup = [](QProcess &process) {
2658 process.setCommand({"sleep", {"3"}});
2659 };
2660 const auto onDone = [](const QProcess &process, DoneWith result) {
2661 if (result == DoneWith::Success)
2662 qDebug() << "Success" << process.cleanedStdOut();
2663 else
2664 qDebug() << "Failure" << process.cleanedStdErr();
2665 };
2666 const Group root {
2667 QProcessTask(onSetup, onDone)
2668 };
2669 \endcode
2670
2671 The done handler may collect output data from QProcess, and store it
2672 for further processing or perform additional actions.
2673
2674 \note If the task setup handler returns StopWithSuccess or StopWithError,
2675 the done handler is not invoked.
2676
2677 \section1 Group Handlers
2678
2679 Similarly to task handlers, group handlers enable you to set up a group to
2680 execute and to apply more actions when the whole group finishes with
2681 success or an error.
2682
2683 \section2 Group's Start Handler
2684
2685 The task tree invokes the group start handler before it starts the child
2686 tasks. The group handler doesn't take any arguments:
2687
2688 \code
2689 const auto onSetup = [] {
2690 qDebug() << "Entering the group";
2691 };
2692 const Group root {
2693 onGroupSetup(onSetup),
2694 QProcessTask(...)
2695 };
2696 \endcode
2697
2698 The group setup handler is optional. To define a group setup handler, add an
2699 onGroupSetup() element to a group. The argument of onGroupSetup() is a user
2700 handler. If you add more than one onGroupSetup() element to a group, an assert
2701 is triggered at runtime that includes an error message.
2702
2703 Like the task's start handler, the group start handler may return SetupResult.
2704 The returned SetupResult value affects the start behavior of the
2705 whole group. If you do not specify a group start handler or its return type
2706 is void, the default group's action is SetupResult::Continue, so that all
2707 tasks are started normally. Otherwise, when the start handler returns
2708 SetupResult::StopWithSuccess or SetupResult::StopWithError, the tasks are not
2709 started (they are skipped) and the group itself reports success or failure,
2710 depending on the returned value, respectively.
2711
2712 \code
2713 const Group root {
2714 onGroupSetup([] { qDebug() << "Root setup"; }),
2715 Group {
2716 onGroupSetup([] { qDebug() << "Group 1 setup"; return SetupResult::Continue; }),
2717 QProcessTask(...) // Process 1
2718 },
2719 Group {
2720 onGroupSetup([] { qDebug() << "Group 2 setup"; return SetupResult::StopWithSuccess; }),
2721 QProcessTask(...) // Process 2
2722 },
2723 Group {
2724 onGroupSetup([] { qDebug() << "Group 3 setup"; return SetupResult::StopWithError; }),
2725 QProcessTask(...) // Process 3
2726 },
2727 QProcessTask(...) // Process 4
2728 };
2729 \endcode
2730
2731 In the above example, all subgroups of a root group define their setup handlers.
2732 The following scenario assumes that all started processes finish with success:
2733
2734 \table
2735 \header
2736 \li Scenario
2737 \li Comment
2738 \row
2739 \li Root Group starts
2740 \li Doesn't return SetupResult, so its tasks are executed.
2741 \row
2742 \li Group 1 starts
2743 \li Returns Continue, so its tasks are executed.
2744 \row
2745 \li Process 1 starts
2746 \li
2747 \row
2748 \li ...
2749 \li ...
2750 \row
2751 \li Process 1 finishes (success)
2752 \li
2753 \row
2754 \li Group 1 finishes (success)
2755 \li
2756 \row
2757 \li Group 2 starts
2758 \li Returns StopWithSuccess, so Process 2 is skipped and Group 2 reports
2759 success.
2760 \row
2761 \li Group 2 finishes (success)
2762 \li
2763 \row
2764 \li Group 3 starts
2765 \li Returns StopWithError, so Process 3 is skipped and Group 3 reports
2766 an error.
2767 \row
2768 \li Group 3 finishes (error)
2769 \li
2770 \row
2771 \li Root Group finishes (error)
2772 \li Group 3, which is a direct child of the root group, finished with an
2773 error, so the root group stops executing, skips Process 4, which has
2774 not started yet, and reports an error.
2775 \endtable
2776
2777 \section2 Groups's Done Handler
2778
2779 A Group's done handler is executed after the successful or failed execution of its tasks.
2780 The final value reported by the group depends on its \l {Workflow Policy}.
2781 The handler can apply other necessary actions.
2782 The done handler is defined inside the onGroupDone() element of a group.
2783 It may take the optional DoneWith argument, indicating the successful or failed execution:
2784
2785 \code
2786 const Group root {
2787 onGroupSetup([] { qDebug() << "Root setup"; }),
2788 QProcessTask(...),
2789 onGroupDone([](DoneWith result) {
2790 if (result == DoneWith::Success)
2791 qDebug() << "Root finished with success";
2792 else
2793 qDebug() << "Root finished with an error";
2794 })
2795 };
2796 \endcode
2797
2798 The group done handler is optional. If you add more than one onGroupDone() to a group,
2799 an assert is triggered at runtime that includes an error message.
2800
2801 \note Even if the group setup handler returns StopWithSuccess or StopWithError,
2802 the group's done handler is invoked. This behavior differs from that of task done handler
2803 and might change in the future.
2804
2805 \section1 Other Group Elements
2806
2807 A group can contain other elements that describe the processing flow, such as
2808 the execution mode or workflow policy. It can also contain storage elements
2809 that are responsible for collecting and sharing custom common data gathered
2810 during group execution.
2811
2812 \section2 Execution Mode
2813
2814 The execution mode element in a Group specifies how the direct child tasks of
2815 the Group are started. The most common execution modes are \l sequential and
2816 \l parallel. It's also possible to specify the limit of tasks running
2817 in parallel by using the parallelLimit() function.
2818
2819 In all execution modes, a group starts tasks in the oder in which they appear.
2820
2821 If a child of a group is also a group, the child group runs its tasks
2822 according to its own execution mode.
2823
2824 \section2 Workflow Policy
2825
2826 The workflow policy element in a Group specifies how the group should behave
2827 when any of its \e direct child's tasks finish. For a detailed description of possible
2828 policies, refer to WorkflowPolicy.
2829
2830 If a child of a group is also a group, the child group runs its tasks
2831 according to its own workflow policy.
2832
2833 \section2 Storage
2834
2835 Use the \l {Tasking::Storage} {Storage} element to exchange information between tasks.
2836 Especially, in the sequential execution mode, when a task needs data from another,
2837 already finished task, before it can start. For example, a task tree that copies data by reading
2838 it from a source and writing it to a destination might look as follows:
2839
2840 \code
2841 static QByteArray load(const QString &fileName) { ... }
2842 static void save(const QString &fileName, const QByteArray &array) { ... }
2843
2844 static Group copyRecipe(const QString &source, const QString &destination)
2845 {
2846 struct CopyStorage { // [1] custom inter-task struct
2847 QByteArray content; // [2] custom inter-task data
2848 };
2849
2850 // [3] instance of custom inter-task struct manageable by task tree
2851 const Storage<CopyStorage> storage;
2852
2853 const auto onLoaderSetup = [source](ConcurrentCall<QByteArray> &async) {
2854 async.setConcurrentCallData(&load, source);
2855 };
2856 // [4] runtime: task tree activates the instance from [7] before invoking handler
2857 const auto onLoaderDone = [storage](const ConcurrentCall<QByteArray> &async) {
2858 storage->content = async.result(); // [5] loader stores the result in storage
2859 };
2860
2861 // [4] runtime: task tree activates the instance from [7] before invoking handler
2862 const auto onSaverSetup = [storage, destination](ConcurrentCall<void> &async) {
2863 const QByteArray content = storage->content; // [6] saver takes data from storage
2864 async.setConcurrentCallData(&save, destination, content);
2865 };
2866 const auto onSaverDone = [](const ConcurrentCall<void> &async) {
2867 qDebug() << "Save done successfully";
2868 };
2869
2870 const Group root {
2871 // [7] runtime: task tree creates an instance of CopyStorage when root is entered
2872 storage,
2873 ConcurrentCallTask<QByteArray>(onLoaderSetup, onLoaderDone, CallDoneIf::Success),
2874 ConcurrentCallTask<void>(onSaverSetup, onSaverDone, CallDoneIf::Success)
2875 };
2876 return root;
2877 }
2878
2879 const QString source = ...;
2880 const QString destination = ...;
2881 TaskTree taskTree(copyRecipe(source, destination));
2882 connect(&taskTree, &TaskTree::done,
2883 &taskTree, [](DoneWith result) {
2884 if (result == DoneWith::Success)
2885 qDebug() << "The copying finished successfully.";
2886 });
2887 tasktree.start();
2888 \endcode
2889
2890 In the example above, the inter-task data consists of a QByteArray content
2891 variable [2] enclosed in a \c CopyStorage custom struct [1]. If the loader
2892 finishes successfully, it stores the data in a \c CopyStorage::content
2893 variable [5]. The saver then uses the variable to configure the saving task [6].
2894
2895 To enable a task tree to manage the \c CopyStorage struct, an instance of
2896 \l {Tasking::Storage} {Storage}<\c CopyStorage> is created [3]. If a copy of this object is
2897 inserted as the group's child item [7], an instance of the \c CopyStorage struct is
2898 created dynamically when the task tree enters this group. When the task
2899 tree leaves this group, the existing instance of the \c CopyStorage struct is
2900 destructed as it's no longer needed.
2901
2902 If several task trees holding a copy of the common
2903 \l {Tasking::Storage} {Storage}<\c CopyStorage> instance run simultaneously
2904 (including the case when the task trees are run in different threads),
2905 each task tree contains its own copy of the \c CopyStorage struct.
2906
2907 You can access \c CopyStorage from any handler in the group with a storage object.
2908 This includes all handlers of all descendant tasks of the group with
2909 a storage object. To access the custom struct in a handler, pass the
2910 copy of the \l {Tasking::Storage} {Storage}<\c CopyStorage> object to the handler
2911 (for example, in a lambda capture) [4].
2912
2913 When the task tree invokes a handler in a subtree containing the storage [7],
2914 the task tree activates its own \c CopyStorage instance inside the
2915 \l {Tasking::Storage} {Storage}<\c CopyStorage> object. Therefore, the \c CopyStorage struct
2916 may be accessed only from within the handler body. To access the currently active
2917 \c CopyStorage from within \l {Tasking::Storage} {Storage}<\c CopyStorage>, use the
2918 \l {Tasking::Storage::operator->()} {Storage::operator->()},
2919 \l {Tasking::Storage::operator*()} {Storage::operator*()}, or Storage::activeStorage() method.
2920
2921 The following list summarizes how to employ a Storage object into the task
2922 tree:
2923 \list 1
2924 \li Define the custom structure \c MyStorage with custom data [1], [2]
2925 \li Create an instance of the \l {Tasking::Storage} {Storage}<\c MyStorage> storage [3]
2926 \li Pass the \l {Tasking::Storage} {Storage}<\c MyStorage> instance to handlers [4]
2927 \li Access the \c MyStorage instance in handlers [5], [6]
2928 \li Insert the \l {Tasking::Storage} {Storage}<\c MyStorage> instance into a group [7]
2929 \endlist
2930
2931 \section1 TaskTree class
2932
2933 TaskTree executes the tree structure of asynchronous tasks according to the
2934 recipe described by the Group root element.
2935
2936 As TaskTree is also an asynchronous task, it can be a part of another TaskTree.
2937 To place a nested TaskTree inside another TaskTree, insert the TaskTreeTask
2938 element into another Group element.
2939
2940 TaskTree reports progress of completed tasks when running. The progress value
2941 is increased when a task finishes or is skipped or canceled.
2942 When TaskTree is finished and the TaskTree::done() signal is emitted,
2943 the current value of the progress equals the maximum progress value.
2944 Maximum progress equals the total number of asynchronous tasks in a tree.
2945 A nested TaskTree is counted as a single task, and its child tasks are not
2946 counted in the top level tree. Groups themselves are not counted as tasks,
2947 but their tasks are counted. \l {Tasking::Sync} {Sync} tasks are not asynchronous,
2948 so they are not counted as tasks.
2949
2950 To set additional initial data for the running tree, modify the storage
2951 instances in a tree when it creates them by installing a storage setup
2952 handler:
2953
2954 \code
2955 Storage<CopyStorage> storage;
2956 const Group root = ...; // storage placed inside root's group and inside handlers
2957 TaskTree taskTree(root);
2958 auto initStorage = [](CopyStorage &storage) {
2959 storage.content = "initial content";
2960 };
2961 taskTree.onStorageSetup(storage, initStorage);
2962 taskTree.start();
2963 \endcode
2964
2965 When the running task tree creates a \c CopyStorage instance, and before any
2966 handler inside a tree is called, the task tree calls the initStorage handler,
2967 to enable setting up initial data of the storage, unique to this particular
2968 run of taskTree.
2969
2970 Similarly, to collect some additional result data from the running tree,
2971 read it from storage instances in the tree when they are about to be
2972 destroyed. To do this, install a storage done handler:
2973
2974 \code
2975 Storage<CopyStorage> storage;
2976 const Group root = ...; // storage placed inside root's group and inside handlers
2977 TaskTree taskTree(root);
2978 auto collectStorage = [](const CopyStorage &storage) {
2979 qDebug() << "final content" << storage.content;
2980 };
2981 taskTree.onStorageDone(storage, collectStorage);
2982 taskTree.start();
2983 \endcode
2984
2985 When the running task tree is about to destroy a \c CopyStorage instance, the
2986 task tree calls the collectStorage handler, to enable reading the final data
2987 from the storage, unique to this particular run of taskTree.
2988
2989 \section1 Task Adapters
2990
2991 To extend a TaskTree with a new task type, implement a simple adapter class
2992 derived from the TaskAdapter class template. The following class is an
2993 adapter for a single shot timer, which may be considered as a new asynchronous task:
2994
2995 \code
2996 class TimerTaskAdapter : public TaskAdapter<QTimer>
2997 {
2998 public:
2999 TimerTaskAdapter() {
3000 task()->setSingleShot(true);
3001 task()->setInterval(1000);
3002 connect(task(), &QTimer::timeout, this, [this] { emit done(DoneResult::Success); });
3003 }
3004 private:
3005 void start() final { task()->start(); }
3006 };
3007
3008 using TimerTask = CustomTask<TimerTaskAdapter>;
3009 \endcode
3010
3011 You must derive the custom adapter from the TaskAdapter class template
3012 instantiated with a template parameter of the class implementing a running
3013 task. The code above uses QTimer to run the task. This class appears
3014 later as an argument to the task's handlers. The instance of this class
3015 parameter automatically becomes a member of the TaskAdapter template, and is
3016 accessible through the TaskAdapter::task() method. The constructor
3017 of \c TimerTaskAdapter initially configures the QTimer object and connects
3018 to the QTimer::timeout() signal. When the signal is triggered, \c TimerTaskAdapter
3019 emits the TaskInterface::done(DoneResult::Success) signal to inform the task tree that
3020 the task finished successfully. If it emits TaskInterface::done(DoneResult::Error),
3021 the task finished with an error.
3022 The TaskAdapter::start() method starts the timer.
3023
3024 To make QTimer accessible inside TaskTree under the \c TimerTask name,
3025 define \c TimerTask to be an alias to the CustomTask<\c TimerTaskAdapter>.
3026 \c TimerTask becomes a new custom task type, using \c TimerTaskAdapter.
3027
3028 The new task type is now registered, and you can use it in TaskTree:
3029
3030 \code
3031 const auto onSetup = [](QTimer &task) { task.setInterval(2000); };
3032 const auto onDone = [] { qDebug() << "timer triggered"; };
3033 const Group root {
3034 TimerTask(onSetup, onDone)
3035 };
3036 \endcode
3037
3038 When a task tree containing the root from the above example is started, it
3039 prints a debug message within two seconds and then finishes successfully.
3040
3041 \note The class implementing the running task should have a default constructor,
3042 and objects of this class should be freely destructible. It should be allowed
3043 to destroy a running object, preferably without waiting for the running task
3044 to finish (that is, safe non-blocking destructor of a running task).
3045 To achieve a non-blocking destruction of a task that has a blocking destructor,
3046 consider using the optional \c Deleter template parameter of the TaskAdapter.
3047*/
3048
3049/*!
3050 Constructs an empty task tree. Use setRecipe() to pass a declarative description
3051 on how the task tree should execute the tasks and how it should handle the finished tasks.
3052
3053 Starting an empty task tree is no-op and the relevant warning message is issued.
3054
3055 \sa setRecipe(), start()
3056*/
3057TaskTree::TaskTree()
3058 : d(new TaskTreePrivate(this))
3059{}
3060
3061/*!
3062 \overload
3063
3064 Constructs a task tree with a given \a recipe. After the task tree is started,
3065 it executes the tasks contained inside the \a recipe and
3066 handles finished tasks according to the passed description.
3067
3068 \sa setRecipe(), start()
3069*/
3070TaskTree::TaskTree(const Group &recipe) : TaskTree()
3071{
3072 setRecipe(recipe);
3073}
3074
3075/*!
3076 Destroys the task tree.
3077
3078 When the task tree is running while being destructed, it cancels all the running tasks
3079 immediately. In this case, no handlers are called, not even the groups' and
3080 tasks' done handlers or onStorageDone() handlers. The task tree also doesn't emit any
3081 signals from the destructor, not even done() or progressValueChanged() signals.
3082 This behavior may always be relied on.
3083 It is completely safe to destruct the running task tree.
3084
3085 It's a usual pattern to destruct the running task tree.
3086 It's guaranteed that the destruction will run quickly, without having to wait for
3087 the currently running tasks to finish, provided that the used tasks implement
3088 their destructors in a non-blocking way.
3089
3090 \note Do not call the destructor directly from any of the running task's handlers
3091 or task tree's signals. In these cases, use \l deleteLater() instead.
3092
3093 \sa cancel()
3094*/
3095TaskTree::~TaskTree()
3096{
3097 QT_ASSERT(!d->m_guard.isLocked(), qWarning("Deleting TaskTree instance directly from "
3098 "one of its handlers will lead to a crash!"));
3099 // TODO: delete storages explicitly here?
3100 delete d;
3101}
3102
3103/*!
3104 Sets a given \a recipe for the task tree. After the task tree is started,
3105 it executes the tasks contained inside the \a recipe and
3106 handles finished tasks according to the passed description.
3107
3108 \note When called for a running task tree, the call is ignored.
3109
3110 \sa TaskTree(const Tasking::Group &recipe), start()
3111*/
3112void TaskTree::setRecipe(const Group &recipe)
3113{
3114 QT_ASSERT(!isRunning(), qWarning("The TaskTree is already running, ignoring..."); return);
3115 QT_ASSERT(!d->m_guard.isLocked(), qWarning("The setRecipe() is called from one of the"
3116 "TaskTree handlers, ignoring..."); return);
3117 // TODO: Should we clear the m_storageHandlers, too?
3118 d->m_storages.clear();
3119 d->m_root.emplace(args&: d, args: recipe);
3120}
3121
3122/*!
3123 Starts the task tree.
3124
3125 Use setRecipe() or the constructor to set the declarative description according to which
3126 the task tree will execute the contained tasks and handle finished tasks.
3127
3128 When the task tree is empty, that is, constructed with a default constructor,
3129 a call to \c start() is no-op and the relevant warning message is issued.
3130
3131 Otherwise, when the task tree is already running, a call to \e start() is ignored and the
3132 relevant warning message is issued.
3133
3134 Otherwise, the task tree is started.
3135
3136 The started task tree may finish synchronously,
3137 for example when the main group's start handler returns SetupResult::StopWithError.
3138 For this reason, the connection to the done signal should be established before calling
3139 \c start(). Use isRunning() in order to detect whether the task tree is still running
3140 after a call to \c start().
3141
3142 The task tree implementation relies on the running event loop.
3143 Make sure you have a QEventLoop or QCoreApplication or one of its
3144 subclasses running (or about to be run) when calling this method.
3145
3146 \sa TaskTree(const Tasking::Group &), setRecipe(), isRunning(), cancel()
3147*/
3148void TaskTree::start()
3149{
3150 QT_ASSERT(!isRunning(), qWarning("The TaskTree is already running, ignoring..."); return);
3151 QT_ASSERT(!d->m_guard.isLocked(), qWarning("The start() is called from one of the"
3152 "TaskTree handlers, ignoring..."); return);
3153 d->start();
3154}
3155
3156/*!
3157 \fn void TaskTree::started()
3158
3159 This signal is emitted when the task tree is started. The emission of this signal is
3160 followed synchronously by the progressValueChanged() signal with an initial \c 0 value.
3161
3162 \sa start(), done()
3163*/
3164
3165/*!
3166 \fn void TaskTree::done(DoneWith result)
3167
3168 This signal is emitted when the task tree finished, passing the final \a result
3169 of the execution. The task tree neither calls any handler,
3170 nor emits any signal anymore after this signal was emitted.
3171
3172 \note Do not delete the task tree directly from this signal's handler.
3173 Use deleteLater() instead.
3174
3175 \sa started()
3176*/
3177
3178/*!
3179 Cancels the execution of the running task tree.
3180
3181 Cancels all the running tasks immediately.
3182 All running tasks finish with an error, invoking their error handlers.
3183 All running groups dispatch their handlers according to their workflow policies,
3184 invoking their done handlers. The storages' onStorageDone() handlers are invoked, too.
3185 The progressValueChanged() signals are also being sent.
3186 This behavior may always be relied on.
3187
3188 The \c cancel() function is executed synchronously, so that after a call to \c cancel()
3189 all running tasks are finished and the tree is already canceled.
3190 It's guaranteed that \c cancel() will run quickly, without any blocking wait for
3191 the currently running tasks to finish, provided the used tasks implement their destructors
3192 in a non-blocking way.
3193
3194 When the task tree is empty, that is, constructed with a default constructor,
3195 a call to \c cancel() is no-op and the relevant warning message is issued.
3196
3197 Otherwise, when the task tree wasn't started, a call to \c cancel() is ignored.
3198
3199 \note Do not call this function directly from any of the running task's handlers
3200 or task tree's signals.
3201
3202 \sa ~TaskTree()
3203*/
3204void TaskTree::cancel()
3205{
3206 QT_ASSERT(!d->m_guard.isLocked(), qWarning("The cancel() is called from one of the"
3207 "TaskTree handlers, ignoring..."); return);
3208 d->stop();
3209}
3210
3211/*!
3212 Returns \c true if the task tree is currently running; otherwise returns \c false.
3213
3214 \sa start(), cancel()
3215*/
3216bool TaskTree::isRunning() const
3217{
3218 return bool(d->m_runtimeRoot);
3219}
3220
3221/*!
3222 Executes a local event loop with QEventLoop::ExcludeUserInputEvents and starts the task tree.
3223
3224 Returns DoneWith::Success if the task tree finished successfully;
3225 otherwise returns DoneWith::Error.
3226
3227 \note Avoid using this method from the main thread. Use asynchronous start() instead.
3228 This method is to be used in non-main threads or in auto tests.
3229
3230 \sa start()
3231*/
3232DoneWith TaskTree::runBlocking()
3233{
3234 QPromise<void> dummy;
3235 dummy.start();
3236 return runBlocking(future: dummy.future());
3237}
3238
3239/*!
3240 \overload runBlocking()
3241
3242 The passed \a future is used for listening to the cancel event.
3243 When the task tree is canceled, this method cancels the passed \a future.
3244*/
3245DoneWith TaskTree::runBlocking(const QFuture<void> &future)
3246{
3247 if (future.isCanceled())
3248 return DoneWith::Cancel;
3249
3250 DoneWith doneWith = DoneWith::Cancel;
3251 QEventLoop loop;
3252 connect(sender: this, signal: &TaskTree::done, context: &loop, slot: [&loop, &doneWith](DoneWith result) {
3253 doneWith = result;
3254 // Otherwise, the tasks from inside the running tree that were deleteLater()
3255 // will be leaked. Refer to the QObject::deleteLater() docs.
3256 QMetaObject::invokeMethod(object: &loop, function: [&loop] { loop.quit(); }, type: Qt::QueuedConnection);
3257 });
3258 QFutureWatcher<void> watcher;
3259 connect(sender: &watcher, signal: &QFutureWatcherBase::canceled, context: this, slot: &TaskTree::cancel);
3260 watcher.setFuture(future);
3261
3262 QTimer::singleShot(interval: 0, receiver: this, slot: &TaskTree::start);
3263
3264 loop.exec(flags: QEventLoop::ExcludeUserInputEvents);
3265 if (doneWith == DoneWith::Cancel) {
3266 auto nonConstFuture = future;
3267 nonConstFuture.cancel();
3268 }
3269 return doneWith;
3270}
3271
3272/*!
3273 Constructs a temporary task tree using the passed \a recipe and runs it blocking.
3274
3275 The optionally provided \a timeout is used to cancel the tree automatically after
3276 \a timeout milliseconds have passed.
3277
3278 Returns DoneWith::Success if the task tree finished successfully;
3279 otherwise returns DoneWith::Error.
3280
3281 \note Avoid using this method from the main thread. Use asynchronous start() instead.
3282 This method is to be used in non-main threads or in auto tests.
3283
3284 \sa start()
3285*/
3286DoneWith TaskTree::runBlocking(const Group &recipe, milliseconds timeout)
3287{
3288 QPromise<void> dummy;
3289 dummy.start();
3290 return TaskTree::runBlocking(recipe, future: dummy.future(), timeout);
3291}
3292
3293/*!
3294 \overload runBlocking(const Group &recipe, milliseconds timeout)
3295
3296 The passed \a future is used for listening to the cancel event.
3297 When the task tree is canceled, this method cancels the passed \a future.
3298*/
3299DoneWith TaskTree::runBlocking(const Group &recipe, const QFuture<void> &future, milliseconds timeout)
3300{
3301 const Group root = timeout == milliseconds::max() ? recipe
3302 : Group { recipe.withTimeout(timeout) };
3303 TaskTree taskTree(root);
3304 return taskTree.runBlocking(future);
3305}
3306
3307/*!
3308 Returns the current real count of asynchronous chains of invocations.
3309
3310 The returned value indicates how many times the control returns to the caller's
3311 event loop while the task tree is running. Initially, this value is 0.
3312 If the execution of the task tree finishes fully synchronously, this value remains 0.
3313 If the task tree contains any asynchronous tasks that are successfully started during
3314 a call to start(), this value is bumped to 1 just before the call to start() finishes.
3315 Later, when any asynchronous task finishes and any possible continuations are started,
3316 this value is bumped again. The bumping continues until the task tree finishes.
3317 When the task tree emits the done() signal, the bumping stops.
3318 The asyncCountChanged() signal is emitted on every bump of this value.
3319
3320 \sa asyncCountChanged()
3321*/
3322int TaskTree::asyncCount() const
3323{
3324 return d->m_asyncCount;
3325}
3326
3327/*!
3328 \fn void TaskTree::asyncCountChanged(int count)
3329
3330 This signal is emitted when the running task tree is about to return control to the caller's
3331 event loop. When the task tree is started, this signal is emitted with \a count value of 0,
3332 and emitted later on every asyncCount() value bump with an updated \a count value.
3333 Every signal sent (except the initial one with the value of 0) guarantees that the task tree
3334 is still running asynchronously after the emission.
3335
3336 \sa asyncCount()
3337*/
3338
3339/*!
3340 Returns the number of asynchronous tasks contained in the stored recipe.
3341
3342 \note The returned number doesn't include \l {Tasking::Sync} {Sync} tasks.
3343 \note Any task or group that was set up using withTimeout() increases the total number of
3344 tasks by \c 1.
3345
3346 \sa setRecipe(), progressMaximum()
3347*/
3348int TaskTree::taskCount() const
3349{
3350 return d->m_root ? d->m_root->taskCount() : 0;
3351}
3352
3353/*!
3354 \fn void TaskTree::progressValueChanged(int value)
3355
3356 This signal is emitted when the running task tree finished, canceled, or skipped some tasks.
3357 The \a value gives the current total number of finished, canceled or skipped tasks.
3358 When the task tree is started, and after the started() signal was emitted,
3359 this signal is emitted with an initial \a value of \c 0.
3360 When the task tree is about to finish, and before the done() signal is emitted,
3361 this signal is emitted with the final \a value of progressMaximum().
3362
3363 \sa progressValue(), progressMaximum()
3364*/
3365
3366/*!
3367 \fn int TaskTree::progressMaximum() const
3368
3369 Returns the maximum progressValue().
3370
3371 \note Currently, it's the same as taskCount(). This might change in the future.
3372
3373 \sa progressValue()
3374*/
3375
3376/*!
3377 Returns the current progress value, which is between the \c 0 and progressMaximum().
3378
3379 The returned number indicates how many tasks have been already finished, canceled, or skipped
3380 while the task tree is running.
3381 When the task tree is started, this number is set to \c 0.
3382 When the task tree is finished, this number always equals progressMaximum().
3383
3384 \sa progressMaximum(), progressValueChanged()
3385*/
3386int TaskTree::progressValue() const
3387{
3388 return d->m_progressValue;
3389}
3390
3391/*!
3392 \fn template <typename StorageStruct, typename Handler> void TaskTree::onStorageSetup(const Storage<StorageStruct> &storage, Handler &&handler)
3393
3394 Installs a storage setup \a handler for the \a storage to pass the initial data
3395 dynamically to the running task tree.
3396
3397 The \c StorageHandler takes a \e reference to the \c StorageStruct instance:
3398
3399 \code
3400 static void save(const QString &fileName, const QByteArray &array) { ... }
3401
3402 Storage<QByteArray> storage;
3403
3404 const auto onSaverSetup = [storage](ConcurrentCall<QByteArray> &concurrent) {
3405 concurrent.setConcurrentCallData(&save, "foo.txt", *storage);
3406 };
3407
3408 const Group root {
3409 storage,
3410 ConcurrentCallTask(onSaverSetup)
3411 };
3412
3413 TaskTree taskTree(root);
3414 auto initStorage = [](QByteArray &storage){
3415 storage = "initial content";
3416 };
3417 taskTree.onStorageSetup(storage, initStorage);
3418 taskTree.start();
3419 \endcode
3420
3421 When the running task tree enters a Group where the \a storage is placed in,
3422 it creates a \c StorageStruct instance, ready to be used inside this group.
3423 Just after the \c StorageStruct instance is created, and before any handler of this group
3424 is called, the task tree invokes the passed \a handler. This enables setting up
3425 initial content for the given storage dynamically. Later, when any group's handler is invoked,
3426 the task tree activates the created and initialized storage, so that it's available inside
3427 any group's handler.
3428
3429 \sa onStorageDone()
3430*/
3431
3432/*!
3433 \fn template <typename StorageStruct, typename Handler> void TaskTree::onStorageDone(const Storage<StorageStruct> &storage, Handler &&handler)
3434
3435 Installs a storage done \a handler for the \a storage to retrieve the final data
3436 dynamically from the running task tree.
3437
3438 The \c StorageHandler takes a \c const \e reference to the \c StorageStruct instance:
3439
3440 \code
3441 static QByteArray load(const QString &fileName) { ... }
3442
3443 Storage<QByteArray> storage;
3444
3445 const auto onLoaderSetup = [](ConcurrentCall<QByteArray> &concurrent) {
3446 concurrent.setConcurrentCallData(&load, "foo.txt");
3447 };
3448 const auto onLoaderDone = [storage](const ConcurrentCall<QByteArray> &concurrent) {
3449 *storage = concurrent.result();
3450 };
3451
3452 const Group root {
3453 storage,
3454 ConcurrentCallTask(onLoaderSetup, onLoaderDone, CallDoneIf::Success)
3455 };
3456
3457 TaskTree taskTree(root);
3458 auto collectStorage = [](const QByteArray &storage){
3459 qDebug() << "final content" << storage;
3460 };
3461 taskTree.onStorageDone(storage, collectStorage);
3462 taskTree.start();
3463 \endcode
3464
3465 When the running task tree is about to leave a Group where the \a storage is placed in,
3466 it destructs a \c StorageStruct instance.
3467 Just before the \c StorageStruct instance is destructed, and after all possible handlers from
3468 this group were called, the task tree invokes the passed \a handler. This enables reading
3469 the final content of the given storage dynamically and processing it further outside of
3470 the task tree.
3471
3472 This handler is called also when the running tree is canceled. However, it's not called
3473 when the running tree is destructed.
3474
3475 \sa onStorageSetup()
3476*/
3477
3478void TaskTree::setupStorageHandler(const StorageBase &storage,
3479 StorageBase::StorageHandler setupHandler,
3480 StorageBase::StorageHandler doneHandler)
3481{
3482 auto it = d->m_storageHandlers.find(key: storage);
3483 if (it == d->m_storageHandlers.end()) {
3484 d->m_storageHandlers.insert(key: storage, value: {.m_setupHandler: setupHandler, .m_doneHandler: doneHandler});
3485 return;
3486 }
3487 if (setupHandler) {
3488 QT_ASSERT(!it->m_setupHandler,
3489 qWarning("The storage has its setup handler defined, overriding..."));
3490 it->m_setupHandler = setupHandler;
3491 }
3492 if (doneHandler) {
3493 QT_ASSERT(!it->m_doneHandler,
3494 qWarning("The storage has its done handler defined, overriding..."));
3495 it->m_doneHandler = doneHandler;
3496 }
3497}
3498
3499TaskTreeTaskAdapter::TaskTreeTaskAdapter()
3500{
3501 connect(sender: task(), signal: &TaskTree::done, context: this,
3502 slot: [this](DoneWith result) { emit done(result: toDoneResult(doneWith: result)); });
3503}
3504
3505void TaskTreeTaskAdapter::start()
3506{
3507 task()->start();
3508}
3509
3510using TimeoutCallback = std::function<void()>;
3511
3512struct TimerData
3513{
3514 system_clock::time_point m_deadline;
3515 QPointer<QObject> m_context;
3516 TimeoutCallback m_callback;
3517};
3518
3519struct TimerThreadData
3520{
3521 Q_DISABLE_COPY_MOVE(TimerThreadData)
3522
3523 TimerThreadData() = default; // defult constructor is required for initializing with {} since C++20 by Mingw 11.20
3524 QHash<int, TimerData> m_timerIdToTimerData = {};
3525 QMap<system_clock::time_point, QList<int>> m_deadlineToTimerId = {};
3526 int m_timerIdCounter = 0;
3527};
3528
3529// Please note the thread_local keyword below guarantees a separate instance per thread.
3530static thread_local TimerThreadData s_threadTimerData = {};
3531
3532static void removeTimerId(int timerId)
3533{
3534 const auto it = s_threadTimerData.m_timerIdToTimerData.constFind(key: timerId);
3535 QT_ASSERT(it != s_threadTimerData.m_timerIdToTimerData.cend(),
3536 qWarning("Removing active timerId failed."); return);
3537
3538 const system_clock::time_point deadline = it->m_deadline;
3539 s_threadTimerData.m_timerIdToTimerData.erase(it);
3540
3541 QList<int> &ids = s_threadTimerData.m_deadlineToTimerId[deadline];
3542 const int removedCount = ids.removeAll(t: timerId);
3543 QT_ASSERT(removedCount == 1, qWarning("Removing active timerId failed."); return);
3544 if (ids.isEmpty())
3545 s_threadTimerData.m_deadlineToTimerId.remove(key: deadline);
3546}
3547
3548static void handleTimeout(int timerId)
3549{
3550 const auto itData = s_threadTimerData.m_timerIdToTimerData.constFind(key: timerId);
3551 if (itData == s_threadTimerData.m_timerIdToTimerData.cend())
3552 return; // The timer was already activated.
3553
3554 const auto deadline = itData->m_deadline;
3555 while (true) {
3556 auto itMap = s_threadTimerData.m_deadlineToTimerId.begin();
3557 if (itMap == s_threadTimerData.m_deadlineToTimerId.end())
3558 return;
3559
3560 if (itMap.key() > deadline)
3561 return;
3562
3563 std::optional<TimerData> timerData;
3564 QList<int> &idList = *itMap;
3565 if (!idList.isEmpty()) {
3566 const int first = idList.first();
3567 idList.removeFirst();
3568
3569 const auto it = s_threadTimerData.m_timerIdToTimerData.constFind(key: first);
3570 if (it != s_threadTimerData.m_timerIdToTimerData.cend()) {
3571 timerData = it.value();
3572 s_threadTimerData.m_timerIdToTimerData.erase(it);
3573 } else {
3574 QT_CHECK(false);
3575 }
3576 } else {
3577 QT_CHECK(false);
3578 }
3579
3580 if (idList.isEmpty())
3581 s_threadTimerData.m_deadlineToTimerId.erase(it: itMap);
3582 if (timerData && timerData->m_context)
3583 timerData->m_callback();
3584 }
3585}
3586
3587static int scheduleTimeout(milliseconds timeout, QObject *context, const TimeoutCallback &callback)
3588{
3589 const int timerId = ++s_threadTimerData.m_timerIdCounter;
3590 const system_clock::time_point deadline = system_clock::now() + timeout;
3591 QTimer::singleShot(interval: timeout, receiver: context, slot: [timerId] { handleTimeout(timerId); });
3592 s_threadTimerData.m_timerIdToTimerData.emplace(key: timerId, args: TimerData{.m_deadline: deadline, .m_context: context, .m_callback: callback});
3593 s_threadTimerData.m_deadlineToTimerId[deadline].append(t: timerId);
3594 return timerId;
3595}
3596
3597TimeoutTaskAdapter::TimeoutTaskAdapter()
3598{
3599 *task() = milliseconds::zero();
3600}
3601
3602TimeoutTaskAdapter::~TimeoutTaskAdapter()
3603{
3604 if (m_timerId)
3605 removeTimerId(timerId: *m_timerId);
3606}
3607
3608void TimeoutTaskAdapter::start()
3609{
3610 m_timerId = scheduleTimeout(timeout: *task(), context: this, callback: [this] {
3611 m_timerId.reset();
3612 emit done(result: DoneResult::Success);
3613 });
3614}
3615
3616/*!
3617 \typealias Tasking::TaskTreeTask
3618
3619 Type alias for the CustomTask, to be used inside recipes, associated with the TaskTree task.
3620*/
3621
3622/*!
3623 \typealias Tasking::TimeoutTask
3624
3625 Type alias for the CustomTask, to be used inside recipes, associated with the
3626 \c std::chrono::milliseconds type. \c std::chrono::milliseconds is used to set up the
3627 timeout duration. The default timeout is \c std::chrono::milliseconds::zero(), that is,
3628 the TimeoutTask finishes as soon as the control returns to the running event loop.
3629
3630 Example usage:
3631
3632 \code
3633 using namespace std::chrono;
3634 using namespace std::chrono_literals;
3635
3636 const auto onSetup = [](milliseconds &timeout) { timeout = 1000ms; }
3637 const auto onDone = [] { qDebug() << "Timed out."; }
3638
3639 const Group root {
3640 Timeout(onSetup, onDone)
3641 };
3642 \endcode
3643*/
3644
3645} // namespace Tasking
3646
3647QT_END_NAMESPACE
3648

source code of qtbase/src/assets/downloader/tasking/tasktree.cpp