Intel® oneAPI Threading Building Blocks
Ask questions and share information about adding parallelism to your applications when using this threading library.
2465 Discussions

mutex::scoped_lock not released after unhandled exception

Andrei_Vermel
Beginner
1,210 Views
On Vista 64, tbb22_012oss, VS2008

static tbb::mutex the_ProgressMutex;

parallel_for executes the following bit of code:

if(IsMainThread()) {
tbb::mutex::scoped_lock lock(the_ProgressMutex);
throw std::string(""); // this used to be a subtle access violation, but can be simulated with a throw
} else {
tbb::mutex::scoped_lock lock(the_ProgressMutex);
// ....
}

where IsMainThread() uses tbb::enumerable_thread_specific to distinguish the main thread.

The result of the exception is a lockup, with main thread waiting in:
tbb64.dll!__TBB_machine_pause() Line 76 Asm
tbb64.dll!tbb::internal::CustomScheduler<:INTERNAL::INTELSCHEDULERTRAITS>::receive_or_steal_task(__int64 & completion_ref_count=2, bool return_if_no_work=false) Line 2731 C++
tbb64.dll!tbb::internal::CustomScheduler<:INTERNAL::INTELSCHEDULERTRAITS>::local_wait_for_all(tbb::task & parent={...}, tbb::task * child=0x000007fffff9f940) Line 2964 + 0x5c bytes C++
tbb64.dll!tbb::internal::GenericScheduler::local_spawn_root_and_wait(tbb::task & first={...}, tbb::task * & next=0x0000000000000000) Line 2519 + 0x2b bytes C++
> tbb64.dll!tbb::internal::GenericScheduler::spawn_root_and_wait(tbb::task & first={...}, tbb::task * & next=0x0000000000000000) Line 1462 C++
machining64.dll!tbb::task::spawn_root_and_wait(tbb::task & root={...}) Line 581 C++
machining64.dll!tbb::internal::start_for<:BLOCKED_RANGE>,Caching::PointsUtils::JoiningMultithreadedHelper,tbb::auto_partitioner>::run(const tbb::blocked_range & range={...}, const Caching::PointsUtils::JoiningMultithreadedHelper & body={...}, const tbb::auto_partitioner & partitioner={...}) Line 83 + 0xd bytes C++
machining64.dll!tbb::parallel_for<:BLOCKED_RANGE>,Caching::PointsUtils::JoiningMultithreadedHelper>(const tbb::blocked_range & range={...}, const Caching::PointsUtils::JoiningMultithreadedHelper & body={...}, const tbb::auto_partitioner & partitioner={...}) Line 147 C++

The rest of the threads are waiting for the_ProgressMutex to get released. Somehow it remains allocated by the main thread! This follows from LockCount and OwningThread fields in the CRITICAL_SECTION inside the_ProgressMutex. the_ProgressMutex.state is equal to HELD.
Does anyone have a clue how this is possible?
If I surround the scoped_lock and the throw with a try/catch(...), then the_ProgressMutex gets released properly!

Another question about the same issue. Apparently tbb catches all exceptions including access violations.
This makes it very hard to debug them. Enabling debugger to catch the win32 exceptions was not helpful in this case, as it made the access violation go away.
Is there a way to turn off tbb catching (...) ?
0 Kudos
1 Solution
Andrey_Marochko
New Contributor III
1,211 Views
Hi Andrei,

It looks like you have not specified one of the /EH options while compiling your application. In this case exceptions get intercepted by catch-blocks, but no unwinding happens, and thus local objects are not destroyed. Inserting try/catch immediately around the scoped_lock and the throw helps as the scoped lock gets destroyed (and releases the critical section) due to normal exit from its scope.

As for your last question, starting from the last TBB stable release you can compile TBB with macro TBB_USE_EXCEPTIONS=0. This will switch off all exception handling constructs in the library.

Instead of recompiling TBB you can compile your app with /EHa and use _set_se_translator to inject your handler for structured exceptions. The only inconvenience of this approach is that you'll need to use task_scheduler_observer to install the handler in each TBB worker thread (as it must be done on the per-thread basis).

View solution in original post

0 Kudos
3 Replies
Denis_Bolshakov
Beginner
1,211 Views
I can't answer.
But I can advise
Please changer order of your code, if it's thread safe of course
From your example
if(IsMainThread()) {
throw std::string(""); // this used to be a subtle access violation, but can be simulated with a throw
tbb::mutex::scoped_lock lock(the_ProgressMutex);
} else {
tbb::mutex::scoped_lock lock(the_ProgressMutex);
// ....
}

:)
0 Kudos
Andrey_Marochko
New Contributor III
1,212 Views
Hi Andrei,

It looks like you have not specified one of the /EH options while compiling your application. In this case exceptions get intercepted by catch-blocks, but no unwinding happens, and thus local objects are not destroyed. Inserting try/catch immediately around the scoped_lock and the throw helps as the scoped lock gets destroyed (and releases the critical section) due to normal exit from its scope.

As for your last question, starting from the last TBB stable release you can compile TBB with macro TBB_USE_EXCEPTIONS=0. This will switch off all exception handling constructs in the library.

Instead of recompiling TBB you can compile your app with /EHa and use _set_se_translator to inject your handler for structured exceptions. The only inconvenience of this approach is that you'll need to use task_scheduler_observer to install the handler in each TBB worker thread (as it must be done on the per-thread basis).
0 Kudos
Andrei_Vermel
Beginner
1,211 Views
Andrey, thank you for your reply.
The problem turned out to be a "throw ()" in one of the function declarations up the call stack.
This used to have no effect some years and MSVC versions ago, but apparently not anymore.
0 Kudos
Reply