C++11 Std::Mutex in Visual Studio 2012 Deadlock When Locked from Dllmain()

C++11 std::mutex in Visual Studio 2012 deadlock when locked from DllMain()

It seems that using QueueUserAPC() to queue initialization is always executed before main() but out of the dreaded loader lock. This looks like a solution to my problem.

EDIT 1

After some testing it seems that the APC method works if I queue the APC from DllMain() but it does not work if I queue the APC from a ctor of a static global instance of a class. IOW, using the APC is not uniformly usable across all possible combinations of compilers and build modes for me.

std::mutex lock hangs when overriding the new operator

The mutex library uses new, and std::mutex is not recursive (i.e. reentrant) by default. A chicken-and-egg problem.

UPDATE As has been pointed out in the comments below, using std::recursive_mutex may work. But the classic C++ problem of the order of static initialization of globals being not well defined remains, as does the danger of outside access to the global mutex (best to put it inside an anonymous namespace.)

UPDATE 2 You may be switching g_systemInitiated to true too early, i.e. before the mutex has had a chance to complete its initialization, and so the "first-time-through" call to malloc() never happens. To force this, you could try replacing the assignment in main() with a call to an initialization function in the allocator module:

namespace {
std::recursive_mutex g_mutex;
bool g_initialized = false;
}
void initialize()
{
g_mutex.lock();
g_initialized = true;
g_mutex.unlock();
}

Why console app hangs when using a shared dll that containg static variable that use mutex?

First, you should post exact contents of the main - with an empty main everything works. Things go south when the ErrorHandler class is being instantiated inside main.

Second, the initialization of your static members occurs inside __DllMainCRTStartup and as stated in the SO question I marked as duplicate, MSDN states that using synchronization primitives from __DllMainCRTStartup can cause a deadlock. A possible solution is to switch to a critical secion.

Is there any way to work around OS loader lock deadlocks caused by third-party libraries?

Since I'm still exploring my options, I wanted to still see if I could implement a solution in pure .NET code without using any native code, for the sake of simplicity. I'm not sure if this is a fool-proof solution yet, because I'm still trying to figure out whether it actually gives me the mutual exclusion I need, or if it just looks like it does.

Any thoughts or comments are welcome.

The relevant part of the code is below. Some notes:

  • The HandleRpcRequest method is called from a thread-pool thread when a new message is received from a remote client
  • This fires off a separate STA thread so that it can make the COM call safely
  • DbRequestProxy is a thin wrapper class around the real COM class I'm using
  • I used a ManualResetEvent (_safeForNewThread) to provide the mutual exclusion. The basic idea is that this event stays unsignaled (blocking other threads) if any one particular thread is about to exit (and hence potentially about to terminate the VB6 runtime). The event is only signaled again after the current thread completely terminates (after the Join call finishes). This way multiple request-handler threads can still execute concurrently unless an existing thread is exiting.

So far, I think this code is correct and guarantees that two threads can't deadlock in the VB6 runtime initialization/termination code anymore, while still allowing them to execute concurrently for most of their execution time, but I could be missing something here.

public class ClientHandler {

private static ManualResetEvent _safeForNewThread = new ManualResetEvent(true);

private void HandleRpcRequest(string request)
{

Thread rpcThread = new Thread(delegate()
{
DbRequestProxy dbRequest = null;

try
{
Thread.BeginThreadAffinity();

string response = null;

// Creates a COM object. The VB6 runtime initializes itself here.
// Other threads can be executing here at the same time without fear
// of a deadlock, because the VB6 runtime lock is re-entrant.

dbRequest = new DbRequestProxy();

// Call the COM object
response = dbRequest.ProcessDBRequest(request);

// Send response back to client
_messenger.Send(Messages.RpcResponse(response), true);
}
catch (Exception ex)
{
_messenger.Send(Messages.Error(ex.ToString()));
}
finally
{
if (dbRequest != null)
{
// Force release of COM objects and VB6 globals
// to prevent a different deadlock scenario with VB6
// and the .NET garbage collector/finalizer threads
dbRequest.Dispose();
}

// Other request threads cannot start right now, because
// we're exiting this thread, which will detach the VB6 runtime
// when the underlying native thread exits

_safeForNewThread.Reset();
Thread.EndThreadAffinity();
}
});

// Make sure we can start a new thread (i.e. another thread
// isn't in the middle of exiting...)

_safeForNewThread.WaitOne();

// Put the thread into an STA, start it up, and wait for
// it to end. If other requests come in, they'll get picked
// up by other thread-pool threads, so we won't usually be blocking anyone
// by doing this (although we are blocking a thread-pool thread, so
// hopefully we don't block for *too* long).

rpcThread.SetApartmentState(ApartmentState.STA);
rpcThread.Start();
rpcThread.Join();

// Since we've joined the thread, we know at this point
// that any DLL_THREAD_DETACH notifications have been handled
// and that the underlying native thread has completely terminated.
// Hence, other threads can safely be started.

_safeForNewThread.Set();

}
}


Related Topics



Leave a reply



Submit