[cfe-dev] [cxxabi] Thread-safe statics causing deadlocks

Craig, Ben via cfe-dev cfe-dev at lists.llvm.org
Wed Jul 20 12:43:27 PDT 2016


C++14 6.7 Declaration statement, clause 4 has the standardese for 
"Magic" / thread-safe statics.  Footnote 91 says "The implementation 
must not introduce any deadlock around execution of the initializer."  I 
believe this is unimplementable.  The standard (and users) require 
mutual exclusion (though not necessarily a mutex) to be provided over 
unknown / arbitrary code.  This causes well known problems ( 
http://www.drdobbs.com/cpp/avoid-calling-unknown-code-while-inside/202802983 
).

Libcxxabi, libsupc++, and the Microsoft implementation all have 
deadlocks in released compilers.  I have two examples, one heavily 
contrived, and the other lightly contrived.

*Heavily contrived example:
*In the following code, the static A2 can cause the static B2 to be 
constructed, and B2 can cause the static A2 to be constructed.  A bool 
is passed along to prevent recursion.  This leads to the classic "deadly 
embrace", where each thread is waiting for a resource from the other 
thread to be released.  The sleeps have been added to make the race 
condition more likely to trigger.  No user data is racing in this 
example.  There would be a hidden data race on the "is initialized" flag 
on each of the statics, except that that is one of the races that 
thread-safe statics is supposed to fix.

#include <thread>

using namespace std::chrono_literals;

void aMaker(bool MakeB);
void bMaker(bool MakeA);

struct SlowA {
   explicit SlowA(bool MakeB) {
     std::this_thread::sleep_for(2s);
     if(MakeB) bMaker(false);
   }
};

struct FastB {
   explicit FastB(bool MakeA) {
     if(MakeA) aMaker(false);
   }
};

void aMaker(bool MakeB) {  static SlowA A2(MakeB); };
void bMaker(bool MakeA) {  static FastB B2(MakeA); };

int main() {
   std::thread first( []{aMaker(true);});
   std::this_thread::sleep_for(1s);
   std::thread second([]{bMaker(true);});

   first.join();
   second.join();
}

*Lightly contrived example:
*In the following code, we cause a deadlock with only one user defined 
recursive mutex.  I think this issue could actually affect real code 
bases, though I haven't hit the problem myself.

#include <thread>
#include <mutex>

std::recursive_mutex g_mutex;

struct SlowA {
   explicit SlowA() {
     std::lock_guard<std::recursive_mutex> guard(g_mutex);
   }
};

void aMaker() {
   static SlowA A2;
};

int main() {
   using namespace std::chrono_literals;
   std::thread first([]{
     std::lock_guard<std::recursive_mutex> guard(g_mutex);
     std::this_thread::sleep_for(2s);
     aMaker();
   });
   std::this_thread::sleep_for(1s);
   std::thread second([]{ aMaker(); });

   first.join();
   second.join();
}


I'm not sure what should be done.  Removing the lock protections would 
be terrible.  Banning the use of locks in functions that construct 
statics would be terrible.  Banning the use of locks in functions called 
from static construction would be terrible.  It would be embarrassing to 
change the footnote in the standard to say that the language is 
permitted (even required) to introduce deadlocks.

-- 
Employee of Qualcomm Innovation Center, Inc.
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, a Linux Foundation Collaborative Project

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/cfe-dev/attachments/20160720/18192714/attachment.html>


More information about the cfe-dev mailing list