xref: /oneTBB/include/oneapi/tbb/task_group.h (revision 0a2b3987)
1 /*
2     Copyright (c) 2005-2021 Intel Corporation
3 
4     Licensed under the Apache License, Version 2.0 (the "License");
5     you may not use this file except in compliance with the License.
6     You may obtain a copy of the License at
7 
8         http://www.apache.org/licenses/LICENSE-2.0
9 
10     Unless required by applicable law or agreed to in writing, software
11     distributed under the License is distributed on an "AS IS" BASIS,
12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13     See the License for the specific language governing permissions and
14     limitations under the License.
15 */
16 
17 #ifndef __TBB_task_group_H
18 #define __TBB_task_group_H
19 
20 #include "detail/_config.h"
21 #include "detail/_namespace_injection.h"
22 #include "detail/_assert.h"
23 #include "detail/_utils.h"
24 #include "detail/_template_helpers.h"
25 #include "detail/_exception.h"
26 #include "detail/_task.h"
27 #include "detail/_small_object_pool.h"
28 #include "detail/_intrusive_list_node.h"
29 
30 #if __TBB_PREVIEW_TASK_GROUP_EXTENSIONS
31 #include "detail/_task_handle.h"
32 #endif
33 
34 #include "profiling.h"
35 
36 #include <type_traits>
37 
38 #if _MSC_VER && !defined(__INTEL_COMPILER)
39     // Suppress warning: structure was padded due to alignment specifier
40     #pragma warning(push)
41     #pragma warning(disable:4324)
42 #endif
43 
44 namespace tbb {
45 namespace detail {
46 
47 namespace d1 {
48 class delegate_base;
49 class task_arena_base;
50 class task_group_context;
51 class task_group_base;
52 }
53 
54 namespace r1 {
55 // Forward declarations
56 class tbb_exception_ptr;
57 class market;
58 class thread_data;
59 class task_dispatcher;
60 template <bool>
61 class context_guard_helper;
62 struct task_arena_impl;
63 class context_list;
64 
65 TBB_EXPORT void __TBB_EXPORTED_FUNC execute(d1::task_arena_base&, d1::delegate_base&);
66 TBB_EXPORT void __TBB_EXPORTED_FUNC isolate_within_arena(d1::delegate_base&, std::intptr_t);
67 
68 TBB_EXPORT void __TBB_EXPORTED_FUNC initialize(d1::task_group_context&);
69 TBB_EXPORT void __TBB_EXPORTED_FUNC destroy(d1::task_group_context&);
70 TBB_EXPORT void __TBB_EXPORTED_FUNC reset(d1::task_group_context&);
71 TBB_EXPORT bool __TBB_EXPORTED_FUNC cancel_group_execution(d1::task_group_context&);
72 TBB_EXPORT bool __TBB_EXPORTED_FUNC is_group_execution_cancelled(d1::task_group_context&);
73 TBB_EXPORT void __TBB_EXPORTED_FUNC capture_fp_settings(d1::task_group_context&);
74 
75 struct task_group_context_impl;
76 }
77 
78 namespace d2 {
79 
80 #if __TBB_PREVIEW_TASK_GROUP_EXTENSIONS
81 namespace {
82 template<typename F>
83 d1::task* task_ptr_or_nullptr(F&& f);
84 }
85 
86 template<typename F>
87 class function_task : public task_handle_task  {
88     //TODO: apply empty base optimization here
89     const F m_func;
90 
91 private:
92     d1::task* execute(d1::execution_data& ed) override {
93         __TBB_ASSERT(ed.context == &this->ctx(), "The task group context should be used for all tasks");
94         task* res = task_ptr_or_nullptr(m_func);
95         finalize(&ed);
96         return res;
97     }
98     d1::task* cancel(d1::execution_data& ed) override {
99         finalize(&ed);
100         return nullptr;
101     }
102 public:
103     template<typename FF>
104     function_task(FF&& f, d1::wait_context& wo, d1::task_group_context& ctx, d1::small_object_allocator& alloc)
105         : task_handle_task{wo, ctx, alloc},
106           m_func(std::forward<FF>(f)) {}
107 };
108 
109 namespace {
110     template<typename F>
111     d1::task* task_ptr_or_nullptr_impl(std::false_type, F&& f){
112         task_handle th = std::forward<F>(f)();
113         return task_handle_accessor::release(th);
114     }
115 
116     template<typename F>
117     d1::task* task_ptr_or_nullptr_impl(std::true_type, F&& f){
118         std::forward<F>(f)();
119         return nullptr;
120     }
121 
122     template<typename F>
123     d1::task* task_ptr_or_nullptr(F&& f){
124         using is_void_t = std::is_void<
125             decltype(std::forward<F>(f)())
126             >;
127 
128         return  task_ptr_or_nullptr_impl(is_void_t{}, std::forward<F>(f));
129     }
130 }
131 #else
132 namespace {
133     template<typename F>
134     d1::task* task_ptr_or_nullptr(F&& f){
135         std::forward<F>(f)();
136         return nullptr;
137     }
138 }  // namespace
139 #endif // __TBB_PREVIEW_TASK_GROUP_EXTENSIONS
140 } // namespace d2
141 
142 namespace d1 {
143 
144 // This structure is left here for backward compatibility check
145 struct context_list_node {
146     std::atomic<context_list_node*> prev{};
147     std::atomic<context_list_node*> next{};
148 };
149 
150 //! Used to form groups of tasks
151 /** @ingroup task_scheduling
152     The context services explicit cancellation requests from user code, and unhandled
153     exceptions intercepted during tasks execution. Intercepting an exception results
154     in generating internal cancellation requests (which is processed in exactly the
155     same way as external ones).
156 
157     The context is associated with one or more root tasks and defines the cancellation
158     group that includes all the descendants of the corresponding root task(s). Association
159     is established when a context object is passed as an argument to the task::allocate_root()
160     method. See task_group_context::task_group_context for more details.
161 
162     The context can be bound to another one, and other contexts can be bound to it,
163     forming a tree-like structure: parent -> this -> children. Arrows here designate
164     cancellation propagation direction. If a task in a cancellation group is cancelled
165     all the other tasks in this group and groups bound to it (as children) get cancelled too.
166 **/
167 class task_group_context : no_copy {
168 public:
169     enum traits_type {
170         fp_settings     = 1 << 1,
171         concurrent_wait = 1 << 2,
172         default_traits  = 0
173     };
174     enum kind_type {
175         isolated,
176         bound
177     };
178 private:
179     //! Space for platform-specific FPU settings.
180     /** Must only be accessed inside TBB binaries, and never directly in user
181     code or inline methods. */
182     std::uint64_t my_cpu_ctl_env;
183 
184     //! Specifies whether cancellation was requested for this task group.
185     std::atomic<std::uint32_t> my_cancellation_requested;
186 
187     //! Versioning for run-time checks and behavioral traits of the context.
188     enum class task_group_context_version : std::uint8_t {
189         gold_2021U1   = 0,      // version of task_group_context released in oneTBB 2021.1 GOLD
190         proxy_support = 1       // backward compatible support for 'this' context to act as a proxy
191     };
192     task_group_context_version my_version;
193 
194     //! The context traits.
195     struct context_traits {
196         bool fp_settings        : 1;
197         bool concurrent_wait    : 1;
198         bool bound              : 1;
199         bool proxy              : 1; // true if 'this' acts as a proxy for user-specified context
200         bool reserved1          : 1;
201         bool reserved2          : 1;
202         bool reserved3          : 1;
203         bool reserved4          : 1;
204     } my_traits;
205 
206     static_assert(sizeof(context_traits) == 1, "Traits shall fit into one byte.");
207 
208     static constexpr std::uint8_t may_have_children = 1;
209     //! The context internal state (currently only may_have_children).
210     std::atomic<std::uint8_t> my_state;
211 
212     enum class lifetime_state : std::uint8_t {
213         created,
214         locked,
215         isolated,
216         bound,
217         dead
218     };
219 
220     //! The synchronization machine state to manage lifetime.
221     std::atomic<lifetime_state> my_lifetime_state;
222 
223     union {
224         //! Pointer to the context of the parent cancellation group. NULL for isolated contexts.
225         task_group_context* my_parent;
226 
227         //! Pointer to the actual context 'this' context represents a proxy of.
228         task_group_context* my_actual_context;
229     };
230 
231     //! Thread data instance that registered this context in its list.
232     r1::context_list* my_context_list;
233     static_assert(sizeof(std::atomic<r1::thread_data*>) == sizeof(r1::context_list*), "To preserve backward compatibility these types should have the same size");
234 
235     //! Used to form the thread specific list of contexts without additional memory allocation.
236     /** A context is included into the list of the current thread when its binding to
237         its parent happens. Any context can be present in the list of one thread only. **/
238     intrusive_list_node my_node;
239     static_assert(sizeof(intrusive_list_node) == sizeof(context_list_node), "To preserve backward compatibility these types should have the same size");
240 
241     //! Pointer to the container storing exception being propagated across this task group.
242     std::atomic<r1::tbb_exception_ptr*> my_exception;
243     static_assert(sizeof(std::atomic<r1::tbb_exception_ptr*>) == sizeof(r1::tbb_exception_ptr*),
244         "backward compatibility check");
245 
246     //! Used to set and maintain stack stitching point for Intel Performance Tools.
247     void* my_itt_caller;
248 
249     //! Description of algorithm for scheduler based instrumentation.
250     string_resource_index my_name;
251 
252     char padding[max_nfs_size
253         - sizeof(std::uint64_t)                          // my_cpu_ctl_env
254         - sizeof(std::atomic<std::uint32_t>)             // my_cancellation_requested
255         - sizeof(std::uint8_t)                           // my_version
256         - sizeof(context_traits)                         // my_traits
257         - sizeof(std::atomic<std::uint8_t>)              // my_state
258         - sizeof(std::atomic<lifetime_state>)            // my_lifetime_state
259         - sizeof(task_group_context*)                    // my_parent
260         - sizeof(r1::context_list*)                      // my_context_list
261         - sizeof(intrusive_list_node)                    // my_node
262         - sizeof(std::atomic<r1::tbb_exception_ptr*>)    // my_exception
263         - sizeof(void*)                                  // my_itt_caller
264         - sizeof(string_resource_index)                  // my_name
265     ];
266 
267     task_group_context(context_traits t, string_resource_index name)
268         : my_version{task_group_context_version::proxy_support}, my_name{name}
269     {
270         my_traits = t; // GCC4.8 issues warning list initialization for bitset (missing-field-initializers)
271         r1::initialize(*this);
272     }
273 
274 #if __TBB_PREVIEW_TASK_GROUP_EXTENSIONS
275     task_group_context(task_group_context* actual_context)
276         : my_version{task_group_context_version::proxy_support}
277         , my_actual_context{actual_context}
278     {
279         __TBB_ASSERT(my_actual_context, "Passed pointer value points to nothing.");
280         my_traits.proxy = true;
281         my_name = actual_context->my_name;
282 
283         // no need to initialize 'this' context as it acts as a proxy for my_actual_context, which
284         // initialization is a user-side responsibility.
285     }
286 #endif
287 
288     static context_traits make_traits(kind_type relation_with_parent, std::uintptr_t user_traits) {
289         context_traits ct;
290         ct.fp_settings = (user_traits & fp_settings) == fp_settings;
291         ct.concurrent_wait = (user_traits & concurrent_wait) == concurrent_wait;
292         ct.bound = relation_with_parent == bound;
293         ct.proxy = false;
294         ct.reserved1 = ct.reserved2 = ct.reserved3 = ct.reserved4 = false;
295         return ct;
296     }
297 
298 #if __TBB_PREVIEW_TASK_GROUP_EXTENSIONS
299     bool is_proxy() const {
300         return my_version >= task_group_context_version::proxy_support && my_traits.proxy;
301     }
302 #endif
303 
304     task_group_context& actual_context() noexcept {
305 #if __TBB_PREVIEW_TASK_GROUP_EXTENSIONS
306         if (is_proxy()) {
307             __TBB_ASSERT(my_actual_context, "Actual task_group_context is not set.");
308             return *my_actual_context;
309         }
310 #endif
311         return *this;
312     }
313 
314     const task_group_context& actual_context() const noexcept {
315 #if __TBB_PREVIEW_TASK_GROUP_EXTENSIONS
316         if (is_proxy()) {
317             __TBB_ASSERT(my_actual_context, "Actual task_group_context is not set.");
318             return *my_actual_context;
319         }
320 #endif
321         return *this;
322     }
323 
324 public:
325     //! Default & binding constructor.
326     /** By default a bound context is created. That is this context will be bound
327         (as child) to the context of the currently executing task . Cancellation
328         requests passed to the parent context are propagated to all the contexts
329         bound to it. Similarly priority change is propagated from the parent context
330         to its children.
331 
332         If task_group_context::isolated is used as the argument, then the tasks associated
333         with this context will never be affected by events in any other context.
334 
335         Creating isolated contexts involve much less overhead, but they have limited
336         utility. Normally when an exception occurs in an algorithm that has nested
337         ones running, it is desirably to have all the nested algorithms cancelled
338         as well. Such a behavior requires nested algorithms to use bound contexts.
339 
340         There is one good place where using isolated algorithms is beneficial. It is
341         an external thread. That is if a particular algorithm is invoked directly from
342         the external thread (not from a TBB task), supplying it with explicitly
343         created isolated context will result in a faster algorithm startup.
344 
345         VERSIONING NOTE:
346         Implementation(s) of task_group_context constructor(s) cannot be made
347         entirely out-of-line because the run-time version must be set by the user
348         code. This will become critically important for binary compatibility, if
349         we ever have to change the size of the context object. **/
350 
351     task_group_context(kind_type relation_with_parent = bound,
352                        std::uintptr_t t = default_traits)
353         : task_group_context(make_traits(relation_with_parent, t), CUSTOM_CTX) {}
354 
355     // Custom constructor for instrumentation of oneTBB algorithm
356     task_group_context(string_resource_index name )
357         : task_group_context(make_traits(bound, default_traits), name) {}
358 
359     // Do not introduce any logic on user side since it might break state propagation assumptions
360     ~task_group_context() {
361 #if __TBB_PREVIEW_TASK_GROUP_EXTENSIONS
362         // When 'this' serves as a proxy, the initialization does not happen - nor should the
363         // destruction.
364         if (!is_proxy())
365 #endif
366         {
367             r1::destroy(*this);
368         }
369     }
370 
371     //! Forcefully reinitializes the context after the task tree it was associated with is completed.
372     /** Because the method assumes that all the tasks that used to be associated with
373         this context have already finished, calling it while the context is still
374         in use somewhere in the task hierarchy leads to undefined behavior.
375 
376         IMPORTANT: This method is not thread safe!
377 
378         The method does not change the context's parent if it is set. **/
379     void reset() {
380         r1::reset(actual_context());
381     }
382 
383     //! Initiates cancellation of all tasks in this cancellation group and its subordinate groups.
384     /** \return false if cancellation has already been requested, true otherwise.
385 
386         Note that canceling never fails. When false is returned, it just means that
387         another thread (or this one) has already sent cancellation request to this
388         context or to one of its ancestors (if this context is bound). It is guaranteed
389         that when this method is concurrently called on the same not yet cancelled
390         context, true will be returned by one and only one invocation. **/
391     bool cancel_group_execution() {
392         return r1::cancel_group_execution(actual_context());
393     }
394 
395     //! Returns true if the context received cancellation request.
396     bool is_group_execution_cancelled() {
397         return r1::is_group_execution_cancelled(actual_context());
398     }
399 
400 #if __TBB_FP_CONTEXT
401     //! Captures the current FPU control settings to the context.
402     /** Because the method assumes that all the tasks that used to be associated with
403         this context have already finished, calling it while the context is still
404         in use somewhere in the task hierarchy leads to undefined behavior.
405 
406         IMPORTANT: This method is not thread safe!
407 
408         The method does not change the FPU control settings of the context's parent. **/
409     void capture_fp_settings() {
410         r1::capture_fp_settings(actual_context());
411     }
412 #endif
413 
414     //! Returns the user visible context trait
415     std::uintptr_t traits() const {
416         std::uintptr_t t{};
417         const task_group_context& ctx = actual_context();
418         t |= ctx.my_traits.fp_settings ? fp_settings : 0;
419         t |= ctx.my_traits.concurrent_wait ? concurrent_wait : 0;
420         return t;
421     }
422 private:
423     //// TODO: cleanup friends
424     friend class r1::market;
425     friend class r1::thread_data;
426     friend class r1::task_dispatcher;
427     template <bool>
428     friend class r1::context_guard_helper;
429     friend struct r1::task_arena_impl;
430     friend struct r1::task_group_context_impl;
431     friend class task_group_base;
432 }; // class task_group_context
433 
434 static_assert(sizeof(task_group_context) == 128, "Wrong size of task_group_context");
435 
436 enum task_group_status {
437     not_complete,
438     complete,
439     canceled
440 };
441 
442 class task_group;
443 class structured_task_group;
444 #if TBB_PREVIEW_ISOLATED_TASK_GROUP
445 class isolated_task_group;
446 #endif
447 
448 template<typename F>
449 class function_task : public task {
450     const F m_func;
451     wait_context& m_wait_ctx;
452     small_object_allocator m_allocator;
453 
454     void finalize(const execution_data& ed) {
455         // Make a local reference not to access this after destruction.
456         wait_context& wo = m_wait_ctx;
457         // Copy allocator to the stack
458         auto allocator = m_allocator;
459         // Destroy user functor before release wait.
460         this->~function_task();
461         wo.release();
462 
463         allocator.deallocate(this, ed);
464     }
465     task* execute(execution_data& ed) override {
466         task* res = d2::task_ptr_or_nullptr(m_func);
467         finalize(ed);
468         return res;
469     }
470     task* cancel(execution_data& ed) override {
471         finalize(ed);
472         return nullptr;
473     }
474 public:
475     function_task(const F& f, wait_context& wo, small_object_allocator& alloc)
476         : m_func(f)
477         , m_wait_ctx(wo)
478         , m_allocator(alloc) {}
479 
480     function_task(F&& f, wait_context& wo, small_object_allocator& alloc)
481         : m_func(std::move(f))
482         , m_wait_ctx(wo)
483         , m_allocator(alloc) {}
484 };
485 
486 template <typename F>
487 class function_stack_task : public task {
488     const F& m_func;
489     wait_context& m_wait_ctx;
490 
491     void finalize() {
492         m_wait_ctx.release();
493     }
494     task* execute(execution_data&) override {
495         task* res = d2::task_ptr_or_nullptr(m_func);
496         finalize();
497         return res;
498     }
499     task* cancel(execution_data&) override {
500         finalize();
501         return nullptr;
502     }
503 public:
504     function_stack_task(const F& f, wait_context& wo) : m_func(f), m_wait_ctx(wo) {}
505 };
506 
507 class task_group_base : no_copy {
508 protected:
509     wait_context m_wait_ctx;
510     task_group_context m_context;
511 
512     template<typename F>
513     task_group_status internal_run_and_wait(const F& f) {
514         function_stack_task<F> t{ f, m_wait_ctx };
515         m_wait_ctx.reserve();
516         bool cancellation_status = false;
517         try_call([&] {
518             execute_and_wait(t, context(), m_wait_ctx, context());
519         }).on_completion([&] {
520             // TODO: the reset method is not thread-safe. Ensure the correct behavior.
521             cancellation_status = context().is_group_execution_cancelled();
522             context().reset();
523         });
524         return cancellation_status ? canceled : complete;
525     }
526 
527 #if __TBB_PREVIEW_TASK_GROUP_EXTENSIONS
528     task_group_status internal_run_and_wait(d2::task_handle&& h) {
529         if (h == nullptr) {
530             throw_exception(exception_id::bad_task_handle);
531         }
532 
533         using acs = d2::task_handle_accessor;
534         if (&acs::ctx_of(h) != &context()) {
535             throw_exception(exception_id::bad_task_handle_wrong_task_group);
536         }
537 
538         bool cancellation_status = false;
539         try_call([&] {
540             execute_and_wait(*acs::release(h), context(), m_wait_ctx, context());
541         }).on_completion([&] {
542             // TODO: the reset method is not thread-safe. Ensure the correct behavior.
543             cancellation_status = context().is_group_execution_cancelled();
544             context().reset();
545         });
546         return cancellation_status ? canceled : complete;
547     }
548 #endif
549     template<typename F>
550     task* prepare_task(F&& f) {
551         m_wait_ctx.reserve();
552         small_object_allocator alloc{};
553         return alloc.new_object<function_task<typename std::decay<F>::type>>(std::forward<F>(f), m_wait_ctx, alloc);
554     }
555 
556     task_group_context& context() noexcept {
557         return m_context.actual_context();
558     }
559 
560 #if __TBB_PREVIEW_TASK_GROUP_EXTENSIONS
561     template<typename F>
562     d2::task_handle prepare_task_handle(F&& f) {
563         m_wait_ctx.reserve();
564         small_object_allocator alloc{};
565         using function_task_t =  d2::function_task<typename std::decay<F>::type>;
566         d2::task_handle_task* function_task_p =  alloc.new_object<function_task_t>(std::forward<F>(f), m_wait_ctx, context(), alloc);
567 
568         return d2::task_handle_accessor::construct(function_task_p);
569     }
570 #endif
571 
572 public:
573     task_group_base(uintptr_t traits = 0)
574         : m_wait_ctx(0)
575         , m_context(task_group_context::bound, task_group_context::default_traits | traits)
576     {}
577 
578 #if __TBB_PREVIEW_TASK_GROUP_EXTENSIONS
579     task_group_base(task_group_context& ctx)
580         : m_wait_ctx(0)
581         , m_context(&ctx)
582     {}
583 #endif
584 
585     ~task_group_base() noexcept(false) {
586         if (m_wait_ctx.continue_execution()) {
587 #if __TBB_CPP17_UNCAUGHT_EXCEPTIONS_PRESENT
588             bool stack_unwinding_in_progress = std::uncaught_exceptions() > 0;
589 #else
590             bool stack_unwinding_in_progress = std::uncaught_exception();
591 #endif
592             // Always attempt to do proper cleanup to avoid inevitable memory corruption
593             // in case of missing wait (for the sake of better testability & debuggability)
594             if (!context().is_group_execution_cancelled())
595                 cancel();
596             d1::wait(m_wait_ctx, context());
597             if (!stack_unwinding_in_progress)
598                 throw_exception(exception_id::missing_wait);
599         }
600     }
601 
602     task_group_status wait() {
603         bool cancellation_status = false;
604         try_call([&] {
605             d1::wait(m_wait_ctx, context());
606         }).on_completion([&] {
607             // TODO: the reset method is not thread-safe. Ensure the correct behavior.
608             cancellation_status = m_context.is_group_execution_cancelled();
609             context().reset();
610         });
611         return cancellation_status ? canceled : complete;
612     }
613 
614     void cancel() {
615         context().cancel_group_execution();
616     }
617 }; // class task_group_base
618 
619 class task_group : public task_group_base {
620 public:
621     task_group() : task_group_base(task_group_context::concurrent_wait) {}
622 
623 #if __TBB_PREVIEW_TASK_GROUP_EXTENSIONS
624     task_group(task_group_context& ctx) : task_group_base(ctx) {}
625 #endif
626 
627     template<typename F>
628     void run(F&& f) {
629         spawn(*prepare_task(std::forward<F>(f)), context());
630     }
631 
632 #if __TBB_PREVIEW_TASK_GROUP_EXTENSIONS
633     void run(d2::task_handle&& h) {
634         if (h == nullptr) {
635             throw_exception(exception_id::bad_task_handle);
636         }
637 
638         using acs = d2::task_handle_accessor;
639         if (&acs::ctx_of(h) != &context()) {
640             throw_exception(exception_id::bad_task_handle_wrong_task_group);
641         }
642 
643         spawn(*acs::release(h), context());
644     }
645 
646     template<typename F>
647     d2::task_handle defer(F&& f) {
648         return prepare_task_handle(std::forward<F>(f));
649 
650     }
651 #endif //__TBB_PREVIEW_TASK_GROUP_EXTENSIONS
652 
653     template<typename F>
654     task_group_status run_and_wait(const F& f) {
655         return internal_run_and_wait(f);
656     }
657 
658 #if __TBB_PREVIEW_TASK_GROUP_EXTENSIONS
659     task_group_status run_and_wait(d2::task_handle&& h) {
660         return internal_run_and_wait(std::move(h));
661     }
662 #endif
663 }; // class task_group
664 
665 #if TBB_PREVIEW_ISOLATED_TASK_GROUP
666 class spawn_delegate : public delegate_base {
667     task* task_to_spawn;
668     task_group_context& context;
669     bool operator()() const override {
670         spawn(*task_to_spawn, context);
671         return true;
672     }
673 public:
674     spawn_delegate(task* a_task, task_group_context& ctx)
675         : task_to_spawn(a_task), context(ctx)
676     {}
677 };
678 
679 class wait_delegate : public delegate_base {
680     bool operator()() const override {
681         status = tg.wait();
682         return true;
683     }
684 protected:
685     task_group& tg;
686     task_group_status& status;
687 public:
688     wait_delegate(task_group& a_group, task_group_status& tgs)
689         : tg(a_group), status(tgs) {}
690 };
691 
692 template<typename F>
693 class run_wait_delegate : public wait_delegate {
694     F& func;
695     bool operator()() const override {
696         status = tg.run_and_wait(func);
697         return true;
698     }
699 public:
700     run_wait_delegate(task_group& a_group, F& a_func, task_group_status& tgs)
701         : wait_delegate(a_group, tgs), func(a_func) {}
702 };
703 
704 class isolated_task_group : public task_group {
705     intptr_t this_isolation() {
706         return reinterpret_cast<intptr_t>(this);
707     }
708 public:
709     isolated_task_group() : task_group() {}
710 
711 #if __TBB_PREVIEW_TASK_GROUP_EXTENSIONS
712     isolated_task_group(task_group_context& ctx) : task_group(ctx) {}
713 #endif
714 
715     template<typename F>
716     void run(F&& f) {
717         spawn_delegate sd(prepare_task(std::forward<F>(f)), context());
718         r1::isolate_within_arena(sd, this_isolation());
719     }
720 
721 #if __TBB_PREVIEW_TASK_GROUP_EXTENSIONS
722     void run(d2::task_handle&& h) {
723         if (h == nullptr) {
724             throw_exception(exception_id::bad_task_handle);
725         }
726 
727         using acs = d2::task_handle_accessor;
728         if (&acs::ctx_of(h) != &context()) {
729             throw_exception(exception_id::bad_task_handle_wrong_task_group);
730         }
731 
732         spawn_delegate sd(acs::release(h), context());
733         r1::isolate_within_arena(sd, this_isolation());
734     }
735 #endif //__TBB_PREVIEW_TASK_GROUP_EXTENSIONS
736 
737     template<typename F>
738     task_group_status run_and_wait( const F& f ) {
739         task_group_status result = not_complete;
740         run_wait_delegate<const F> rwd(*this, f, result);
741         r1::isolate_within_arena(rwd, this_isolation());
742         __TBB_ASSERT(result != not_complete, "premature exit from wait?");
743         return result;
744     }
745 
746     task_group_status wait() {
747         task_group_status result = not_complete;
748         wait_delegate wd(*this, result);
749         r1::isolate_within_arena(wd, this_isolation());
750         __TBB_ASSERT(result != not_complete, "premature exit from wait?");
751         return result;
752     }
753 }; // class isolated_task_group
754 #endif // TBB_PREVIEW_ISOLATED_TASK_GROUP
755 
756 inline bool is_current_task_group_canceling() {
757     task_group_context* ctx = current_context();
758     return ctx ? ctx->is_group_execution_cancelled() : false;
759 }
760 
761 } // namespace d1
762 } // namespace detail
763 
764 inline namespace v1 {
765 using detail::d1::task_group_context;
766 using detail::d1::task_group;
767 #if TBB_PREVIEW_ISOLATED_TASK_GROUP
768 using detail::d1::isolated_task_group;
769 #endif
770 
771 using detail::d1::task_group_status;
772 using detail::d1::not_complete;
773 using detail::d1::complete;
774 using detail::d1::canceled;
775 
776 using detail::d1::is_current_task_group_canceling;
777 using detail::r1::missing_wait;
778 
779 #if __TBB_PREVIEW_TASK_GROUP_EXTENSIONS
780 using detail::d2::task_handle;
781 #endif
782 }
783 
784 } // namespace tbb
785 
786 #if _MSC_VER && !defined(__INTEL_COMPILER)
787     #pragma warning(pop) // 4324 warning
788 #endif
789 
790 #endif // __TBB_task_group_H
791