easy_profiler icon indicating copy to clipboard operation
easy_profiler copied to clipboard

Use thread_local instead of static in EASY_LOCAL_STATIC_PTR.

Open madevgeny opened this issue 5 years ago • 12 comments

Hi, please look at my changes, without them it's impossible to profile functions called from multiple threads.

madevgeny avatar Oct 02 '18 16:10 madevgeny

Hi
Which compiler do you use?
The branch you have changed should fire only for compilers with support of c++11 thread-safe statics.

cas4ey avatar Oct 02 '18 16:10 cas4ey

Hi Which compiler do you use? The branch you have changed should fire only for compilers with support of c++11 thread-safe statics.

I use visual studio 2015.

I have the following case: there is method

void ed::PoolTask::run() { EASY_BLOCK(id.c_str()); // it's unique for each object. func(); EASY_END_BLOCK; }

Which is called from many threads. So if you use static variable it'll be changed in the middle of profiling. It appears under relatively heavy load.

madevgeny avatar Oct 02 '18 17:10 madevgeny

I think this issue isn't compiler specific.

madevgeny avatar Oct 02 '18 17:10 madevgeny

C++11 standard guarantees that local static initialization is thread-safe. And this static in EASY_BLOCK doesn't change after it is initialized (and additionally it's initialization is performed with a thread-safe method).

I think your problem is related to the usage of id.c_str() - you must ensure that it will exist and will not change during EASY_BLOCK execution. Try to replace id.c_str() with a compie-time string and if it is OK, then check again that id does not change during block execution.

cas4ey avatar Oct 03 '18 06:10 cas4ey

For example:

void ed::PoolTask::run()
{
    const std::string local_id = id; // try to use temporary buffer
    EASY_BLOCK(local_id.c_str());
    func();
    // EASY_END_BLOCK; // this is not necessary because EASY_BLOCK using RAII
}

cas4ey avatar Oct 03 '18 06:10 cas4ey

Hi @madevgeny
Any news?

cas4ey avatar Oct 04 '18 10:10 cas4ey

Hi, it appears that we use /Zc:threadSafeInit- flag which disables thread safe statics. We need it for our protection system and unfortunately we will not be able to change it in near future.

I attached simple example to reproduce crash. But with my patch it works without crash.

Another thought. EASY_BLOCK unrolls to something like this:

static const ::profiler::BaseBlockDescriptor* desc = ::profiler::registerDescription("unique arguments"); ... other code

if we call the same function from multiple threads but with different "unique arguments" we will write to desc pointers to different objects. So there is no guarantee that after writing pointer to desc we will have read the same pointer from it. As static variable desc can be changed from other threads. tread_local solves this problem too.

easy_crash.zip

madevgeny avatar Oct 04 '18 15:10 madevgeny

For example:

void ed::PoolTask::run()
{
    const std::string local_id = id; // try to use temporary buffer
    EASY_BLOCK(local_id.c_str());
    func();
    // EASY_END_BLOCK; // this is not necessary because EASY_BLOCK using RAII
}

No, id is member of PoolTask class.

madevgeny avatar Oct 04 '18 15:10 madevgeny

Hi, it appears that we use /Zc:threadSafeInit- flag which disables thread safe statics. We need it for our protection system and unfortunately we will not be able to change it in near future.

Well, this explains the problem :-)

If this flag could be checked at compile-time in source code then the fix will be:
(I don't know is there _MSC_ZC_THREAD_SAFE_INIT or something like this or not)

#ifndef EASY_LOCAL_STATIC_PTR
# if defined(_MSC_VER) && defined(_MSC_ZC_THREAD_SAFE_INIT) // Something like this
#  define EASY_LOCAL_STATIC_PTR(VarType, VarName, VarInitializer) thread_local static VarType ...
# else // This is existing branch:
#  define EASY_LOCAL_STATIC_PTR(VarType, VarName, VarInitializer) static VarType ...
#  define EASY_MAGIC_STATIC_AVAILABLE
#endif

If it's impossible to check the flag in source code then you will have to add new CMake option into easy_profiler_core/CMakeLists.txt and check it in the same way (the code would be exactly the same like in example above).

cas4ey avatar Oct 04 '18 16:10 cas4ey

It would be great if you could fix it :-)

cas4ey avatar Oct 04 '18 16:10 cas4ey

There is no macros to detect /Zc:threadSafeInit- at compile time.

I'll add option to easy_profiler_core/CMakeLists.txt.

madevgeny avatar Oct 04 '18 18:10 madevgeny

Hi, I added EASY_OPTION_THREAD_SAFE_INIT option and generation of config header.

madevgeny avatar Oct 05 '18 15:10 madevgeny