dougbinks / enkiTS

A permissively licensed C and C++ Task Scheduler for creating parallel programs. Requires C++11 support.
zlib License
1.74k stars 145 forks source link

Race Condition? #120

Closed erincatto closed 7 months ago

erincatto commented 7 months ago

I ran thread sanitizer on TestAll and got this report:

WARNING: ThreadSanitizer: data race (pid=36794)
  Write of size 4 at 0x000108c7690c by main thread:
    #0 enki::TaskScheduler::StartThreads() TaskScheduler.cpp:368 (TestAll:arm64+0x10002bee8)
    #1 enki::TaskScheduler::Initialize(enki::TaskSchedulerConfig) TaskScheduler.cpp:1304 (TestAll:arm64+0x10003147c)
    #2 main::$_8::operator()() const TestAll.cpp:502 (TestAll:arm64+0x10002a620)
    #3 decltype(std::declval<main::$_8&>()()) std::__1::__invoke[abi:v160006]<main::$_8&>(main::$_8&) invoke.h:394 (TestAll:arm64+0x10002a584)
    #4 bool std::__1::__invoke_void_return_wrapper<bool, false>::__call<main::$_8&>(main::$_8&) invoke.h:478 (TestAll:arm64+0x10002a4dc)
    #5 std::__1::__function::__alloc_func<main::$_8, std::__1::allocator<main::$_8>, bool ()>::operator()[abi:v160006]() function.h:185 (TestAll:arm64+0x10002a47c)
    #6 std::__1::__function::__func<main::$_8, std::__1::allocator<main::$_8>, bool ()>::operator()() function.h:356 (TestAll:arm64+0x100028724)
    #7 std::__1::__function::__value_func<bool ()>::operator()[abi:v160006]() const function.h:510 (TestAll:arm64+0x10000431c)
    #8 std::__1::function<bool ()>::operator()() const function.h:1156 (TestAll:arm64+0x100001dfc)
    #9 RunTestFunction(char const*, std::__1::function<bool ()>) TestAll.cpp:44 (TestAll:arm64+0x100001c9c)
    #10 main TestAll.cpp:498 (TestAll:arm64+0x1000027ec)

  Previous write of size 4 at 0x000108c7690c by thread T55:
    #0 enki::TaskScheduler::TryRunTask(unsigned int, unsigned int, unsigned int&) TaskScheduler.cpp:577 (TestAll:arm64+0x10002d8b0)
    #1 enki::TaskScheduler::TryRunTask(unsigned int, unsigned int&) TaskScheduler.cpp:516 (TestAll:arm64+0x10002b6c4)
    #2 enki::TaskScheduler::TaskingThreadFunction(enki::ThreadArgs const&) TaskScheduler.cpp:294 (TestAll:arm64+0x10002b47c)
    #3 decltype(std::declval<void (*)(enki::ThreadArgs const&)>()(std::declval<enki::ThreadArgs>())) std::__1::__invoke[abi:v160006]<void (*)(enki::ThreadArgs const&), enki::ThreadArgs>(void (*&&)(enki::ThreadArgs const&), enki::ThreadArgs&&) invoke.h:394 (TestAll:arm64+0x100034644)
    #4 void std::__1::__thread_execute[abi:v160006]<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct>>, void (*)(enki::ThreadArgs const&), enki::ThreadArgs, 2ul>(std::__1::tuple<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct>>, void (*)(enki::ThreadArgs const&), enki::ThreadArgs>&, std::__1::__tuple_indices<2ul>) thread:288 (TestAll:arm64+0x1000345d0)
    #5 void* std::__1::__thread_proxy[abi:v160006]<std::__1::tuple<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct>>, void (*)(enki::ThreadArgs const&), enki::ThreadArgs>>(void*) thread:299 (TestAll:arm64+0x100033fac)

  Location is heap block of size 512 at 0x000108c76800 allocated by main thread:
    #0 posix_memalign <null>:85642884 (libclang_rt.tsan_osx_dynamic.dylib:arm64e+0x564c0)
    #1 enki::DefaultAllocFunc(unsigned long, unsigned long, void*, char const*, int) TaskScheduler.cpp:208 (TestAll:arm64+0x10002aa90)
    #2 enki::ThreadDataStore* enki::TaskScheduler::NewArray<enki::ThreadDataStore>(unsigned long, char const*, int) TaskScheduler.cpp:1213 (TestAll:arm64+0x10002c4ec)
    #3 enki::TaskScheduler::StartThreads() TaskScheduler.cpp:339 (TestAll:arm64+0x10002bb64)
    #4 enki::TaskScheduler::Initialize(enki::TaskSchedulerConfig) TaskScheduler.cpp:1304 (TestAll:arm64+0x10003147c)
    #5 main::$_8::operator()() const TestAll.cpp:502 (TestAll:arm64+0x10002a620)
    #6 decltype(std::declval<main::$_8&>()()) std::__1::__invoke[abi:v160006]<main::$_8&>(main::$_8&) invoke.h:394 (TestAll:arm64+0x10002a584)
    #7 bool std::__1::__invoke_void_return_wrapper<bool, false>::__call<main::$_8&>(main::$_8&) invoke.h:478 (TestAll:arm64+0x10002a4dc)
    #8 std::__1::__function::__alloc_func<main::$_8, std::__1::allocator<main::$_8>, bool ()>::operator()[abi:v160006]() function.h:185 (TestAll:arm64+0x10002a47c)
    #9 std::__1::__function::__func<main::$_8, std::__1::allocator<main::$_8>, bool ()>::operator()() function.h:356 (TestAll:arm64+0x100028724)
    #10 std::__1::__function::__value_func<bool ()>::operator()[abi:v160006]() const function.h:510 (TestAll:arm64+0x10000431c)
    #11 std::__1::function<bool ()>::operator()() const function.h:1156 (TestAll:arm64+0x100001dfc)
    #12 RunTestFunction(char const*, std::__1::function<bool ()>) TestAll.cpp:44 (TestAll:arm64+0x100001c9c)
    #13 main TestAll.cpp:498 (TestAll:arm64+0x1000027ec)

  Thread T55 (tid=1451132, running) created by main thread at:
    #0 pthread_create <null>:85642884 (libclang_rt.tsan_osx_dynamic.dylib:arm64e+0x3062c)
    #1 std::__1::__libcpp_thread_create[abi:v160006](_opaque_pthread_t**, void* (*)(void*), void*) __threading_support:378 (TestAll:arm64+0x10000e52c)
    #2 std::__1::thread::thread<void (&)(enki::ThreadArgs const&), enki::ThreadArgs, void>(void (&)(enki::ThreadArgs const&), enki::ThreadArgs&&) thread:315 (TestAll:arm64+0x100033d80)
    #3 std::__1::thread::thread<void (&)(enki::ThreadArgs const&), enki::ThreadArgs, void>(void (&)(enki::ThreadArgs const&), enki::ThreadArgs&&) thread:307 (TestAll:arm64+0x10002c7ec)
    #4 enki::TaskScheduler::StartThreads() TaskScheduler.cpp:360 (TestAll:arm64+0x10002bdec)
    #5 enki::TaskScheduler::Initialize(enki::TaskSchedulerConfig) TaskScheduler.cpp:1304 (TestAll:arm64+0x10003147c)
    #6 main::$_8::operator()() const TestAll.cpp:502 (TestAll:arm64+0x10002a620)
    #7 decltype(std::declval<main::$_8&>()()) std::__1::__invoke[abi:v160006]<main::$_8&>(main::$_8&) invoke.h:394 (TestAll:arm64+0x10002a584)
    #8 bool std::__1::__invoke_void_return_wrapper<bool, false>::__call<main::$_8&>(main::$_8&) invoke.h:478 (TestAll:arm64+0x10002a4dc)
    #9 std::__1::__function::__alloc_func<main::$_8, std::__1::allocator<main::$_8>, bool ()>::operator()[abi:v160006]() function.h:185 (TestAll:arm64+0x10002a47c)
    #10 std::__1::__function::__func<main::$_8, std::__1::allocator<main::$_8>, bool ()>::operator()() function.h:356 (TestAll:arm64+0x100028724)
    #11 std::__1::__function::__value_func<bool ()>::operator()[abi:v160006]() const function.h:510 (TestAll:arm64+0x10000431c)
    #12 std::__1::function<bool ()>::operator()() const function.h:1156 (TestAll:arm64+0x100001dfc)
    #13 RunTestFunction(char const*, std::__1::function<bool ()>) TestAll.cpp:44 (TestAll:arm64+0x100001c9c)
    #14 main TestAll.cpp:498 (TestAll:arm64+0x1000027ec)

SUMMARY: ThreadSanitizer: data race TaskScheduler.cpp:368 in enki::TaskScheduler::StartThreads()

I got this by changing CMakeLists.txt for APPLE.

if( APPLE )
    SET( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++ -fsanitize=thread -fno-omit-frame-pointer" )
endif()
    add_executable( TestAll example/TestAll.cpp )
    target_link_libraries(TestAll enkiTS )
    target_link_options(TestAll PRIVATE -fsanitize=thread)
dougbinks commented 7 months ago

I'll look into resolving this report, but it is harmless - the rndSeed variable has a potential data race during initialization but it isn't critical to the functioning of enkiTS, it's used to help distribute where threads start looking for tasks and will work whether there is a data race or not.

dougbinks commented 7 months ago

I've confirmed this is a data race, but it also harmless.

I've pushed a fix for this to the dev branch, which I'll merge to master after further testing.

Many thanks for the report. It seems thread sanitizer is a lot better at not generating false positives than it used to be, so I'll look into integrating it into testing in future.

dougbinks commented 7 months ago

Many thanks for this report - I've merged this to main now.