ExplodingSoap ExplodingSoap - 1 month ago 10
C++ Question

Calling method on singleton before initialization finished

When converting an old Visual Studio 2003 project to 2015, I ran into an issue where the application freezes right after start. I can't seem to get much information out of the debugger because the application is not really crashing.

When I pause the debugger, it always points to the same line of code, the static variable in a singleton GetInstance method. This makes it look like the app is waiting for it to be initialized. The constructor of this object is calling a method that uses this same GetInstance method, so the instance is being used before the constructor ends.

This is how the code works:

//A.cpp
A::A()
{
B::GetInstance()->DoSomething();
}

A* A::GetInstance()
{
static A instance; // The debugger always points out that this line is next to be executed after pausing the application
return &instance;
}

//B.cpp
void B::DoSomething()
{
A::GetInstance()->DoSomethingElse();
}


I understand that this code can be improved and there are ways to work around it, but I'm wondering why this code worked fine in Visual Studio 2003 and breaks in Visual Studio 2015.

Thanks in advance.

Answer

@Lehu was mostly right in their deleted answer. It's a circular reference that would go recursive if not blocked by a mutex. Here's what happens:

  1. B::DoSomething() called
  2. B::DoSomething() invokes A::GetInstance()
  3. A::GetInstance() calls constructor A::A() to construct static A instance. This locks a critical section around the creation of instance to make sure the job can be finished without interruptions.
  4. A::A() invokes B::DoSomething(); See the circle forming?
  5. B::DoSomething() invokes A::GetInstance()
  6. A::GetInstance() attempts to access instance, but instance has not yet finished constructing, so execution halts until it can.

Unfortunately step 3 can't complete until step 6 completes This is a classic deadlock: 3 is waiting for 6 to complete and 6 is waiting for 3 to complete. To Quote Pvt. Hudson, "Game over, man! Game over!"

Edit: Thinking this over, mutex isn't quite the right term. A Crtitcal Section around acquiring the static variable instance is more appropriate.

My cut of an MCVE to demonstrate:

struct A
{
    A();
    static A* GetInstance()
    {
        static A instance; 
        return &instance;
    }
    void DoSomethingElse()
    {

    }
};
struct B
{
    void DoSomething()
    {
        A::GetInstance()->DoSomethingElse();
    }
    static B* GetInstance()
    {
        static B instance; 
        return &instance;
    }
};

A::A()
{
    B::GetInstance()->DoSomething();
}


int main()
{
    B::GetInstance()->DoSomething();
}