Mutex lock in C++ destructor causes exception when exit() called in Python
I have an application that loads a DLL with a class that handles jobs in a queue. In order to keep it thread safe, a mutex is locked whenever the queue is modified. When the application exits and the destructor is called, the mutex is locked in order to clear the queue.
However, when I load this DLL in Python, create an instance of the object, and call exit()
(in Python) an exception is thrown when the mutex tries to lock:
Microsoft Visual Studio C Runtime Library has detected a fatal error in python.exe.
I have simplified the destructor down to just creating a mutex locally and trying to lock it, and can still reproduce the issue:
QueueHandler::~QueueHandler(void)
{
mutex mut; // in reality, this is a member of the class and there are actual operations between lock and unlock
mut.lock(); // exception here
mut.unlock();
}
If I take my unmodified code and simply remove the lock around the queue operation, it works fine.
Here is the seemingly relevant section of the call stack:
KernelBase.dll!RaiseException() Unknown
msvcr120.dll!_CxxThrowException(void * pExceptionObject, const _s__ThrowInfo * pThrowInfo) Line 154 C++
msvcr120.dll!Concurrency::details::SchedulerBase::SchedulerBase(const Concurrency::SchedulerPolicy & policy) Line 149 C++
msvcr120.dll!Concurrency::details:: SchedulerBase::CreateWithoutInitializing(const Concurrency::SchedulerPolicy & policy) Line 285 C++
msvcr120.dll!Concurrency::details:: SchedulerBase::CreateContextFromDefaultScheduler() Line 571 C++
msvcr120.dll!Concurrency::details::SchedulerBase::CurrentContext() Line 404 C++
[Inline Frame] msvcr120.dll!Concurrency::details::LockQueueNode::{ctor (unsigned int) Line 619 C++
msvcr120.dll!Concurrency::critical_section::lock() Line 1031 C++
msvcp120.dll!mtx_do_lock(_Mtx_internal_imp_t * * mtx, const xtime * target) Line 67 C++
--> MyApplication.dll!QueueHandler::~QueueHandler() Line 106 C++
MyApplication.dll!_CRT_INIT(void * hDllHandle, unsigned long dwReason, void * lpreserved) Line 416 C
MyApplication.dll!__DllMainCRTStartup(void * hDllHandle, unsigned long dwReason, void * lpreserved) Line 522 C
ntdll.dll!LdrShutdownProcess() Unknown
ntdll.dll!RtlExitUserProcess() Unknown
msvcr100.dll!doexit(int code, int quick, int retcaller) Line 621 C
python27.dll!000000001e13be65() Unknown
...
python27.dll!000000001e043494() Unknown
python.exe!000000001d00119e() Unknown
Questions:
exit()
from Pyton? Edit: MCVE: QueueHandlerApp - Run the app or run script.py to demonstrate the issue.
Some people, when confronted with a problem, think, "I know, I'll use lazy initialization." Now they have two problems.
This is a bug in MSVC implementation of std::mutex
. Before MSVC14 std::mutex
and std::condition_variable
would perform some internal initializations lazily. This alone is bad, but becomes even worse due to how modules are deinitialized on Windows.
The bug was fixed in MSVC14 (Visual Studio 2015) - std::mutex
was rewritten to use SRWLock
internally. SRWLock
is a simple primitive without additional dependencies. It relies only on atomic instructions and Keyed Events syscalls. As the kernel is isolated from the userspace, SRWLock
's should work seemlessly regardless of where they are used.
Seems like you are using MSVC12 (Visual Studio 2013). You should switch to MSVC14 (Visual Studio 2015) or use Boost.Thread instead.
There are actually many issues with std::mutex
on MSVC12 and earlier. Some are related to the actual implementation used in CRT, others (as I heard) are caused by bugs in Windows 7 and were fixed in Windows 8.