0
votes

I came across a strange Boost (v1.38) mutex deadlock in a preexisting .NET (C#, 3.5) application that makes calls to a C++ library. An exception is [properly] thrown at a point after a read lock is obtained and that exception goes unhandled all the way back to the managed .NET code (where it is handled). The next call to the c++ library that attempts to use a setter method hangs on the unique lock aquisition indefinately (presumably the read lock was not released):

ntdll.dll!NtWaitForSingleObject() + 0x15 bytes 
kernel32.dll!WaitForSingleObjectEx() + 0x43 bytes 
kernel32.dll!WaitForSingleObject() + 0x12 bytes 
OurCPPLib.dll!boost::shared_mutex::unlock_upgrade_and_lock() Line 478 + 0x11 bytes C++ 
OurCPPLib.dll!boost::unique_lock<boost::shared_mutex>::unique_lock<boost::shared_mutex>(boost::detail::thread_move_t<boost::upgrade_lock<boost::shared_mutex> > other) Line 788 C++ 
OurCPPLib.dll!boost::upgrade_to_unique_lock<boost::shared_mutex>::upgrade_to_unique_lock<boost::shared_mutex>(boost::upgrade_lock<boost::shared_mutex> & m_) Line 802 + 0x98 bytes C++ 
OurCPPLib.dll!OurClass::SetSomething(double something) Line 95 C++ 

The class defines a number of Get and Set methods (readers/writers) and implements them like so:

boost::shared_mutex _calcSharedMutex;

RETURNCODE GetSomething(double& something)
{
    boost::shared_lock<boost::shared_mutex> lock(_calcSharedMutex);
    return _anotherObject->GetSomething(something);
}

RETURNCODE SetSomething(double something)
{
    boost::upgrade_lock<boost::shared_mutex> lock(_calcSharedMutex);
    boost::upgrade_to_unique_lock<boost::shared_mutex> uniqueLock(lock);
    return _anotherObject->SetSomething(something);
}

The call to _anotherObject->GetSomething() will throw an exception in a rare condition:

throw std::invalid_argument("Unknown something");

Also, there are some calls within getters to _anotherObject->GetSomething() that are made within a try/catch in the C++ library itself, preventing the exception from going back to the managed code, and that does not cause this deadlock. Does the unhandled exception break the boost mutex scope unlocking?

Thanks in advance to anyone that may have some insight!

2
Is this code compiled with /clr in effect? If it is, did you put #pragma managed(push, off) before it?Hans Passant
The c++ is not compiled with the /clr switch.roken
Well that's odd, this should work. Beyond a demo program that actually shows the problem, the only other option you have is to compile that C++ code with /EHa. Although it doesn't make sense that that would work.Hans Passant

2 Answers

2
votes

It is unspecified in C++ whether the stack is unwound when an unhandled exception is thrown. Some implementations do so (invoking destructors and everything else that should happen), while others don't. I don't know exactly how C++/CLI handles this, but if the C++ part sees the exception as being unhandled, then it is possible that it won't unwind the stack, and thus won't call the destructor and release the mutex.

(if so, simply catching and rethrowing the exception in the C++ code should solve the problem)

But this is just a guess. I've never used C++/CLI much, and I have no clue how exceptions are propagated across between native and managed code.

0
votes

There is a bug in the 2.0 CLR that prevents the stack from unwinding when the native-born exception is handled in managed code.

Microsoft Connect: /Ehsc & /Eha & stack unwinding

Switching the managed executable to run on the 4.0 CLR corrected the problem.

As a side note, the native C++ library was being called from a managed C# library which was targeting the 2.0 CLR. The managed assembly can continue to target the 2.0 CLR since it will execute on the bug-free CLR of the executable (4.0), but it will be executed in a 2.0 compatability mode.