Open
Description
The ThreadSanitizer seems to ignore std::shared_timed_mutex
es which have been locked via try_lock_for()
and consequently reports false-positive data races.
Here is a minimal but complete code example:
#include <unistd.h>
#include <iostream>
#include <shared_mutex>
#include <thread>
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) {
std::shared_timed_mutex mx;
int x = 0;
auto t0 = std::thread([&] {
for(size_t i = 0; i < 10; ++i) {
usleep(100000);
auto ok = mx.try_lock_for(std::chrono::seconds(600));
if(!ok) {
throw std::logic_error("No lock!");
}
x++;
mx.unlock();
}
});
for(size_t i = 0; i < 10; ++i) {
usleep(100000);
auto ok = mx.try_lock_for(std::chrono::seconds(600));
if(!ok) {
throw std::logic_error("No lock!");
}
std::cout << x << std::endl;
mx.unlock();
}
t0.join();
}
Compiling this code with TSan enabled and running it will produce the following output:
0
/usr/bin/llvm-symbolizer-18: error: '[stack]': No such file or directory
==================
WARNING: ThreadSanitizer: data race (pid=511614)
Write of size 4 at 0x7fffffffce8c by thread T1:
#0 main::$_0::operator()() const /home/mhier/software/sources/CppPlayground/src/main.cc:19:8 (CppPlayground+0xe006b) (BuildId: dc419eb81c4ef89babd371b15b4ef9c1a4f8715b)
#1 void std::__invoke_impl<void, main::$_0>(std::__invoke_other, main::$_0&&) /usr/bin/../lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/invoke.h:61:14 (CppPlayground+0xdffb5) (BuildId: dc419eb81c4ef89babd371b15b4ef9c1a4f8715b)
#2 std::__invoke_result<main::$_0>::type std::__invoke<main::$_0>(main::$_0&&) /usr/bin/../lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/invoke.h:96:14 (CppPlayground+0xdff85) (BuildId: dc419eb81c4ef89babd371b15b4ef9c1a4f8715b)
#3 void std::thread::_Invoker<std::tuple<main::$_0>>::_M_invoke<0ul>(std::_Index_tuple<0ul>) /usr/bin/../lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/std_thread.h:292:13 (CppPlayground+0xdff55) (BuildId: dc419eb81c4ef89babd371b15b4ef9c1a4f8715b)
#4 std::thread::_Invoker<std::tuple<main::$_0>>::operator()() /usr/bin/../lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/std_thread.h:299:11 (CppPlayground+0xdff25) (BuildId: dc419eb81c4ef89babd371b15b4ef9c1a4f8715b)
#5 std::thread::_State_impl<std::thread::_Invoker<std::tuple<main::$_0>>>::_M_run() /usr/bin/../lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/std_thread.h:244:13 (CppPlayground+0xdfe49) (BuildId: dc419eb81c4ef89babd371b15b4ef9c1a4f8715b)
#6 execute_native_thread_routine /build/gcc-14-ig5ci0/gcc-14-14.2.0/build/x86_64-linux-gnu/libstdc++-v3/src/c++11/../../../../../src/libstdc++-v3/src/c++11/thread.cc:104:18 (libstdc++.so.6+0xecdb3) (BuildId: ca77dae775ec87540acd7218fa990c40d1c94ab1)
Previous read of size 4 at 0x7fffffffce8c by main thread:
#0 main /home/mhier/software/sources/CppPlayground/src/main.cc:30:18 (CppPlayground+0xdfbfd) (BuildId: dc419eb81c4ef89babd371b15b4ef9c1a4f8715b)
As if synchronized via sleep:
#0 usleep <null> (CppPlayground+0x5dbca) (BuildId: dc419eb81c4ef89babd371b15b4ef9c1a4f8715b)
#1 main::$_0::operator()() const /home/mhier/software/sources/CppPlayground/src/main.cc:14:7 (CppPlayground+0xe0019) (BuildId: dc419eb81c4ef89babd371b15b4ef9c1a4f8715b)
#2 void std::__invoke_impl<void, main::$_0>(std::__invoke_other, main::$_0&&) /usr/bin/../lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/invoke.h:61:14 (CppPlayground+0xdffb5) (BuildId: dc419eb81c4ef89babd371b15b4ef9c1a4f8715b)
#3 std::__invoke_result<main::$_0>::type std::__invoke<main::$_0>(main::$_0&&) /usr/bin/../lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/invoke.h:96:14 (CppPlayground+0xdff85) (BuildId: dc419eb81c4ef89babd371b15b4ef9c1a4f8715b)
#4 void std::thread::_Invoker<std::tuple<main::$_0>>::_M_invoke<0ul>(std::_Index_tuple<0ul>) /usr/bin/../lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/std_thread.h:292:13 (CppPlayground+0xdff55) (BuildId: dc419eb81c4ef89babd371b15b4ef9c1a4f8715b)
#5 std::thread::_Invoker<std::tuple<main::$_0>>::operator()() /usr/bin/../lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/std_thread.h:299:11 (CppPlayground+0xdff25) (BuildId: dc419eb81c4ef89babd371b15b4ef9c1a4f8715b)
#6 std::thread::_State_impl<std::thread::_Invoker<std::tuple<main::$_0>>>::_M_run() /usr/bin/../lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/bits/std_thread.h:244:13 (CppPlayground+0xdfe49) (BuildId: dc419eb81c4ef89babd371b15b4ef9c1a4f8715b)
#7 execute_native_thread_routine /build/gcc-14-ig5ci0/gcc-14-14.2.0/build/x86_64-linux-gnu/libstdc++-v3/src/c++11/../../../../../src/libstdc++-v3/src/c++11/thread.cc:104:18 (libstdc++.so.6+0xecdb3) (BuildId: ca77dae775ec87540acd7218fa990c40d1c94ab1)
Location is stack of main thread.
Location is global '??' at 0x7ffffffdd000 ([stack]+0x1fe8c)
Thread T1 (tid=511616, running) created by main thread at:
#0 pthread_create <null> (CppPlayground+0x6083b) (BuildId: dc419eb81c4ef89babd371b15b4ef9c1a4f8715b)
#1 __gthread_create /build/gcc-14-ig5ci0/gcc-14-14.2.0/build/x86_64-linux-gnu/libstdc++-v3/include/x86_64-linux-gnu/bits/gthr-default.h:676:35 (libstdc++.so.6+0xeceb0) (BuildId: ca77dae775ec87540acd7218fa990c40d1c94ab1)
#2 std::thread::_M_start_thread(std::unique_ptr<std::thread::_State, std::default_delete<std::thread::_State>>, void (*)()) /build/gcc-14-ig5ci0/gcc-14-14.2.0/build/x86_64-linux-gnu/libstdc++-v3/src/c++11/../../../../../src/libstdc++-v3/src/c++11/thread.cc:172:37 (libstdc++.so.6+0xeceb0)
#3 main /home/mhier/software/sources/CppPlayground/src/main.cc:12:13 (CppPlayground+0xdfb95) (BuildId: dc419eb81c4ef89babd371b15b4ef9c1a4f8715b)
SUMMARY: ThreadSanitizer: data race /home/mhier/software/sources/CppPlayground/src/main.cc:19:8 in main::$_0::operator()() const
==================
1
2
3
4
5
6
7
8
9
ThreadSanitizer: reported 1 warnings
The above output has been generated with clang 18.1.3 on Ubuntu but the issue can be reproduced also with the latest clang 21.1 installed through https://apt.llvm.org/llvm.sh
. In all cases I have been compiling and running this code on Ubuntu 24.04, hence I am using libstdc++ rather than libc++.
The problem goes away when:
- using
mx.lock()
instead oftry_lock_for()
, - changing the
shared_timed_mutex
into atimed_mutex
(despite we always lock the mutex exclusively!) - changing to the boost equivalent
boost::shared_mutex
.