Open
Description
Prerequisites
- Put an X between the brackets on this line if you have checked that your issue isn't already filed: https://github.com/search?l=&q=repo%3Aetr%2Flibhttpserver&type=Issues
Description
The documentation states that All functions are guaranteed to be completely reentrant and thread-safe (unless differently specified)
. However, the webserver
implementation does not use any protection while reading/modifying its member variables (registered_resources
, registered_resources_str
, bans
and allowances
).
Steps to Reproduce
Here is a small test that show the issue when compiled using clang-16 with thread sanitizing enabled:
LT_BEGIN_AUTO_TEST(basic_suite, thread_safety)
simple_resource resource;
std::atomic_bool done = false;
auto register_thread = std::thread([&]() {
int i = 0;
using namespace std::chrono;
while (!done) {
ws->register_resource(
std::string("/route") + std::to_string(++i), &resource);
}
});
auto get_thread = std::thread([&](){
while (!done) {
CURL *curl = curl_easy_init();
std::string s;
std::string url = "localhost:" PORT_STRING "/route" + std::to_string(
(int)((rand() * 10000000.0) / RAND_MAX));
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s);
curl_easy_perform(curl);
curl_easy_cleanup(curl);
}
});
using namespace std::chrono_literals;
std::this_thread::sleep_for(10s);
done = true;
if (register_thread.joinable()) {
register_thread.join();
}
if (get_thread.joinable()) {
get_thread.join();
}
LT_CHECK_EQ(1, 1);
LT_END_AUTO_TEST(thread_safety)
Expected behavior:
No data races are detected by clang.
Actual behavior:
Clang reports the following data race (and others):
Running test (1): thread_safety
==================
WARNING: ThreadSanitizer: data race (pid=21795)
Read of size 8 at 0x7b1400003550 by thread T1:
#0 memcmp /clang-16.0.1/projects/compiler-rt/lib/tsan/rtl/../../sanitizer_common/sanitizer_common_interceptors.inc:939:3 (lt-basic+0x67439)
#1 memcmp /clang-16.0.1/projects/compiler-rt/lib/tsan/rtl/../../sanitizer_common/sanitizer_common_interceptors.inc:935:1 (lt-basic+0x67439)
#2 httpserver::webserver::finalize_answer(MHD_Connection*, httpserver::details::modded_request*, char const*) <null> (libhttpserver.so.0+0x227b2)
#3 MHD_connection_handle_idle <null> (libmicrohttpd.so.12+0xd1ab) (BuildId: 72677d816e65dce550957833f9aea14ac2e0e4c8)
#4 call_handlers daemon.c (libmicrohttpd.so.12+0x118af) (BuildId: 72677d816e65dce550957833f9aea14ac2e0e4c8)
#5 MHD_epoll daemon.c (libmicrohttpd.so.12+0x194ad) (BuildId: 72677d816e65dce550957833f9aea14ac2e0e4c8)
#6 MHD_polling_thread daemon.c (libmicrohttpd.so.12+0x1a02e) (BuildId: 72677d816e65dce550957833f9aea14ac2e0e4c8)
#7 named_thread_starter mhd_threads.c (libmicrohttpd.so.12+0x24515) (BuildId: 72677d816e65dce550957833f9aea14ac2e0e4c8)
Previous write of size 8 at 0x7b1400003550 by thread T2:
#0 operator new(unsigned long) /clang-16.0.1/projects/compiler-rt/lib/tsan/rtl/tsan_new_delete.cpp:64:3 (lt-basic+0xea6e5)
#1 std::pair<std::_Rb_tree_iterator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const, httpserver::http_resource*>>, bool> std::_Rb_tree<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>, std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const, httpserver::http_resource*>, std::_Select1st<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const, httpserver::http_resource*>>, std::less<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>>, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const, httpserver::http_resource*>>>::_M_emplace_unique<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>, httpserver::http_resource*>>(std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>, httpserver::http_resource*>&&) <null> (libhttpserver.so.0+0x2669a)
Location is heap block of size 72 at 0x7b1400003520 allocated by thread T2:
#0 operator new(unsigned long) /clang-16.0.1/projects/compiler-rt/lib/tsan/rtl/tsan_new_delete.cpp:64:3 (lt-basic+0xea6e5)
#1 std::pair<std::_Rb_tree_iterator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const, httpserver::http_resource*>>, bool> std::_Rb_tree<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>, std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const, httpserver::http_resource*>, std::_Select1st<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const, httpserver::http_resource*>>, std::less<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>>, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const, httpserver::http_resource*>>>::_M_emplace_unique<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>, httpserver::http_resource*>>(std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>, httpserver::http_resource*>&&) <null> (libhttpserver.so.0+0x2669a)
Thread T1 'MHD-single' (tid=21814, running) created by main thread at:
#0 pthread_create /clang-16.0.1/projects/compiler-rt/lib/tsan/rtl/tsan_interceptors_posix.cpp:1048:3 (lt-basic+0x2db2f)
#1 MHD_create_thread_ <null> (libmicrohttpd.so.12+0x2443d) (BuildId: 72677d816e65dce550957833f9aea14ac2e0e4c8)
#2 MHD_create_named_thread_ <null> (libmicrohttpd.so.12+0x24602) (BuildId: 72677d816e65dce550957833f9aea14ac2e0e4c8)
#3 MHD_start_daemon_va <null> (libmicrohttpd.so.12+0x1e4de) (BuildId: 72677d816e65dce550957833f9aea14ac2e0e4c8)
#4 MHD_start_daemon <null> (libmicrohttpd.so.12+0x1a1d6) (BuildId: 72677d816e65dce550957833f9aea14ac2e0e4c8)
#5 httpserver::webserver::start(bool) <null> (libhttpserver.so.0+0x20090)
Thread T2 (tid=21815, running) created by main thread at:
#0 pthread_create /clang-16.0.1/projects/compiler-rt/lib/tsan/rtl/tsan_interceptors_posix.cpp:1048:3 (lt-basic+0x2db2f)
#1 std::thread::_M_start_thread(std::unique_ptr<std::thread::_State, std::default_delete<std::thread::_State>>, void (*)()) <null> (libstdc++.so.6+0xed8f9) (BuildId: 06e9553aa6e15b32e77410de83bbdd7d208a620d)
SUMMARY: ThreadSanitizer: data race (/home/florian/work/libhttpserver/build/src/.libs/libhttpserver.so.0+0x227b2) in httpserver::webserver::finalize_answer(MHD_Connection*, httpserver::details::modded_request*, char const*)
==================
- Time spent during "thread_safety": 10702.4 ms
==================
Reproduces how often: 100%
Versions
- OS version : Linux 5f8a17221539 5.19.0-46-generic Cannot register multiple endpoints #47~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Wed Jun 21 15:35:31 UTC 2 x86_64 GNU/Linux
- libhttpserver version: master (d249ba6) compiled locally
- libmicrohttpd version: 0.9.64 compiled locally