Skip to content

Commit 91af3ae

Browse files
vossmjpaleksei-fedotovkboyarinovakukanov
authored
Make task_group::wait more thread safe (#1780)
Co-authored-by: Aleksei Fedotov <aleksei.fedotov@intel.com> Co-authored-by: Konstantin Boyarinov <konstantin.boyarinov@intel.com> Co-authored-by: Alexey Kukanov <alexey.kukanov@intel.com>
1 parent 49b71fb commit 91af3ae

File tree

7 files changed

+375
-31
lines changed

7 files changed

+375
-31
lines changed

include/oneapi/tbb/task_group.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ template <bool>
5858
class context_guard_helper;
5959
struct task_arena_impl;
6060
class context_list;
61+
void handle_context_exception(d1::task_group_context& ctx, bool rethrow);
6162

6263
TBB_EXPORT void __TBB_EXPORTED_FUNC execute(d1::task_arena_base&, d1::delegate_base&);
6364
TBB_EXPORT void __TBB_EXPORTED_FUNC isolate_within_arena(d1::delegate_base&, std::intptr_t);
@@ -448,6 +449,7 @@ class task_group_context : no_copy {
448449
friend struct r1::task_arena_impl;
449450
friend struct r1::task_group_context_impl;
450451
friend class d2::task_group_base;
452+
friend void r1::handle_context_exception(d1::task_group_context&, bool rethrow);
451453
}; // class task_group_context
452454

453455
static_assert(sizeof(task_group_context) == 128, "Wrong size of task_group_context");

src/tbb/arena.cpp

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/*
22
Copyright (c) 2005-2025 Intel Corporation
3+
Copyright (c) 2025 UXL Foundation Contributors
34
45
Licensed under the Apache License, Version 2.0 (the "License");
56
you may not use this file except in compliance with the License.
@@ -844,11 +845,7 @@ void task_arena_impl::execute(d1::task_arena_base& ta, d1::delegate_base& d) {
844845
a->my_exit_monitors.notify_one(); // do not relax!
845846
}
846847
// process possible exception
847-
auto exception = exec_context.my_exception.load(std::memory_order_acquire);
848-
if (exception) {
849-
__TBB_ASSERT(exec_context.is_group_execution_cancelled(), "The task group context with an exception should be canceled.");
850-
exception->throw_self();
851-
}
848+
handle_context_exception(exec_context);
852849
__TBB_ASSERT(governor::is_thread_data_set(td), nullptr);
853850
return;
854851
} // if (index1 == arena::out_of_arena)

src/tbb/scheduler_common.h

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -336,13 +336,32 @@ class tbb_exception_ptr {
336336
/** Note that objects of this type can be created only by the allocate() method. **/
337337
void destroy() noexcept;
338338

339-
//! Throws the contained exception .
340-
void throw_self();
339+
//! Throws the contained exception and then destroys this object.
340+
void rethrow_and_destroy();
341341

342342
private:
343343
tbb_exception_ptr(const std::exception_ptr& src) : my_ptr(src) {}
344344
}; // class tbb_exception_ptr
345345

346+
//! Utility function to atomically clear and handle exceptions from task_group_context
347+
/** This function implements thread-safe pattern for clearing exceptions
348+
from a task_group_context and either destroying or throwing them. **/
349+
inline void handle_context_exception(d1::task_group_context& ctx, bool rethrow = true) {
350+
351+
tbb_exception_ptr* exception = ctx.my_exception.load(std::memory_order_acquire);
352+
if (exception) {
353+
if (ctx.my_exception.compare_exchange_strong(exception, nullptr,
354+
std::memory_order_acq_rel)) {
355+
// TODO: An exception should not be captured and then not rethrown.
356+
// Either add asserts or remove corner cases.
357+
if (rethrow) {
358+
exception->rethrow_and_destroy();
359+
} else
360+
exception->destroy();
361+
}
362+
}
363+
}
364+
346365
//------------------------------------------------------------------------
347366
// Debugging support
348367
//------------------------------------------------------------------------

src/tbb/task_dispatcher.cpp

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/*
22
Copyright (c) 2020-2025 Intel Corporation
3+
Copyright (c) 2025 UXL Foundation Contributors
34
45
Licensed under the Apache License, Version 2.0 (the "License");
56
you may not use this file except in compliance with the License.
@@ -173,11 +174,7 @@ void task_dispatcher::execute_and_wait(d1::task* t, d1::wait_context& wait_ctx,
173174
local_td.m_thread_data->my_inbox.set_is_idle(false);
174175
}
175176

176-
auto exception = w_ctx.my_exception.load(std::memory_order_acquire);
177-
if (exception) {
178-
__TBB_ASSERT(w_ctx.is_group_execution_cancelled(), "The task group context with an exception should be canceled.");
179-
exception->throw_self();
180-
}
177+
handle_context_exception(w_ctx);
181178
}
182179

183180
#if __TBB_RESUMABLE_TASKS

src/tbb/task_dispatcher.h

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -380,9 +380,15 @@ d1::task* task_dispatcher::local_wait_for_all(d1::task* t, Waiter& waiter ) {
380380
if (global_control::active_value(global_control::terminate_on_exception) == 1) {
381381
do_throw_noexcept([] { throw; });
382382
}
383-
if (ed.context->cancel_group_execution()) {
384-
/* We are the first to signal cancellation, so store the exception that caused it. */
385-
ed.context->my_exception.store(tbb_exception_ptr::allocate(), std::memory_order_release);
383+
384+
ed.context->cancel_group_execution();
385+
tbb_exception_ptr* exception = ed.context->my_exception.load(std::memory_order_acquire);
386+
if (!exception) {
387+
auto e = tbb_exception_ptr::allocate();
388+
if (!ed.context->my_exception.compare_exchange_strong(exception, e,
389+
std::memory_order_acq_rel)) {
390+
e->destroy();
391+
}
386392
}
387393
}
388394
} // Infinite exception loop

src/tbb/task_group_context.cpp

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/*
2-
Copyright (c) 2005-2023 Intel Corporation
2+
Copyright (c) 2005-2025 Intel Corporation
3+
Copyright (c) 2025 UXL Foundation Contributors
34
45
Licensed under the Apache License, Version 2.0 (the "License");
56
you may not use this file except in compliance with the License.
@@ -42,9 +43,11 @@ void tbb_exception_ptr::destroy() noexcept {
4243
deallocate_memory(this);
4344
}
4445

45-
void tbb_exception_ptr::throw_self() {
46+
void tbb_exception_ptr::rethrow_and_destroy() {
47+
auto temp_ptr = my_ptr;
48+
destroy();
4649
if (governor::rethrow_exception_broken()) fix_broken_rethrow();
47-
std::rethrow_exception(my_ptr);
50+
std::rethrow_exception(temp_ptr);
4851
}
4952

5053
//------------------------------------------------------------------------
@@ -65,10 +68,7 @@ void task_group_context_impl::destroy(d1::task_group_context& ctx) {
6568
#endif
6669
ctl->~cpu_ctl_env();
6770

68-
auto exception = ctx.my_exception.load(std::memory_order_relaxed);
69-
if (exception) {
70-
exception->destroy();
71-
}
71+
handle_context_exception(ctx, /* rethrow = */ false);
7272
ITT_STACK_DESTROY(ctx.my_itt_caller);
7373

7474
poison_pointer(ctx.my_parent);
@@ -240,19 +240,14 @@ bool task_group_context_impl::is_group_execution_cancelled(const d1::task_group_
240240
return ctx.my_cancellation_requested.load(std::memory_order_relaxed) != 0;
241241
}
242242

243-
// IMPORTANT: It is assumed that this method is not used concurrently!
243+
// IMPORTANT: If used while tasks are in the context, the cancellation signal can be lost
244244
void task_group_context_impl::reset(d1::task_group_context& ctx) {
245245
__TBB_ASSERT(!is_poisoned(ctx.my_context_list), nullptr);
246246
//! TODO: Add assertion that this context does not have children
247-
// No fences are necessary since this context can be accessed from another thread
248-
// only after stealing happened (which means necessary fences were used).
247+
248+
handle_context_exception(ctx, /* rethrow = */ false);
249249

250-
auto exception = ctx.my_exception.load(std::memory_order_relaxed);
251-
if (exception) {
252-
exception->destroy();
253-
ctx.my_exception.store(nullptr, std::memory_order_relaxed);
254-
}
255-
ctx.my_cancellation_requested = 0;
250+
ctx.my_cancellation_requested.store(0, std::memory_order_relaxed);
256251
}
257252

258253
// IMPORTANT: It is assumed that this method is not used concurrently!

0 commit comments

Comments
 (0)