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 TaskTreePrivate(TaskTree *taskTree)
1812 : q(taskTree) {}
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 std::optional<Loop> loop() const;
1910 void deleteChild(RuntimeTask *node);
1911
1912 const int m_iterationIndex = 0;
1913 const bool m_isProgressive = true;
1914 RuntimeContainer *m_container = nullptr;
1915 int m_doneCount = 0;
1916 std::vector<std::unique_ptr<RuntimeTask>> m_children = {}; // Owning.
1917};
1918
1919class RuntimeContainer
1920{
1921 Q_DISABLE_COPY(RuntimeContainer)
1922
1923public:
1924 RuntimeContainer(const ContainerNode &taskContainer, RuntimeTask *parentTask)
1925 : m_containerNode(taskContainer)
1926 , m_parentTask(parentTask)
1927 , m_storages(createStorages(container: taskContainer))
1928 , m_successBit(initialSuccessBit(workflowPolicy: taskContainer.m_workflowPolicy))
1929 , m_shouldIterate(taskContainer.m_loop)
1930 {}
1931
1932 ~RuntimeContainer()
1933 {
1934 for (int i = m_containerNode.m_storageList.size() - 1; i >= 0; --i) { // iterate in reverse order
1935 const StorageBase storage = m_containerNode.m_storageList[i];
1936 StoragePtr storagePtr = m_storages.value(i);
1937 if (m_callStorageDoneHandlersOnDestruction)
1938 m_containerNode.m_taskTreePrivate->callDoneHandler(storage, storagePtr);
1939 storage.m_storageData->m_destructor(storagePtr);
1940 }
1941 }
1942
1943 static QList<StoragePtr> createStorages(const ContainerNode &container);
1944 bool isStarting() const { return m_startGuard.isLocked(); }
1945 RuntimeIteration *parentIteration() const;
1946 bool updateSuccessBit(bool success);
1947 void deleteFinishedIterations();
1948 int progressiveLoopCount() const
1949 {
1950 return m_containerNode.m_taskTreePrivate->effectiveLoopCount(loop: m_containerNode.m_loop);
1951 }
1952
1953 const ContainerNode &m_containerNode; // Not owning.
1954 RuntimeTask *m_parentTask = nullptr; // Not owning.
1955 const QList<StoragePtr> m_storages; // Owning.
1956
1957 bool m_successBit = true;
1958 bool m_callStorageDoneHandlersOnDestruction = false;
1959 Guard m_startGuard;
1960
1961 int m_iterationCount = 0;
1962 int m_nextToStart = 0;
1963 int m_runningChildren = 0;
1964 bool m_shouldIterate = true;
1965 std::vector<std::unique_ptr<RuntimeIteration>> m_iterations; // Owning.
1966};
1967
1968class RuntimeTask
1969{
1970public:
1971 ~RuntimeTask()
1972 {
1973 if (m_task) {
1974 // Ensures the running task's d'tor doesn't emit done() signal. QTCREATORBUG-30204.
1975 QObject::disconnect(sender: m_task.get(), signal: &TaskInterface::done,
1976 receiver: m_taskNode.m_container.m_taskTreePrivate->q, zero: nullptr);
1977 }
1978 }
1979
1980 const TaskNode &m_taskNode; // Not owning.
1981 RuntimeIteration *m_parentIteration = nullptr; // Not owning.
1982 std::optional<RuntimeContainer> m_container = {}; // Owning.
1983 std::unique_ptr<TaskInterface> m_task = {}; // Owning.
1984};
1985
1986static bool isProgressive(RuntimeContainer *container)
1987{
1988 RuntimeIteration *iteration = container->m_parentTask->m_parentIteration;
1989 return iteration ? iteration->m_isProgressive : true;
1990}
1991
1992void ExecutionContextActivator::activateTaskTree(RuntimeIteration *iteration)
1993{
1994 activateTaskTree(container: iteration->m_container);
1995}
1996
1997void ExecutionContextActivator::activateTaskTree(RuntimeContainer *container)
1998{
1999 s_activeTaskTrees.push_back(t: container->m_containerNode.m_taskTreePrivate->q);
2000}
2001
2002void ExecutionContextActivator::activateContext(RuntimeIteration *iteration)
2003{
2004 std::optional<Loop> loop = iteration->loop();
2005 if (loop) {
2006 loop->m_loopData->threadData().pushIteration(index: iteration->m_iterationIndex);
2007 m_activeLoops.append(t: *loop);
2008 }
2009 activateContext(container: iteration->m_container);
2010}
2011
2012void ExecutionContextActivator::activateContext(RuntimeContainer *container)
2013{
2014 const ContainerNode &containerNode = container->m_containerNode;
2015 for (int i = 0; i < containerNode.m_storageList.size(); ++i) {
2016 const StorageBase &storage = containerNode.m_storageList[i];
2017 if (m_activeStorages.contains(t: storage))
2018 continue; // Storage shadowing: The storage is already active, skipping it...
2019 m_activeStorages.append(t: storage);
2020 storage.m_storageData->threadData().pushStorage(storagePtr: container->m_storages.value(i));
2021 }
2022 // Go to the parent after activating this storages so that storage shadowing works
2023 // in the direction from child to parent root.
2024 if (container->parentIteration())
2025 activateContext(iteration: container->parentIteration());
2026}
2027
2028void TaskTreePrivate::start()
2029{
2030 QT_ASSERT(m_root, return);
2031 QT_ASSERT(!m_runtimeRoot, return);
2032 m_asyncCount = 0;
2033 m_progressValue = 0;
2034 {
2035 GuardLocker locker(m_guard);
2036 emit q->started();
2037 emit q->asyncCountChanged(count: m_asyncCount);
2038 emit q->progressValueChanged(value: m_progressValue);
2039 }
2040 // TODO: check storage handlers for not existing storages in tree
2041 for (auto it = m_storageHandlers.cbegin(); it != m_storageHandlers.cend(); ++it) {
2042 QT_ASSERT(m_storages.contains(it.key()), qWarning("The registered storage doesn't "
2043 "exist in task tree. Its handlers will never be called."));
2044 }
2045 m_runtimeRoot.reset(p: new RuntimeTask{.m_taskNode: *m_root});
2046 start(node: m_runtimeRoot.get());
2047 bumpAsyncCount();
2048}
2049
2050void TaskTreePrivate::stop()
2051{
2052 QT_ASSERT(m_root, return);
2053 if (!m_runtimeRoot)
2054 return;
2055 stop(node: m_runtimeRoot.get());
2056 m_runtimeRoot.reset();
2057 emitDone(result: DoneWith::Cancel);
2058}
2059
2060void TaskTreePrivate::bumpAsyncCount()
2061{
2062 if (!m_runtimeRoot)
2063 return;
2064 ++m_asyncCount;
2065 GuardLocker locker(m_guard);
2066 emit q->asyncCountChanged(count: m_asyncCount);
2067}
2068
2069void TaskTreePrivate::advanceProgress(int byValue)
2070{
2071 if (byValue == 0)
2072 return;
2073 QT_CHECK(byValue > 0);
2074 QT_CHECK(m_progressValue + byValue <= m_root->taskCount());
2075 m_progressValue += byValue;
2076 GuardLocker locker(m_guard);
2077 emit q->progressValueChanged(value: m_progressValue);
2078}
2079
2080void TaskTreePrivate::emitDone(DoneWith result)
2081{
2082 QT_CHECK(m_progressValue == m_root->taskCount());
2083 GuardLocker locker(m_guard);
2084 emit q->done(result);
2085}
2086
2087RuntimeIteration::RuntimeIteration(int index, RuntimeContainer *container)
2088 : m_iterationIndex(index)
2089 , m_isProgressive(index < container->progressiveLoopCount() && isProgressive(container))
2090 , m_container(container)
2091{}
2092
2093std::optional<Loop> RuntimeIteration::loop() const
2094{
2095 return m_container->m_containerNode.m_loop;
2096}
2097
2098void RuntimeIteration::deleteChild(RuntimeTask *task)
2099{
2100 const auto it = std::find_if(first: m_children.cbegin(), last: m_children.cend(), pred: [task](const auto &ptr) {
2101 return ptr.get() == task;
2102 });
2103 if (it != m_children.cend())
2104 m_children.erase(position: it);
2105}
2106
2107static std::vector<TaskNode> createChildren(TaskTreePrivate *taskTreePrivate,
2108 const QList<GroupItem> &children)
2109{
2110 std::vector<TaskNode> result;
2111 result.reserve(n: children.size());
2112 for (const GroupItem &child : children)
2113 result.emplace_back(args&: taskTreePrivate, args: child);
2114 return result;
2115}
2116
2117ContainerNode::ContainerNode(TaskTreePrivate *taskTreePrivate, const GroupItem &task)
2118 : m_taskTreePrivate(taskTreePrivate)
2119 , m_groupHandler(task.m_groupData.m_groupHandler)
2120 , m_parallelLimit(task.m_groupData.m_parallelLimit.value_or(u: 1))
2121 , m_workflowPolicy(task.m_groupData.m_workflowPolicy.value_or(u: WorkflowPolicy::StopOnError))
2122 , m_loop(task.m_groupData.m_loop)
2123 , m_storageList(task.m_storageList)
2124 , m_children(createChildren(taskTreePrivate, children: task.m_children))
2125 , m_taskCount(std::accumulate(first: m_children.cbegin(), last: m_children.cend(), init: 0,
2126 binary_op: [](int r, const TaskNode &n) { return r + n.taskCount(); })
2127 * taskTreePrivate->effectiveLoopCount(loop: m_loop))
2128{
2129 for (const StorageBase &storage : m_storageList)
2130 m_taskTreePrivate->m_storages << storage;
2131}
2132
2133QList<StoragePtr> RuntimeContainer::createStorages(const ContainerNode &container)
2134{
2135 QList<StoragePtr> storages;
2136 for (const StorageBase &storage : container.m_storageList) {
2137 StoragePtr storagePtr = storage.m_storageData->m_constructor();
2138 storages.append(t: storagePtr);
2139 container.m_taskTreePrivate->callSetupHandler(storage, storagePtr);
2140 }
2141 return storages;
2142}
2143
2144RuntimeIteration *RuntimeContainer::parentIteration() const
2145{
2146 return m_parentTask->m_parentIteration;
2147}
2148
2149bool RuntimeContainer::updateSuccessBit(bool success)
2150{
2151 if (m_containerNode.m_workflowPolicy == WorkflowPolicy::FinishAllAndSuccess
2152 || m_containerNode.m_workflowPolicy == WorkflowPolicy::FinishAllAndError
2153 || m_containerNode.m_workflowPolicy == WorkflowPolicy::StopOnSuccessOrError) {
2154 if (m_containerNode.m_workflowPolicy == WorkflowPolicy::StopOnSuccessOrError)
2155 m_successBit = success;
2156 return m_successBit;
2157 }
2158
2159 const bool donePolicy = m_containerNode.m_workflowPolicy == WorkflowPolicy::StopOnSuccess
2160 || m_containerNode.m_workflowPolicy == WorkflowPolicy::ContinueOnSuccess;
2161 m_successBit = donePolicy ? (m_successBit || success) : (m_successBit && success);
2162 return m_successBit;
2163}
2164
2165void RuntimeContainer::deleteFinishedIterations()
2166{
2167 for (auto it = m_iterations.cbegin(); it != m_iterations.cend(); ) {
2168 if (it->get()->m_doneCount == int(m_containerNode.m_children.size()))
2169 it = m_iterations.erase(position: it);
2170 else
2171 ++it;
2172 }
2173}
2174
2175SetupResult TaskTreePrivate::start(RuntimeContainer *container)
2176{
2177 const ContainerNode &containerNode = container->m_containerNode;
2178 SetupResult startAction = SetupResult::Continue;
2179 if (containerNode.m_groupHandler.m_setupHandler) {
2180 startAction = invokeHandler(container, handler: containerNode.m_groupHandler.m_setupHandler);
2181 if (startAction != SetupResult::Continue) {
2182 if (isProgressive(container))
2183 advanceProgress(byValue: containerNode.m_taskCount);
2184 // Non-Continue SetupResult takes precedence over the workflow policy.
2185 container->m_successBit = startAction == SetupResult::StopWithSuccess;
2186 }
2187 }
2188 return continueStart(container, startAction);
2189}
2190
2191SetupResult TaskTreePrivate::continueStart(RuntimeContainer *container, SetupResult startAction)
2192{
2193 const SetupResult groupAction = startAction == SetupResult::Continue ? startChildren(container)
2194 : startAction;
2195 if (groupAction == SetupResult::Continue)
2196 return groupAction;
2197
2198 const bool bit = container->updateSuccessBit(success: groupAction == SetupResult::StopWithSuccess);
2199 RuntimeIteration *parentIteration = container->parentIteration();
2200 RuntimeTask *parentTask = container->m_parentTask;
2201 QT_CHECK(parentTask);
2202 const bool result = invokeDoneHandler(container, doneWith: bit ? DoneWith::Success : DoneWith::Error);
2203 if (parentIteration) {
2204 parentIteration->deleteChild(task: parentTask);
2205 if (!parentIteration->m_container->isStarting())
2206 childDone(iteration: parentIteration, success: result);
2207 } else {
2208 QT_CHECK(m_runtimeRoot.get() == parentTask);
2209 m_runtimeRoot.reset();
2210 emitDone(result: result ? DoneWith::Success : DoneWith::Error);
2211 }
2212 return toSetupResult(success: result);
2213}
2214
2215SetupResult TaskTreePrivate::startChildren(RuntimeContainer *container)
2216{
2217 const ContainerNode &containerNode = container->m_containerNode;
2218 const int childCount = int(containerNode.m_children.size());
2219
2220 if (container->m_iterationCount == 0) {
2221 if (container->m_shouldIterate && !invokeLoopHandler(container)) {
2222 if (isProgressive(container))
2223 advanceProgress(byValue: containerNode.m_taskCount);
2224 return toSetupResult(success: container->m_successBit);
2225 }
2226 container->m_iterations.emplace_back(
2227 args: std::make_unique<RuntimeIteration>(args&: container->m_iterationCount, args&: container));
2228 ++container->m_iterationCount;
2229 }
2230
2231 GuardLocker locker(container->m_startGuard);
2232
2233 while (containerNode.m_parallelLimit == 0
2234 || container->m_runningChildren < containerNode.m_parallelLimit) {
2235 container->deleteFinishedIterations();
2236 if (container->m_nextToStart == childCount) {
2237 if (invokeLoopHandler(container)) {
2238 container->m_nextToStart = 0;
2239 container->m_iterations.emplace_back(
2240 args: std::make_unique<RuntimeIteration>(args&: container->m_iterationCount, args&: container));
2241 ++container->m_iterationCount;
2242 } else if (container->m_iterations.empty()) {
2243 return toSetupResult(success: container->m_successBit);
2244 } else {
2245 return SetupResult::Continue;
2246 }
2247 }
2248 if (containerNode.m_children.size() == 0) // Empty loop body.
2249 continue;
2250
2251 RuntimeIteration *iteration = container->m_iterations.back().get();
2252 RuntimeTask *newTask = new RuntimeTask{.m_taskNode: containerNode.m_children.at(n: container->m_nextToStart),
2253 .m_parentIteration: iteration};
2254 iteration->m_children.emplace_back(args&: newTask);
2255 ++container->m_runningChildren;
2256 ++container->m_nextToStart;
2257
2258 const SetupResult startAction = start(node: newTask);
2259 if (startAction == SetupResult::Continue)
2260 continue;
2261
2262 const SetupResult finalizeAction = childDone(iteration,
2263 success: startAction == SetupResult::StopWithSuccess);
2264 if (finalizeAction != SetupResult::Continue)
2265 return finalizeAction;
2266 }
2267 return SetupResult::Continue;
2268}
2269
2270SetupResult TaskTreePrivate::childDone(RuntimeIteration *iteration, bool success)
2271{
2272 RuntimeContainer *container = iteration->m_container;
2273 const WorkflowPolicy &workflowPolicy = container->m_containerNode.m_workflowPolicy;
2274 const bool shouldStop = workflowPolicy == WorkflowPolicy::StopOnSuccessOrError
2275 || (workflowPolicy == WorkflowPolicy::StopOnSuccess && success)
2276 || (workflowPolicy == WorkflowPolicy::StopOnError && !success);
2277 ++iteration->m_doneCount;
2278 --container->m_runningChildren;
2279 if (shouldStop)
2280 stop(container);
2281
2282 const bool updatedSuccess = container->updateSuccessBit(success);
2283 const SetupResult startAction = shouldStop ? toSetupResult(success: updatedSuccess)
2284 : SetupResult::Continue;
2285
2286 if (container->isStarting())
2287 return startAction;
2288 return continueStart(container, startAction);
2289}
2290
2291void TaskTreePrivate::stop(RuntimeContainer *container)
2292{
2293 const ContainerNode &containerNode = container->m_containerNode;
2294 for (auto &iteration : container->m_iterations) {
2295 for (auto &child : iteration->m_children) {
2296 ++iteration->m_doneCount;
2297 stop(node: child.get());
2298 }
2299
2300 if (iteration->m_isProgressive) {
2301 int skippedTaskCount = 0;
2302 for (int i = iteration->m_doneCount; i < int(containerNode.m_children.size()); ++i)
2303 skippedTaskCount += containerNode.m_children.at(n: i).taskCount();
2304 advanceProgress(byValue: skippedTaskCount);
2305 }
2306 }
2307 const int skippedIterations = container->progressiveLoopCount() - container->m_iterationCount;
2308 if (skippedIterations > 0) {
2309 advanceProgress(byValue: container->m_containerNode.m_taskCount / container->progressiveLoopCount()
2310 * skippedIterations);
2311 }
2312}
2313
2314static bool shouldCall(CallDoneIf callDoneIf, DoneWith result)
2315{
2316 if (result == DoneWith::Success)
2317 return callDoneIf != CallDoneIf::Error;
2318 return callDoneIf != CallDoneIf::Success;
2319}
2320
2321bool TaskTreePrivate::invokeDoneHandler(RuntimeContainer *container, DoneWith doneWith)
2322{
2323 DoneResult result = toDoneResult(doneWith);
2324 const GroupItem::GroupHandler &groupHandler = container->m_containerNode.m_groupHandler;
2325 if (groupHandler.m_doneHandler && shouldCall(callDoneIf: groupHandler.m_callDoneIf, result: doneWith))
2326 result = invokeHandler(container, handler: groupHandler.m_doneHandler, args&: doneWith);
2327 container->m_callStorageDoneHandlersOnDestruction = true;
2328 // TODO: is it needed?
2329 container->m_parentTask->m_container.reset();
2330 return result == DoneResult::Success;
2331}
2332
2333bool TaskTreePrivate::invokeLoopHandler(RuntimeContainer *container)
2334{
2335 if (container->m_shouldIterate) {
2336 const LoopData *loopData = container->m_containerNode.m_loop->m_loopData.get();
2337 if (loopData->m_loopCount) {
2338 container->m_shouldIterate = container->m_iterationCount < loopData->m_loopCount;
2339 } else if (loopData->m_condition) {
2340 container->m_shouldIterate = invokeHandler(container, handler: loopData->m_condition,
2341 args&: container->m_iterationCount);
2342 }
2343 }
2344 return container->m_shouldIterate;
2345}
2346
2347SetupResult TaskTreePrivate::start(RuntimeTask *node)
2348{
2349 if (!node->m_taskNode.isTask()) {
2350 node->m_container.emplace(args: node->m_taskNode.m_container, args&: node);
2351 return start(container: &*node->m_container);
2352 }
2353
2354 const GroupItem::TaskHandler &handler = node->m_taskNode.m_taskHandler;
2355 node->m_task.reset(p: handler.m_createHandler());
2356 const SetupResult startAction = handler.m_setupHandler
2357 ? invokeHandler(container: node->m_parentIteration, handler: handler.m_setupHandler, args&: *node->m_task.get())
2358 : SetupResult::Continue;
2359 if (startAction != SetupResult::Continue) {
2360 if (node->m_parentIteration->m_isProgressive)
2361 advanceProgress(byValue: 1);
2362 node->m_parentIteration->deleteChild(task: node);
2363 return startAction;
2364 }
2365 const std::shared_ptr<SetupResult> unwindAction
2366 = std::make_shared<SetupResult>(args: SetupResult::Continue);
2367 QObject::connect(sender: node->m_task.get(), signal: &TaskInterface::done,
2368 context: q, slot: [this, node, unwindAction](DoneResult doneResult) {
2369 const bool result = invokeDoneHandler(node, doneWith: toDoneWith(result: doneResult));
2370 QObject::disconnect(sender: node->m_task.get(), signal: &TaskInterface::done, receiver: q, zero: nullptr);
2371 node->m_task.release()->deleteLater();
2372 RuntimeIteration *parentIteration = node->m_parentIteration;
2373 parentIteration->deleteChild(task: node);
2374 if (parentIteration->m_container->isStarting()) {
2375 *unwindAction = toSetupResult(success: result);
2376 } else {
2377 childDone(iteration: parentIteration, success: result);
2378 bumpAsyncCount();
2379 }
2380 });
2381
2382 node->m_task->start();
2383 return *unwindAction;
2384}
2385
2386void TaskTreePrivate::stop(RuntimeTask *node)
2387{
2388 if (!node->m_task) {
2389 if (!node->m_container)
2390 return;
2391 stop(container: &*node->m_container);
2392 node->m_container->updateSuccessBit(success: false);
2393 invokeDoneHandler(container: &*node->m_container, doneWith: DoneWith::Cancel);
2394 return;
2395 }
2396
2397 invokeDoneHandler(node, doneWith: DoneWith::Cancel);
2398 node->m_task.reset();
2399}
2400
2401bool TaskTreePrivate::invokeDoneHandler(RuntimeTask *node, DoneWith doneWith)
2402{
2403 DoneResult result = toDoneResult(doneWith);
2404 const GroupItem::TaskHandler &handler = node->m_taskNode.m_taskHandler;
2405 if (handler.m_doneHandler && shouldCall(callDoneIf: handler.m_callDoneIf, result: doneWith)) {
2406 result = invokeHandler(container: node->m_parentIteration,
2407 handler: handler.m_doneHandler, args&: *node->m_task.get(), args&: doneWith);
2408 }
2409 if (node->m_parentIteration->m_isProgressive)
2410 advanceProgress(byValue: 1);
2411 return result == DoneResult::Success;
2412}
2413
2414/*!
2415 \class Tasking::TaskTree
2416 \inheaderfile solutions/tasking/tasktree.h
2417 \inmodule TaskingSolution
2418 \brief The TaskTree class runs an async task tree structure defined in a declarative way.
2419 \reentrant
2420
2421 Use the Tasking namespace to build extensible, declarative task tree
2422 structures that contain possibly asynchronous tasks, such as QProcess,
2423 NetworkQuery, or ConcurrentCall<ReturnType>. TaskTree structures enable you
2424 to create a sophisticated mixture of a parallel or sequential flow of tasks
2425 in the form of a tree and to run it any time later.
2426
2427 \section1 Root Element and Tasks
2428
2429 The TaskTree has a mandatory Group root element, which may contain
2430 any number of tasks of various types, such as QProcessTask, NetworkQueryTask,
2431 or ConcurrentCallTask<ReturnType>:
2432
2433 \code
2434 using namespace Tasking;
2435
2436 const Group root {
2437 QProcessTask(...),
2438 NetworkQueryTask(...),
2439 ConcurrentCallTask<int>(...)
2440 };
2441
2442 TaskTree *taskTree = new TaskTree(root);
2443 connect(taskTree, &TaskTree::done, ...); // finish handler
2444 taskTree->start();
2445 \endcode
2446
2447 The task tree above has a top level element of the Group type that contains
2448 tasks of the QProcessTask, NetworkQueryTask, and ConcurrentCallTask<int> type.
2449 After taskTree->start() is called, the tasks are run in a chain, starting
2450 with QProcessTask. When the QProcessTask finishes successfully, the NetworkQueryTask
2451 task is started. Finally, when the network task finishes successfully, the
2452 ConcurrentCallTask<int> task is started.
2453
2454 When the last running task finishes with success, the task tree is considered
2455 to have run successfully and the done() signal is emitted with DoneWith::Success.
2456 When a task finishes with an error, the execution of the task tree is stopped
2457 and the remaining tasks are skipped. The task tree finishes with an error and
2458 sends the TaskTree::done() signal with DoneWith::Error.
2459
2460 \section1 Groups
2461
2462 The parent of the Group sees it as a single task. Like other tasks,
2463 the group can be started and it can finish with success or an error.
2464 The Group elements can be nested to create a tree structure:
2465
2466 \code
2467 const Group root {
2468 Group {
2469 parallel,
2470 QProcessTask(...),
2471 ConcurrentCallTask<int>(...)
2472 },
2473 NetworkQueryTask(...)
2474 };
2475 \endcode
2476
2477 The example above differs from the first example in that the root element has
2478 a subgroup that contains the QProcessTask and ConcurrentCallTask<int>. The subgroup is a
2479 sibling element of the NetworkQueryTask in the root. The subgroup contains an
2480 additional \e parallel element that instructs its Group to execute its tasks
2481 in parallel.
2482
2483 So, when the tree above is started, the QProcessTask and ConcurrentCallTask<int> start
2484 immediately and run in parallel. Since the root group doesn't contain a
2485 \e parallel element, its direct child tasks are run in sequence. Thus, the
2486 NetworkQueryTask starts when the whole subgroup finishes. The group is
2487 considered as finished when all its tasks have finished. The order in which
2488 the tasks finish is not relevant.
2489
2490 So, depending on which task lasts longer (QProcessTask or ConcurrentCallTask<int>), the
2491 following scenarios can take place:
2492
2493 \table
2494 \header
2495 \li Scenario 1
2496 \li Scenario 2
2497 \row
2498 \li Root Group starts
2499 \li Root Group starts
2500 \row
2501 \li Sub Group starts
2502 \li Sub Group starts
2503 \row
2504 \li QProcessTask starts
2505 \li QProcessTask starts
2506 \row
2507 \li ConcurrentCallTask<int> starts
2508 \li ConcurrentCallTask<int> starts
2509 \row
2510 \li ...
2511 \li ...
2512 \row
2513 \li \b {QProcessTask finishes}
2514 \li \b {ConcurrentCallTask<int> finishes}
2515 \row
2516 \li ...
2517 \li ...
2518 \row
2519 \li \b {ConcurrentCallTask<int> finishes}
2520 \li \b {QProcessTask finishes}
2521 \row
2522 \li Sub Group finishes
2523 \li Sub Group finishes
2524 \row
2525 \li NetworkQueryTask starts
2526 \li NetworkQueryTask starts
2527 \row
2528 \li ...
2529 \li ...
2530 \row
2531 \li NetworkQueryTask finishes
2532 \li NetworkQueryTask finishes
2533 \row
2534 \li Root Group finishes
2535 \li Root Group finishes
2536 \endtable
2537
2538 The differences between the scenarios are marked with bold. Three dots mean
2539 that an unspecified amount of time passes between previous and next events
2540 (a task or tasks continue to run). No dots between events
2541 means that they occur synchronously.
2542
2543 The presented scenarios assume that all tasks run successfully. If a task
2544 fails during execution, the task tree finishes with an error. In particular,
2545 when QProcessTask finishes with an error while ConcurrentCallTask<int> is still being executed,
2546 the ConcurrentCallTask<int> is automatically canceled, the subgroup finishes with an error,
2547 the NetworkQueryTask is skipped, and the tree finishes with an error.
2548
2549 \section1 Task Types
2550
2551 Each task type is associated with its corresponding task class that executes
2552 the task. For example, a QProcessTask inside a task tree is associated with
2553 the QProcess class that executes the process. The associated objects are
2554 automatically created, started, and destructed exclusively by the task tree
2555 at the appropriate time.
2556
2557 If a root group consists of five sequential QProcessTask tasks, and the task tree
2558 executes the group, it creates an instance of QProcess for the first
2559 QProcessTask and starts it. If the QProcess instance finishes successfully,
2560 the task tree destructs it and creates a new QProcess instance for the
2561 second QProcessTask, and so on. If the first task finishes with an error, the task
2562 tree stops creating QProcess instances, and the root group finishes with an
2563 error.
2564
2565 The following table shows examples of task types and their corresponding task
2566 classes:
2567
2568 \table
2569 \header
2570 \li Task Type (Tasking Namespace)
2571 \li Associated Task Class
2572 \li Brief Description
2573 \row
2574 \li QProcessTask
2575 \li QProcess
2576 \li Starts process.
2577 \row
2578 \li ConcurrentCallTask<ReturnType>
2579 \li Tasking::ConcurrentCall<ReturnType>
2580 \li Starts asynchronous task, runs in separate thread.
2581 \row
2582 \li TaskTreeTask
2583 \li Tasking::TaskTree
2584 \li Starts nested task tree.
2585 \row
2586 \li NetworkQueryTask
2587 \li NetworkQuery
2588 \li Starts network download.
2589 \endtable
2590
2591 \section1 Task Handlers
2592
2593 Use Task handlers to set up a task for execution and to enable reading
2594 the output data from the task when it finishes with success or an error.
2595
2596 \section2 Task's Start Handler
2597
2598 When a corresponding task class object is created and before it's started,
2599 the task tree invokes an optionally user-provided setup handler. The setup
2600 handler should always take a \e reference to the associated task class object:
2601
2602 \code
2603 const auto onSetup = [](QProcess &process) {
2604 process.setCommand({"sleep", {"3"}});
2605 };
2606 const Group root {
2607 QProcessTask(onSetup)
2608 };
2609 \endcode
2610
2611 You can modify the passed QProcess in the setup handler, so that the task
2612 tree can start the process according to your configuration.
2613 You should not call \c {process.start();} in the setup handler,
2614 as the task tree calls it when needed. The setup handler is optional. When used,
2615 it must be the first argument of the task's constructor.
2616
2617 Optionally, the setup handler may return a SetupResult. The returned
2618 SetupResult influences the further start behavior of a given task. The
2619 possible values are:
2620
2621 \table
2622 \header
2623 \li SetupResult Value
2624 \li Brief Description
2625 \row
2626 \li Continue
2627 \li The task will be started normally. This is the default behavior when the
2628 setup handler doesn't return SetupResult (that is, its return type is
2629 void).
2630 \row
2631 \li StopWithSuccess
2632 \li The task won't be started and it will report success to its parent.
2633 \row
2634 \li StopWithError
2635 \li The task won't be started and it will report an error to its parent.
2636 \endtable
2637
2638 This is useful for running a task only when a condition is met and the data
2639 needed to evaluate this condition is not known until previously started tasks
2640 finish. In this way, the setup handler dynamically decides whether to start the
2641 corresponding task normally or skip it and report success or an error.
2642 For more information about inter-task data exchange, see \l Storage.
2643
2644 \section2 Task's Done Handler
2645
2646 When a running task finishes, the task tree invokes an optionally provided done handler.
2647 The handler should take a \c const \e reference to the associated task class object:
2648
2649 \code
2650 const auto onSetup = [](QProcess &process) {
2651 process.setCommand({"sleep", {"3"}});
2652 };
2653 const auto onDone = [](const QProcess &process, DoneWith result) {
2654 if (result == DoneWith::Success)
2655 qDebug() << "Success" << process.cleanedStdOut();
2656 else
2657 qDebug() << "Failure" << process.cleanedStdErr();
2658 };
2659 const Group root {
2660 QProcessTask(onSetup, onDone)
2661 };
2662 \endcode
2663
2664 The done handler may collect output data from QProcess, and store it
2665 for further processing or perform additional actions.
2666
2667 \note If the task setup handler returns StopWithSuccess or StopWithError,
2668 the done handler is not invoked.
2669
2670 \section1 Group Handlers
2671
2672 Similarly to task handlers, group handlers enable you to set up a group to
2673 execute and to apply more actions when the whole group finishes with
2674 success or an error.
2675
2676 \section2 Group's Start Handler
2677
2678 The task tree invokes the group start handler before it starts the child
2679 tasks. The group handler doesn't take any arguments:
2680
2681 \code
2682 const auto onSetup = [] {
2683 qDebug() << "Entering the group";
2684 };
2685 const Group root {
2686 onGroupSetup(onSetup),
2687 QProcessTask(...)
2688 };
2689 \endcode
2690
2691 The group setup handler is optional. To define a group setup handler, add an
2692 onGroupSetup() element to a group. The argument of onGroupSetup() is a user
2693 handler. If you add more than one onGroupSetup() element to a group, an assert
2694 is triggered at runtime that includes an error message.
2695
2696 Like the task's start handler, the group start handler may return SetupResult.
2697 The returned SetupResult value affects the start behavior of the
2698 whole group. If you do not specify a group start handler or its return type
2699 is void, the default group's action is SetupResult::Continue, so that all
2700 tasks are started normally. Otherwise, when the start handler returns
2701 SetupResult::StopWithSuccess or SetupResult::StopWithError, the tasks are not
2702 started (they are skipped) and the group itself reports success or failure,
2703 depending on the returned value, respectively.
2704
2705 \code
2706 const Group root {
2707 onGroupSetup([] { qDebug() << "Root setup"; }),
2708 Group {
2709 onGroupSetup([] { qDebug() << "Group 1 setup"; return SetupResult::Continue; }),
2710 QProcessTask(...) // Process 1
2711 },
2712 Group {
2713 onGroupSetup([] { qDebug() << "Group 2 setup"; return SetupResult::StopWithSuccess; }),
2714 QProcessTask(...) // Process 2
2715 },
2716 Group {
2717 onGroupSetup([] { qDebug() << "Group 3 setup"; return SetupResult::StopWithError; }),
2718 QProcessTask(...) // Process 3
2719 },
2720 QProcessTask(...) // Process 4
2721 };
2722 \endcode
2723
2724 In the above example, all subgroups of a root group define their setup handlers.
2725 The following scenario assumes that all started processes finish with success:
2726
2727 \table
2728 \header
2729 \li Scenario
2730 \li Comment
2731 \row
2732 \li Root Group starts
2733 \li Doesn't return SetupResult, so its tasks are executed.
2734 \row
2735 \li Group 1 starts
2736 \li Returns Continue, so its tasks are executed.
2737 \row
2738 \li Process 1 starts
2739 \li
2740 \row
2741 \li ...
2742 \li ...
2743 \row
2744 \li Process 1 finishes (success)
2745 \li
2746 \row
2747 \li Group 1 finishes (success)
2748 \li
2749 \row
2750 \li Group 2 starts
2751 \li Returns StopWithSuccess, so Process 2 is skipped and Group 2 reports
2752 success.
2753 \row
2754 \li Group 2 finishes (success)
2755 \li
2756 \row
2757 \li Group 3 starts
2758 \li Returns StopWithError, so Process 3 is skipped and Group 3 reports
2759 an error.
2760 \row
2761 \li Group 3 finishes (error)
2762 \li
2763 \row
2764 \li Root Group finishes (error)
2765 \li Group 3, which is a direct child of the root group, finished with an
2766 error, so the root group stops executing, skips Process 4, which has
2767 not started yet, and reports an error.
2768 \endtable
2769
2770 \section2 Groups's Done Handler
2771
2772 A Group's done handler is executed after the successful or failed execution of its tasks.
2773 The final value reported by the group depends on its \l {Workflow Policy}.
2774 The handler can apply other necessary actions.
2775 The done handler is defined inside the onGroupDone() element of a group.
2776 It may take the optional DoneWith argument, indicating the successful or failed execution:
2777
2778 \code
2779 const Group root {
2780 onGroupSetup([] { qDebug() << "Root setup"; }),
2781 QProcessTask(...),
2782 onGroupDone([](DoneWith result) {
2783 if (result == DoneWith::Success)
2784 qDebug() << "Root finished with success";
2785 else
2786 qDebug() << "Root finished with an error";
2787 })
2788 };
2789 \endcode
2790
2791 The group done handler is optional. If you add more than one onGroupDone() to a group,
2792 an assert is triggered at runtime that includes an error message.
2793
2794 \note Even if the group setup handler returns StopWithSuccess or StopWithError,
2795 the group's done handler is invoked. This behavior differs from that of task done handler
2796 and might change in the future.
2797
2798 \section1 Other Group Elements
2799
2800 A group can contain other elements that describe the processing flow, such as
2801 the execution mode or workflow policy. It can also contain storage elements
2802 that are responsible for collecting and sharing custom common data gathered
2803 during group execution.
2804
2805 \section2 Execution Mode
2806
2807 The execution mode element in a Group specifies how the direct child tasks of
2808 the Group are started. The most common execution modes are \l sequential and
2809 \l parallel. It's also possible to specify the limit of tasks running
2810 in parallel by using the parallelLimit() function.
2811
2812 In all execution modes, a group starts tasks in the oder in which they appear.
2813
2814 If a child of a group is also a group, the child group runs its tasks
2815 according to its own execution mode.
2816
2817 \section2 Workflow Policy
2818
2819 The workflow policy element in a Group specifies how the group should behave
2820 when any of its \e direct child's tasks finish. For a detailed description of possible
2821 policies, refer to WorkflowPolicy.
2822
2823 If a child of a group is also a group, the child group runs its tasks
2824 according to its own workflow policy.
2825
2826 \section2 Storage
2827
2828 Use the \l {Tasking::Storage} {Storage} element to exchange information between tasks.
2829 Especially, in the sequential execution mode, when a task needs data from another,
2830 already finished task, before it can start. For example, a task tree that copies data by reading
2831 it from a source and writing it to a destination might look as follows:
2832
2833 \code
2834 static QByteArray load(const QString &fileName) { ... }
2835 static void save(const QString &fileName, const QByteArray &array) { ... }
2836
2837 static Group copyRecipe(const QString &source, const QString &destination)
2838 {
2839 struct CopyStorage { // [1] custom inter-task struct
2840 QByteArray content; // [2] custom inter-task data
2841 };
2842
2843 // [3] instance of custom inter-task struct manageable by task tree
2844 const Storage<CopyStorage> storage;
2845
2846 const auto onLoaderSetup = [source](ConcurrentCall<QByteArray> &async) {
2847 async.setConcurrentCallData(&load, source);
2848 };
2849 // [4] runtime: task tree activates the instance from [7] before invoking handler
2850 const auto onLoaderDone = [storage](const ConcurrentCall<QByteArray> &async) {
2851 storage->content = async.result(); // [5] loader stores the result in storage
2852 };
2853
2854 // [4] runtime: task tree activates the instance from [7] before invoking handler
2855 const auto onSaverSetup = [storage, destination](ConcurrentCall<void> &async) {
2856 const QByteArray content = storage->content; // [6] saver takes data from storage
2857 async.setConcurrentCallData(&save, destination, content);
2858 };
2859 const auto onSaverDone = [](const ConcurrentCall<void> &async) {
2860 qDebug() << "Save done successfully";
2861 };
2862
2863 const Group root {
2864 // [7] runtime: task tree creates an instance of CopyStorage when root is entered
2865 storage,
2866 ConcurrentCallTask<QByteArray>(onLoaderSetup, onLoaderDone, CallDoneIf::Success),
2867 ConcurrentCallTask<void>(onSaverSetup, onSaverDone, CallDoneIf::Success)
2868 };
2869 return root;
2870 }
2871
2872 const QString source = ...;
2873 const QString destination = ...;
2874 TaskTree taskTree(copyRecipe(source, destination));
2875 connect(&taskTree, &TaskTree::done,
2876 &taskTree, [](DoneWith result) {
2877 if (result == DoneWith::Success)
2878 qDebug() << "The copying finished successfully.";
2879 });
2880 tasktree.start();
2881 \endcode
2882
2883 In the example above, the inter-task data consists of a QByteArray content
2884 variable [2] enclosed in a \c CopyStorage custom struct [1]. If the loader
2885 finishes successfully, it stores the data in a \c CopyStorage::content
2886 variable [5]. The saver then uses the variable to configure the saving task [6].
2887
2888 To enable a task tree to manage the \c CopyStorage struct, an instance of
2889 \l {Tasking::Storage} {Storage}<\c CopyStorage> is created [3]. If a copy of this object is
2890 inserted as the group's child item [7], an instance of the \c CopyStorage struct is
2891 created dynamically when the task tree enters this group. When the task
2892 tree leaves this group, the existing instance of the \c CopyStorage struct is
2893 destructed as it's no longer needed.
2894
2895 If several task trees holding a copy of the common
2896 \l {Tasking::Storage} {Storage}<\c CopyStorage> instance run simultaneously
2897 (including the case when the task trees are run in different threads),
2898 each task tree contains its own copy of the \c CopyStorage struct.
2899
2900 You can access \c CopyStorage from any handler in the group with a storage object.
2901 This includes all handlers of all descendant tasks of the group with
2902 a storage object. To access the custom struct in a handler, pass the
2903 copy of the \l {Tasking::Storage} {Storage}<\c CopyStorage> object to the handler
2904 (for example, in a lambda capture) [4].
2905
2906 When the task tree invokes a handler in a subtree containing the storage [7],
2907 the task tree activates its own \c CopyStorage instance inside the
2908 \l {Tasking::Storage} {Storage}<\c CopyStorage> object. Therefore, the \c CopyStorage struct
2909 may be accessed only from within the handler body. To access the currently active
2910 \c CopyStorage from within \l {Tasking::Storage} {Storage}<\c CopyStorage>, use the
2911 \l {Tasking::Storage::operator->()} {Storage::operator->()},
2912 \l {Tasking::Storage::operator*()} {Storage::operator*()}, or Storage::activeStorage() method.
2913
2914 The following list summarizes how to employ a Storage object into the task
2915 tree:
2916 \list 1
2917 \li Define the custom structure \c MyStorage with custom data [1], [2]
2918 \li Create an instance of the \l {Tasking::Storage} {Storage}<\c MyStorage> storage [3]
2919 \li Pass the \l {Tasking::Storage} {Storage}<\c MyStorage> instance to handlers [4]
2920 \li Access the \c MyStorage instance in handlers [5], [6]
2921 \li Insert the \l {Tasking::Storage} {Storage}<\c MyStorage> instance into a group [7]
2922 \endlist
2923
2924 \section1 TaskTree class
2925
2926 TaskTree executes the tree structure of asynchronous tasks according to the
2927 recipe described by the Group root element.
2928
2929 As TaskTree is also an asynchronous task, it can be a part of another TaskTree.
2930 To place a nested TaskTree inside another TaskTree, insert the TaskTreeTask
2931 element into another Group element.
2932
2933 TaskTree reports progress of completed tasks when running. The progress value
2934 is increased when a task finishes or is skipped or canceled.
2935 When TaskTree is finished and the TaskTree::done() signal is emitted,
2936 the current value of the progress equals the maximum progress value.
2937 Maximum progress equals the total number of asynchronous tasks in a tree.
2938 A nested TaskTree is counted as a single task, and its child tasks are not
2939 counted in the top level tree. Groups themselves are not counted as tasks,
2940 but their tasks are counted. \l {Tasking::Sync} {Sync} tasks are not asynchronous,
2941 so they are not counted as tasks.
2942
2943 To set additional initial data for the running tree, modify the storage
2944 instances in a tree when it creates them by installing a storage setup
2945 handler:
2946
2947 \code
2948 Storage<CopyStorage> storage;
2949 const Group root = ...; // storage placed inside root's group and inside handlers
2950 TaskTree taskTree(root);
2951 auto initStorage = [](CopyStorage &storage) {
2952 storage.content = "initial content";
2953 };
2954 taskTree.onStorageSetup(storage, initStorage);
2955 taskTree.start();
2956 \endcode
2957
2958 When the running task tree creates a \c CopyStorage instance, and before any
2959 handler inside a tree is called, the task tree calls the initStorage handler,
2960 to enable setting up initial data of the storage, unique to this particular
2961 run of taskTree.
2962
2963 Similarly, to collect some additional result data from the running tree,
2964 read it from storage instances in the tree when they are about to be
2965 destroyed. To do this, install a storage done handler:
2966
2967 \code
2968 Storage<CopyStorage> storage;
2969 const Group root = ...; // storage placed inside root's group and inside handlers
2970 TaskTree taskTree(root);
2971 auto collectStorage = [](const CopyStorage &storage) {
2972 qDebug() << "final content" << storage.content;
2973 };
2974 taskTree.onStorageDone(storage, collectStorage);
2975 taskTree.start();
2976 \endcode
2977
2978 When the running task tree is about to destroy a \c CopyStorage instance, the
2979 task tree calls the collectStorage handler, to enable reading the final data
2980 from the storage, unique to this particular run of taskTree.
2981
2982 \section1 Task Adapters
2983
2984 To extend a TaskTree with a new task type, implement a simple adapter class
2985 derived from the TaskAdapter class template. The following class is an
2986 adapter for a single shot timer, which may be considered as a new asynchronous task:
2987
2988 \code
2989 class TimerTaskAdapter : public TaskAdapter<QTimer>
2990 {
2991 public:
2992 TimerTaskAdapter() {
2993 task()->setSingleShot(true);
2994 task()->setInterval(1000);
2995 connect(task(), &QTimer::timeout, this, [this] { emit done(DoneResult::Success); });
2996 }
2997 private:
2998 void start() final { task()->start(); }
2999 };
3000
3001 using TimerTask = CustomTask<TimerTaskAdapter>;
3002 \endcode
3003
3004 You must derive the custom adapter from the TaskAdapter class template
3005 instantiated with a template parameter of the class implementing a running
3006 task. The code above uses QTimer to run the task. This class appears
3007 later as an argument to the task's handlers. The instance of this class
3008 parameter automatically becomes a member of the TaskAdapter template, and is
3009 accessible through the TaskAdapter::task() method. The constructor
3010 of \c TimerTaskAdapter initially configures the QTimer object and connects
3011 to the QTimer::timeout() signal. When the signal is triggered, \c TimerTaskAdapter
3012 emits the TaskInterface::done(DoneResult::Success) signal to inform the task tree that
3013 the task finished successfully. If it emits TaskInterface::done(DoneResult::Error),
3014 the task finished with an error.
3015 The TaskAdapter::start() method starts the timer.
3016
3017 To make QTimer accessible inside TaskTree under the \c TimerTask name,
3018 define \c TimerTask to be an alias to the CustomTask<\c TimerTaskAdapter>.
3019 \c TimerTask becomes a new custom task type, using \c TimerTaskAdapter.
3020
3021 The new task type is now registered, and you can use it in TaskTree:
3022
3023 \code
3024 const auto onSetup = [](QTimer &task) { task.setInterval(2000); };
3025 const auto onDone = [] { qDebug() << "timer triggered"; };
3026 const Group root {
3027 TimerTask(onSetup, onDone)
3028 };
3029 \endcode
3030
3031 When a task tree containing the root from the above example is started, it
3032 prints a debug message within two seconds and then finishes successfully.
3033
3034 \note The class implementing the running task should have a default constructor,
3035 and objects of this class should be freely destructible. It should be allowed
3036 to destroy a running object, preferably without waiting for the running task
3037 to finish (that is, safe non-blocking destructor of a running task).
3038 To achieve a non-blocking destruction of a task that has a blocking destructor,
3039 consider using the optional \c Deleter template parameter of the TaskAdapter.
3040*/
3041
3042/*!
3043 Constructs an empty task tree. Use setRecipe() to pass a declarative description
3044 on how the task tree should execute the tasks and how it should handle the finished tasks.
3045
3046 Starting an empty task tree is no-op and the relevant warning message is issued.
3047
3048 \sa setRecipe(), start()
3049*/
3050TaskTree::TaskTree()
3051 : d(new TaskTreePrivate(this))
3052{}
3053
3054/*!
3055 \overload
3056
3057 Constructs a task tree with a given \a recipe. After the task tree is started,
3058 it executes the tasks contained inside the \a recipe and
3059 handles finished tasks according to the passed description.
3060
3061 \sa setRecipe(), start()
3062*/
3063TaskTree::TaskTree(const Group &recipe) : TaskTree()
3064{
3065 setRecipe(recipe);
3066}
3067
3068/*!
3069 Destroys the task tree.
3070
3071 When the task tree is running while being destructed, it cancels all the running tasks
3072 immediately. In this case, no handlers are called, not even the groups' and
3073 tasks' done handlers or onStorageDone() handlers. The task tree also doesn't emit any
3074 signals from the destructor, not even done() or progressValueChanged() signals.
3075 This behavior may always be relied on.
3076 It is completely safe to destruct the running task tree.
3077
3078 It's a usual pattern to destruct the running task tree.
3079 It's guaranteed that the destruction will run quickly, without having to wait for
3080 the currently running tasks to finish, provided that the used tasks implement
3081 their destructors in a non-blocking way.
3082
3083 \note Do not call the destructor directly from any of the running task's handlers
3084 or task tree's signals. In these cases, use \l deleteLater() instead.
3085
3086 \sa cancel()
3087*/
3088TaskTree::~TaskTree()
3089{
3090 QT_ASSERT(!d->m_guard.isLocked(), qWarning("Deleting TaskTree instance directly from "
3091 "one of its handlers will lead to a crash!"));
3092 // TODO: delete storages explicitly here?
3093 delete d;
3094}
3095
3096/*!
3097 Sets a given \a recipe for the task tree. After the task tree is started,
3098 it executes the tasks contained inside the \a recipe and
3099 handles finished tasks according to the passed description.
3100
3101 \note When called for a running task tree, the call is ignored.
3102
3103 \sa TaskTree(const Tasking::Group &recipe), start()
3104*/
3105void TaskTree::setRecipe(const Group &recipe)
3106{
3107 QT_ASSERT(!isRunning(), qWarning("The TaskTree is already running, ignoring..."); return);
3108 QT_ASSERT(!d->m_guard.isLocked(), qWarning("The setRecipe() is called from one of the"
3109 "TaskTree handlers, ignoring..."); return);
3110 // TODO: Should we clear the m_storageHandlers, too?
3111 d->m_storages.clear();
3112 d->m_root.emplace(args&: d, args: recipe);
3113}
3114
3115/*!
3116 Starts the task tree.
3117
3118 Use setRecipe() or the constructor to set the declarative description according to which
3119 the task tree will execute the contained tasks and handle finished tasks.
3120
3121 When the task tree is empty, that is, constructed with a default constructor,
3122 a call to \c start() is no-op and the relevant warning message is issued.
3123
3124 Otherwise, when the task tree is already running, a call to \e start() is ignored and the
3125 relevant warning message is issued.
3126
3127 Otherwise, the task tree is started.
3128
3129 The started task tree may finish synchronously,
3130 for example when the main group's start handler returns SetupResult::StopWithError.
3131 For this reason, the connection to the done signal should be established before calling
3132 \c start(). Use isRunning() in order to detect whether the task tree is still running
3133 after a call to \c start().
3134
3135 The task tree implementation relies on the running event loop.
3136 Make sure you have a QEventLoop or QCoreApplication or one of its
3137 subclasses running (or about to be run) when calling this method.
3138
3139 \sa TaskTree(const Tasking::Group &), setRecipe(), isRunning(), cancel()
3140*/
3141void TaskTree::start()
3142{
3143 QT_ASSERT(!isRunning(), qWarning("The TaskTree is already running, ignoring..."); return);
3144 QT_ASSERT(!d->m_guard.isLocked(), qWarning("The start() is called from one of the"
3145 "TaskTree handlers, ignoring..."); return);
3146 d->start();
3147}
3148
3149/*!
3150 \fn void TaskTree::started()
3151
3152 This signal is emitted when the task tree is started. The emission of this signal is
3153 followed synchronously by the progressValueChanged() signal with an initial \c 0 value.
3154
3155 \sa start(), done()
3156*/
3157
3158/*!
3159 \fn void TaskTree::done(DoneWith result)
3160
3161 This signal is emitted when the task tree finished, passing the final \a result
3162 of the execution. The task tree neither calls any handler,
3163 nor emits any signal anymore after this signal was emitted.
3164
3165 \note Do not delete the task tree directly from this signal's handler.
3166 Use deleteLater() instead.
3167
3168 \sa started()
3169*/
3170
3171/*!
3172 Cancels the execution of the running task tree.
3173
3174 Cancels all the running tasks immediately.
3175 All running tasks finish with an error, invoking their error handlers.
3176 All running groups dispatch their handlers according to their workflow policies,
3177 invoking their done handlers. The storages' onStorageDone() handlers are invoked, too.
3178 The progressValueChanged() signals are also being sent.
3179 This behavior may always be relied on.
3180
3181 The \c cancel() function is executed synchronously, so that after a call to \c cancel()
3182 all running tasks are finished and the tree is already canceled.
3183 It's guaranteed that \c cancel() will run quickly, without any blocking wait for
3184 the currently running tasks to finish, provided the used tasks implement their destructors
3185 in a non-blocking way.
3186
3187 When the task tree is empty, that is, constructed with a default constructor,
3188 a call to \c cancel() is no-op and the relevant warning message is issued.
3189
3190 Otherwise, when the task tree wasn't started, a call to \c cancel() is ignored.
3191
3192 \note Do not call this function directly from any of the running task's handlers
3193 or task tree's signals.
3194
3195 \sa ~TaskTree()
3196*/
3197void TaskTree::cancel()
3198{
3199 QT_ASSERT(!d->m_guard.isLocked(), qWarning("The cancel() is called from one of the"
3200 "TaskTree handlers, ignoring..."); return);
3201 d->stop();
3202}
3203
3204/*!
3205 Returns \c true if the task tree is currently running; otherwise returns \c false.
3206
3207 \sa start(), cancel()
3208*/
3209bool TaskTree::isRunning() const
3210{
3211 return bool(d->m_runtimeRoot);
3212}
3213
3214/*!
3215 Executes a local event loop with QEventLoop::ExcludeUserInputEvents and starts the task tree.
3216
3217 Returns DoneWith::Success if the task tree finished successfully;
3218 otherwise returns DoneWith::Error.
3219
3220 \note Avoid using this method from the main thread. Use asynchronous start() instead.
3221 This method is to be used in non-main threads or in auto tests.
3222
3223 \sa start()
3224*/
3225DoneWith TaskTree::runBlocking()
3226{
3227 QPromise<void> dummy;
3228 dummy.start();
3229 return runBlocking(future: dummy.future());
3230}
3231
3232/*!
3233 \overload runBlocking()
3234
3235 The passed \a future is used for listening to the cancel event.
3236 When the task tree is canceled, this method cancels the passed \a future.
3237*/
3238DoneWith TaskTree::runBlocking(const QFuture<void> &future)
3239{
3240 if (future.isCanceled())
3241 return DoneWith::Cancel;
3242
3243 DoneWith doneWith = DoneWith::Cancel;
3244 QEventLoop loop;
3245 connect(sender: this, signal: &TaskTree::done, context: &loop, slot: [&loop, &doneWith](DoneWith result) {
3246 doneWith = result;
3247 // Otherwise, the tasks from inside the running tree that were deleteLater()
3248 // will be leaked. Refer to the QObject::deleteLater() docs.
3249 QMetaObject::invokeMethod(object: &loop, function: [&loop] { loop.quit(); }, type: Qt::QueuedConnection);
3250 });
3251 QFutureWatcher<void> watcher;
3252 connect(sender: &watcher, signal: &QFutureWatcherBase::canceled, context: this, slot: &TaskTree::cancel);
3253 watcher.setFuture(future);
3254
3255 QTimer::singleShot(interval: 0, receiver: this, slot: &TaskTree::start);
3256
3257 loop.exec(flags: QEventLoop::ExcludeUserInputEvents);
3258 if (doneWith == DoneWith::Cancel) {
3259 auto nonConstFuture = future;
3260 nonConstFuture.cancel();
3261 }
3262 return doneWith;
3263}
3264
3265/*!
3266 Constructs a temporary task tree using the passed \a recipe and runs it blocking.
3267
3268 The optionally provided \a timeout is used to cancel the tree automatically after
3269 \a timeout milliseconds have passed.
3270
3271 Returns DoneWith::Success if the task tree finished successfully;
3272 otherwise returns DoneWith::Error.
3273
3274 \note Avoid using this method from the main thread. Use asynchronous start() instead.
3275 This method is to be used in non-main threads or in auto tests.
3276
3277 \sa start()
3278*/
3279DoneWith TaskTree::runBlocking(const Group &recipe, milliseconds timeout)
3280{
3281 QPromise<void> dummy;
3282 dummy.start();
3283 return TaskTree::runBlocking(recipe, future: dummy.future(), timeout);
3284}
3285
3286/*!
3287 \overload runBlocking(const Group &recipe, milliseconds timeout)
3288
3289 The passed \a future is used for listening to the cancel event.
3290 When the task tree is canceled, this method cancels the passed \a future.
3291*/
3292DoneWith TaskTree::runBlocking(const Group &recipe, const QFuture<void> &future, milliseconds timeout)
3293{
3294 const Group root = timeout == milliseconds::max() ? recipe
3295 : Group { recipe.withTimeout(timeout) };
3296 TaskTree taskTree(root);
3297 return taskTree.runBlocking(future);
3298}
3299
3300/*!
3301 Returns the current real count of asynchronous chains of invocations.
3302
3303 The returned value indicates how many times the control returns to the caller's
3304 event loop while the task tree is running. Initially, this value is 0.
3305 If the execution of the task tree finishes fully synchronously, this value remains 0.
3306 If the task tree contains any asynchronous tasks that are successfully started during
3307 a call to start(), this value is bumped to 1 just before the call to start() finishes.
3308 Later, when any asynchronous task finishes and any possible continuations are started,
3309 this value is bumped again. The bumping continues until the task tree finishes.
3310 When the task tree emits the done() signal, the bumping stops.
3311 The asyncCountChanged() signal is emitted on every bump of this value.
3312
3313 \sa asyncCountChanged()
3314*/
3315int TaskTree::asyncCount() const
3316{
3317 return d->m_asyncCount;
3318}
3319
3320/*!
3321 \fn void TaskTree::asyncCountChanged(int count)
3322
3323 This signal is emitted when the running task tree is about to return control to the caller's
3324 event loop. When the task tree is started, this signal is emitted with \a count value of 0,
3325 and emitted later on every asyncCount() value bump with an updated \a count value.
3326 Every signal sent (except the initial one with the value of 0) guarantees that the task tree
3327 is still running asynchronously after the emission.
3328
3329 \sa asyncCount()
3330*/
3331
3332/*!
3333 Returns the number of asynchronous tasks contained in the stored recipe.
3334
3335 \note The returned number doesn't include \l {Tasking::Sync} {Sync} tasks.
3336 \note Any task or group that was set up using withTimeout() increases the total number of
3337 tasks by \c 1.
3338
3339 \sa setRecipe(), progressMaximum()
3340*/
3341int TaskTree::taskCount() const
3342{
3343 return d->m_root ? d->m_root->taskCount() : 0;
3344}
3345
3346/*!
3347 \fn void TaskTree::progressValueChanged(int value)
3348
3349 This signal is emitted when the running task tree finished, canceled, or skipped some tasks.
3350 The \a value gives the current total number of finished, canceled or skipped tasks.
3351 When the task tree is started, and after the started() signal was emitted,
3352 this signal is emitted with an initial \a value of \c 0.
3353 When the task tree is about to finish, and before the done() signal is emitted,
3354 this signal is emitted with the final \a value of progressMaximum().
3355
3356 \sa progressValue(), progressMaximum()
3357*/
3358
3359/*!
3360 \fn int TaskTree::progressMaximum() const
3361
3362 Returns the maximum progressValue().
3363
3364 \note Currently, it's the same as taskCount(). This might change in the future.
3365
3366 \sa progressValue()
3367*/
3368
3369/*!
3370 Returns the current progress value, which is between the \c 0 and progressMaximum().
3371
3372 The returned number indicates how many tasks have been already finished, canceled, or skipped
3373 while the task tree is running.
3374 When the task tree is started, this number is set to \c 0.
3375 When the task tree is finished, this number always equals progressMaximum().
3376
3377 \sa progressMaximum(), progressValueChanged()
3378*/
3379int TaskTree::progressValue() const
3380{
3381 return d->m_progressValue;
3382}
3383
3384/*!
3385 \fn template <typename StorageStruct, typename Handler> void TaskTree::onStorageSetup(const Storage<StorageStruct> &storage, Handler &&handler)
3386
3387 Installs a storage setup \a handler for the \a storage to pass the initial data
3388 dynamically to the running task tree.
3389
3390 The \c StorageHandler takes a \e reference to the \c StorageStruct instance:
3391
3392 \code
3393 static void save(const QString &fileName, const QByteArray &array) { ... }
3394
3395 Storage<QByteArray> storage;
3396
3397 const auto onSaverSetup = [storage](ConcurrentCall<QByteArray> &concurrent) {
3398 concurrent.setConcurrentCallData(&save, "foo.txt", *storage);
3399 };
3400
3401 const Group root {
3402 storage,
3403 ConcurrentCallTask(onSaverSetup)
3404 };
3405
3406 TaskTree taskTree(root);
3407 auto initStorage = [](QByteArray &storage){
3408 storage = "initial content";
3409 };
3410 taskTree.onStorageSetup(storage, initStorage);
3411 taskTree.start();
3412 \endcode
3413
3414 When the running task tree enters a Group where the \a storage is placed in,
3415 it creates a \c StorageStruct instance, ready to be used inside this group.
3416 Just after the \c StorageStruct instance is created, and before any handler of this group
3417 is called, the task tree invokes the passed \a handler. This enables setting up
3418 initial content for the given storage dynamically. Later, when any group's handler is invoked,
3419 the task tree activates the created and initialized storage, so that it's available inside
3420 any group's handler.
3421
3422 \sa onStorageDone()
3423*/
3424
3425/*!
3426 \fn template <typename StorageStruct, typename Handler> void TaskTree::onStorageDone(const Storage<StorageStruct> &storage, Handler &&handler)
3427
3428 Installs a storage done \a handler for the \a storage to retrieve the final data
3429 dynamically from the running task tree.
3430
3431 The \c StorageHandler takes a \c const \e reference to the \c StorageStruct instance:
3432
3433 \code
3434 static QByteArray load(const QString &fileName) { ... }
3435
3436 Storage<QByteArray> storage;
3437
3438 const auto onLoaderSetup = [](ConcurrentCall<QByteArray> &concurrent) {
3439 concurrent.setConcurrentCallData(&load, "foo.txt");
3440 };
3441 const auto onLoaderDone = [storage](const ConcurrentCall<QByteArray> &concurrent) {
3442 *storage = concurrent.result();
3443 };
3444
3445 const Group root {
3446 storage,
3447 ConcurrentCallTask(onLoaderSetup, onLoaderDone, CallDoneIf::Success)
3448 };
3449
3450 TaskTree taskTree(root);
3451 auto collectStorage = [](const QByteArray &storage){
3452 qDebug() << "final content" << storage;
3453 };
3454 taskTree.onStorageDone(storage, collectStorage);
3455 taskTree.start();
3456 \endcode
3457
3458 When the running task tree is about to leave a Group where the \a storage is placed in,
3459 it destructs a \c StorageStruct instance.
3460 Just before the \c StorageStruct instance is destructed, and after all possible handlers from
3461 this group were called, the task tree invokes the passed \a handler. This enables reading
3462 the final content of the given storage dynamically and processing it further outside of
3463 the task tree.
3464
3465 This handler is called also when the running tree is canceled. However, it's not called
3466 when the running tree is destructed.
3467
3468 \sa onStorageSetup()
3469*/
3470
3471void TaskTree::setupStorageHandler(const StorageBase &storage,
3472 StorageBase::StorageHandler setupHandler,
3473 StorageBase::StorageHandler doneHandler)
3474{
3475 auto it = d->m_storageHandlers.find(key: storage);
3476 if (it == d->m_storageHandlers.end()) {
3477 d->m_storageHandlers.insert(key: storage, value: {.m_setupHandler: setupHandler, .m_doneHandler: doneHandler});
3478 return;
3479 }
3480 if (setupHandler) {
3481 QT_ASSERT(!it->m_setupHandler,
3482 qWarning("The storage has its setup handler defined, overriding..."));
3483 it->m_setupHandler = setupHandler;
3484 }
3485 if (doneHandler) {
3486 QT_ASSERT(!it->m_doneHandler,
3487 qWarning("The storage has its done handler defined, overriding..."));
3488 it->m_doneHandler = doneHandler;
3489 }
3490}
3491
3492TaskTreeTaskAdapter::TaskTreeTaskAdapter()
3493{
3494 connect(sender: task(), signal: &TaskTree::done, context: this,
3495 slot: [this](DoneWith result) { emit done(result: toDoneResult(doneWith: result)); });
3496}
3497
3498void TaskTreeTaskAdapter::start()
3499{
3500 task()->start();
3501}
3502
3503using TimeoutCallback = std::function<void()>;
3504
3505struct TimerData
3506{
3507 system_clock::time_point m_deadline;
3508 QPointer<QObject> m_context;
3509 TimeoutCallback m_callback;
3510};
3511
3512struct TimerThreadData
3513{
3514 Q_DISABLE_COPY_MOVE(TimerThreadData)
3515
3516 TimerThreadData() = default; // defult constructor is required for initializing with {} since C++20 by Mingw 11.20
3517 QHash<int, TimerData> m_timerIdToTimerData = {};
3518 QMap<system_clock::time_point, QList<int>> m_deadlineToTimerId = {};
3519 int m_timerIdCounter = 0;
3520};
3521
3522// Please note the thread_local keyword below guarantees a separate instance per thread.
3523static thread_local TimerThreadData s_threadTimerData = {};
3524
3525static void removeTimerId(int timerId)
3526{
3527 const auto it = s_threadTimerData.m_timerIdToTimerData.constFind(key: timerId);
3528 QT_ASSERT(it != s_threadTimerData.m_timerIdToTimerData.cend(),
3529 qWarning("Removing active timerId failed."); return);
3530
3531 const system_clock::time_point deadline = it->m_deadline;
3532 s_threadTimerData.m_timerIdToTimerData.erase(it);
3533
3534 QList<int> &ids = s_threadTimerData.m_deadlineToTimerId[deadline];
3535 const int removedCount = ids.removeAll(t: timerId);
3536 QT_ASSERT(removedCount == 1, qWarning("Removing active timerId failed."); return);
3537 if (ids.isEmpty())
3538 s_threadTimerData.m_deadlineToTimerId.remove(key: deadline);
3539}
3540
3541static void handleTimeout(int timerId)
3542{
3543 const auto itData = s_threadTimerData.m_timerIdToTimerData.constFind(key: timerId);
3544 if (itData == s_threadTimerData.m_timerIdToTimerData.cend())
3545 return; // The timer was already activated.
3546
3547 const auto deadline = itData->m_deadline;
3548 while (true) {
3549 auto itMap = s_threadTimerData.m_deadlineToTimerId.begin();
3550 if (itMap == s_threadTimerData.m_deadlineToTimerId.end())
3551 return;
3552
3553 if (itMap.key() > deadline)
3554 return;
3555
3556 std::optional<TimerData> timerData;
3557 QList<int> &idList = *itMap;
3558 if (!idList.isEmpty()) {
3559 const int first = idList.first();
3560 idList.removeFirst();
3561
3562 const auto it = s_threadTimerData.m_timerIdToTimerData.constFind(key: first);
3563 if (it != s_threadTimerData.m_timerIdToTimerData.cend()) {
3564 timerData = it.value();
3565 s_threadTimerData.m_timerIdToTimerData.erase(it);
3566 } else {
3567 QT_CHECK(false);
3568 }
3569 } else {
3570 QT_CHECK(false);
3571 }
3572
3573 if (idList.isEmpty())
3574 s_threadTimerData.m_deadlineToTimerId.erase(it: itMap);
3575 if (timerData && timerData->m_context)
3576 timerData->m_callback();
3577 }
3578}
3579
3580static int scheduleTimeout(milliseconds timeout, QObject *context, const TimeoutCallback &callback)
3581{
3582 const int timerId = ++s_threadTimerData.m_timerIdCounter;
3583 const system_clock::time_point deadline = system_clock::now() + timeout;
3584 QTimer::singleShot(interval: timeout, receiver: context, slot: [timerId] { handleTimeout(timerId); });
3585 s_threadTimerData.m_timerIdToTimerData.emplace(key: timerId, args: TimerData{.m_deadline: deadline, .m_context: context, .m_callback: callback});
3586 s_threadTimerData.m_deadlineToTimerId[deadline].append(t: timerId);
3587 return timerId;
3588}
3589
3590TimeoutTaskAdapter::TimeoutTaskAdapter()
3591{
3592 *task() = milliseconds::zero();
3593}
3594
3595TimeoutTaskAdapter::~TimeoutTaskAdapter()
3596{
3597 if (m_timerId)
3598 removeTimerId(timerId: *m_timerId);
3599}
3600
3601void TimeoutTaskAdapter::start()
3602{
3603 m_timerId = scheduleTimeout(timeout: *task(), context: this, callback: [this] {
3604 m_timerId.reset();
3605 emit done(result: DoneResult::Success);
3606 });
3607}
3608
3609/*!
3610 \typealias Tasking::TaskTreeTask
3611
3612 Type alias for the CustomTask, to be used inside recipes, associated with the TaskTree task.
3613*/
3614
3615/*!
3616 \typealias Tasking::TimeoutTask
3617
3618 Type alias for the CustomTask, to be used inside recipes, associated with the
3619 \c std::chrono::milliseconds type. \c std::chrono::milliseconds is used to set up the
3620 timeout duration. The default timeout is \c std::chrono::milliseconds::zero(), that is,
3621 the TimeoutTask finishes as soon as the control returns to the running event loop.
3622
3623 Example usage:
3624
3625 \code
3626 using namespace std::chrono;
3627 using namespace std::chrono_literals;
3628
3629 const auto onSetup = [](milliseconds &timeout) { timeout = 1000ms; }
3630 const auto onDone = [] { qDebug() << "Timed out."; }
3631
3632 const Group root {
3633 Timeout(onSetup, onDone)
3634 };
3635 \endcode
3636*/
3637
3638} // namespace Tasking
3639
3640QT_END_NAMESPACE
3641

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