ThreadSantizer warning for sqlite3_enable_shared_cache()
(1) By timwoj on 2021-06-28 19:56:29 updated by 1.1 [link] [source]
I'm running into a warning from ThreadSanitizer while using `sqlite3_enable_shared_cache()` between two threads. I've minimized it down to a simple test program below, followed by the back trace from TSan. ``` #include <chrono> #include <thread> #include <sqlite3.h> namespace { void Init() { sqlite3_enable_shared_cache(1); std::this_thread::sleep_for(std::chrono::seconds(2)); } } // namespace int main(int argc, char* argv[]) { std::thread t1(&Init); std::thread t2(&Init); t1.join(); t2.join(); return 0; } ``` With that file in `test.cc` and `sqlite3.{h,c}` in the same directory, I'm building with: ``` clang-11 -fsanitize=thread -g -c sqlite3.c clang-11 -std=c++17 -stdlib=libstdc++ -fsanitize=thread -g -I. -c test.cc clang -lstdc++ -std=c++17 -fsanitize=thread *.o -o warn_test ``` Running `warn_test` results in this report from TSan: ``` ================== WARNING: ThreadSanitizer: data race (pid=123110) Write of size 4 at 0x000000706dac by thread T2: #0 sqlite3_enable_shared_cache /home/tim/test/sqlite3.c:65571:3 (race+0x4c3d8a) #1 (anonymous namespace)::Init() /home/tim/test/test.cc:12:2 (race+0x6a35ea) #2 void std::__invoke_impl<void, void (*)()>(std::__invoke_other, void (*&&)()) /usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/bits/invoke.h:60:14 (race+0x6a49ed) #3 std::__invoke_result<void (*)()>::type std::__invoke<void (*)()>(void (*&&)()) /usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/bits/invoke.h:95:14 (race+0x6a48f0) #4 void std::thread::_Invoker<std::tuple<void (*)()> >::_M_invoke<0ul>(std::_Index_tuple<0ul>) /usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/thread:244:13 (race+0x6a4898) #5 std::thread::_Invoker<std::tuple<void (*)()> >::operator()() /usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/thread:251:11 (race+0x6a4838) #6 std::thread::_State_impl<std::thread::_Invoker<std::tuple<void (*)()> > >::_M_run() /usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/thread:195:13 (race+0x6a461f) #7 <null> <null> (libstdc++.so.6+0xd6de3) Previous write of size 4 at 0x000000706dac by thread T1: #0 sqlite3_enable_shared_cache /home/tim/test/sqlite3.c:65571:3 (race+0x4c3d8a) #1 (anonymous namespace)::Init() /home/tim/test/test.cc:12:2 (race+0x6a35ea) #2 void std::__invoke_impl<void, void (*)()>(std::__invoke_other, void (*&&)()) /usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/bits/invoke.h:60:14 (race+0x6a49ed) #3 std::__invoke_result<void (*)()>::type std::__invoke<void (*)()>(void (*&&)()) /usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/bits/invoke.h:95:14 (race+0x6a48f0) #4 void std::thread::_Invoker<std::tuple<void (*)()> >::_M_invoke<0ul>(std::_Index_tuple<0ul>) /usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/thread:244:13 (race+0x6a4898) #5 std::thread::_Invoker<std::tuple<void (*)()> >::operator()() /usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/thread:251:11 (race+0x6a4838) #6 std::thread::_State_impl<std::thread::_Invoker<std::tuple<void (*)()> > >::_M_run() /usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/thread:195:13 (race+0x6a461f) #7 <null> <null> (libstdc++.so.6+0xd6de3) Location is global 'sqlite3Config' of size 424 at 0x000000706c60 (race+0x000000706dac) Thread T2 (tid=123113, running) created by main thread at: #0 pthread_create <null> (race+0x425ccb) #1 std::thread::_M_start_thread(std::unique_ptr<std::thread::_State, std::default_delete<std::thread::_State> >, void (*)()) <null> (libstdc++.so.6+0xd70a8) #2 main /home/tim/test/test.cc:20:14 (race+0x6a34d1) Thread T1 (tid=123112, running) created by main thread at: #0 pthread_create <null> (race+0x425ccb) #1 std::thread::_M_start_thread(std::unique_ptr<std::thread::_State, std::default_delete<std::thread::_State> >, void (*)()) <null> (libstdc++.so.6+0xd70a8) #2 main /home/tim/test/test.cc:19:14 (race+0x6a34a7) SUMMARY: ThreadSanitizer: data race /home/tim/test/sqlite3.c:65571:3 in sqlite3_enable_shared_cache ================== ThreadSanitizer: reported 1 warnings ``` I'm able to fix it by changing `sqlite3GlobalConfig.sharedCacheEnabled` to be an `atomic_int` and #include'ing `<stdatomic.h>` but I understand that's a C11 feature and may not be acceptable. I did try to fix it using `AtomicLoad()` and `AtomicStore()` but for some reason I was getting the same warning from TSan.
ThreadSantizer warning for sqlite3_enable_shared_cache()
(1.1) By timwoj on 2021-06-28 20:57:25 edited from 1.0 [link] [source]
I'm running into a warning from ThreadSanitizer while using sqlite3_enable_shared_cache()
between two threads. I've minimized it down to a simple test program below, followed by the back trace from TSan.
#include <chrono>
#include <thread>
#include <sqlite3.h>
namespace {
void Init() {
sqlite3_enable_shared_cache(1);
std::this_thread::sleep_for(std::chrono::seconds(2));
}
} // namespace
int main(int argc, char* argv[]) {
std::thread t1(&Init);
std::thread t2(&Init);
t1.join();
t2.join();
return 0;
}
With that file in test.cc
and sqlite3.{h,c}
in the same directory, I'm building with:
clang-11 -fsanitize=thread -g -c sqlite3.c
clang-11 -std=c++17 -stdlib=libstdc++ -fsanitize=thread -g -I. -c test.cc
clang-11 -lstdc++ -std=c++17 -fsanitize=thread *.o -o warn_test
Running warn_test
results in this report from TSan:
==================
WARNING: ThreadSanitizer: data race (pid=123110)
Write of size 4 at 0x000000706dac by thread T2:
#0 sqlite3_enable_shared_cache /home/tim/test/sqlite3.c:65571:3 (race+0x4c3d8a)
#1 (anonymous namespace)::Init() /home/tim/test/test.cc:12:2 (race+0x6a35ea)
#2 void std::__invoke_impl<void, void (*)()>(std::__invoke_other, void (*&&)()) /usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/bits/invoke.h:60:14 (race+0x6a49ed)
#3 std::__invoke_result<void (*)()>::type std::__invoke<void (*)()>(void (*&&)()) /usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/bits/invoke.h:95:14 (race+0x6a48f0)
#4 void std::thread::_Invoker<std::tuple<void (*)()> >::_M_invoke<0ul>(std::_Index_tuple<0ul>) /usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/thread:244:13 (race+0x6a4898)
#5 std::thread::_Invoker<std::tuple<void (*)()> >::operator()() /usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/thread:251:11 (race+0x6a4838)
#6 std::thread::_State_impl<std::thread::_Invoker<std::tuple<void (*)()> > >::_M_run() /usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/thread:195:13 (race+0x6a461f)
#7 <null> <null> (libstdc++.so.6+0xd6de3)
Previous write of size 4 at 0x000000706dac by thread T1:
#0 sqlite3_enable_shared_cache /home/tim/test/sqlite3.c:65571:3 (race+0x4c3d8a)
#1 (anonymous namespace)::Init() /home/tim/test/test.cc:12:2 (race+0x6a35ea)
#2 void std::__invoke_impl<void, void (*)()>(std::__invoke_other, void (*&&)()) /usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/bits/invoke.h:60:14 (race+0x6a49ed)
#3 std::__invoke_result<void (*)()>::type std::__invoke<void (*)()>(void (*&&)()) /usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/bits/invoke.h:95:14 (race+0x6a48f0)
#4 void std::thread::_Invoker<std::tuple<void (*)()> >::_M_invoke<0ul>(std::_Index_tuple<0ul>) /usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/thread:244:13 (race+0x6a4898)
#5 std::thread::_Invoker<std::tuple<void (*)()> >::operator()() /usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/thread:251:11 (race+0x6a4838)
#6 std::thread::_State_impl<std::thread::_Invoker<std::tuple<void (*)()> > >::_M_run() /usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/thread:195:13 (race+0x6a461f)
#7 <null> <null> (libstdc++.so.6+0xd6de3)
Location is global 'sqlite3Config' of size 424 at 0x000000706c60 (race+0x000000706dac)
Thread T2 (tid=123113, running) created by main thread at:
#0 pthread_create <null> (race+0x425ccb)
#1 std::thread::_M_start_thread(std::unique_ptr<std::thread::_State, std::default_delete<std::thread::_State> >, void (*)()) <null> (libstdc++.so.6+0xd70a8)
#2 main /home/tim/test/test.cc:20:14 (race+0x6a34d1)
Thread T1 (tid=123112, running) created by main thread at:
#0 pthread_create <null> (race+0x425ccb)
#1 std::thread::_M_start_thread(std::unique_ptr<std::thread::_State, std::default_delete<std::thread::_State> >, void (*)()) <null> (libstdc++.so.6+0xd70a8)
#2 main /home/tim/test/test.cc:19:14 (race+0x6a34a7)
SUMMARY: ThreadSanitizer: data race /home/tim/test/sqlite3.c:65571:3 in sqlite3_enable_shared_cache
==================
ThreadSanitizer: reported 1 warnings
I'm able to fix it by changing sqlite3GlobalConfig.sharedCacheEnabled
to be an atomic_int
and #include'ing <stdatomic.h>
but I understand that's a C11 feature and may not be acceptable. I did try to fix it using AtomicLoad()
and AtomicStore()
but for some reason I was getting the same warning from TSan.
(2) By Richard Hipp (drh) on 2021-06-30 12:16:21 in reply to 1.1 [link] [source]
The documentation says of sqlite3_enable_shared_cache():
This interface is threadsafe on processors where writing a 32-bit integer is atomic.
Perhaps this statement should be enhanced to include the word "only" before "threadsafe". Regardless, I don't think TSAN qualifies as a platform on which writing 32-bit integers is atomic, does it?
(3) By timwoj on 2021-08-09 20:15:27 in reply to 2 [source]
So is this something that can be fixed on the SQLite side? I can disable the call if I'm under ThreadSanitizer, but I'd rather not if it can just be fixed.
(4) By anonymous on 2021-10-08 13:59:52 in reply to 3 [link] [source]
Hello. A random search lead me here, but I figured I can answer this for you since you haven't gotten a response...
The short answer is No. ThreadSanitizer sees a data race and is reporting correctly, however, depending on the situation, that is okay. It's not a false positive, but without the _Atomic keyword, it cannot make the assumption that the operation is indeed atomic.
A fix for this not to show up may require a mutex and locking. This is a super expensive (relatively) change to something that was a single instruction, so there is a very real performance hit here. It's really overkill to use a mutex here.
When writing a threaded application, sometimes we need to access data that another thread may be writing to. However, I may not care if I get the pre-updated value or the post-updated value. The only thing I care is that it is one of them. As long as the operation is atomic, I get what I want, however, by definition, it's still a race condition.
-Jason