SQLite Forum

ThreadSantizer warning for sqlite3_enable_shared_cache()
Login

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 [link] [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 [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