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