Skip to content

Commit 356bac1

Browse files
etrclaude
andcommitted
CI: fix lint copyright + tsan races on PR #374
Three CI failures on feature/v2.0 PR #374 run 26183259463: 1. cpplint: examples/hello_world.cpp was missing the copyright line. Added single-line copyright header (the file is the deliberately minimal lambda-form example, so the full LGPL block would defeat its purpose). 2. tsan ws_start_stop: webserver::stop() and is_running() read impl_->running with no lock while start() writes it from the blocking-server thread. Made the field std::atomic<bool> — fixes the genuine race without changing the mutex/cond_var discipline that gates the blocking wait. 3. tsan route_table_concurrency + threadsafety_stress: libstdc++'s std::ctype<char>::narrow lazily fills a 256-byte cache; the guard flag is not atomic so concurrent std::regex compiles inside http_endpoint::http_endpoint look like a race even though every initialiser computes the same bytes. Added test/tsan.supp scoped to that one libstdc++ symbol pair, plumbed via TSAN_OPTIONS only on the tsan matrix lane, and shipped via test/Makefile.am EXTRA_DIST. Libhttpserver-internal races stay fatal. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 9c61716 commit 356bac1

5 files changed

Lines changed: 26 additions & 1 deletion

File tree

.github/workflows/verify-build.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -777,6 +777,11 @@ jobs:
777777
- name: Run tests
778778
run: |
779779
cd build ;
780+
# tsan: suppress libstdc++'s benign std::ctype<char> narrow-cache race
781+
# (see test/tsan.supp). All libhttpserver-internal races stay fatal.
782+
if [ "$BUILD_TYPE" = "tsan" ]; then
783+
export TSAN_OPTIONS="suppressions=$(pwd)/../test/tsan.supp" ;
784+
fi
780785
make check;
781786
# TASK-037: flag-invariance-{on,off} are compile/link-only gates, so
782787
# skip the full `make check` (which spins up real servers and would

examples/hello_world.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// Copyright 2026 Sebastiano Merlino
12
// libhttpserver hello-world example — the lambda form (PRD §3.4).
23
// Compiles in ten lines including main(), with no http_resource subclass
34
// and no raw-pointer ownership. Production code typically qualifies names

src/httpserver/detail/webserver_impl.hpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
#include <stdarg.h>
4343

4444
#include <array>
45+
#include <atomic>
4546
#include <cstddef>
4647
#include <cstring>
4748
#include <list>
@@ -183,7 +184,10 @@ class webserver_impl {
183184
pthread_mutex_t mutexwait;
184185
pthread_cond_t mutexcond;
185186

186-
bool running = false;
187+
// Atomic to allow lock-free reads in stop()/is_running() concurrent
188+
// with the mutex-guarded writes in start()/stop(). TSan-flagged in the
189+
// ws_start_stop integ test (start on worker thread, stop on main).
190+
std::atomic<bool> running{false};
187191

188192
// TASK-023: route-table entries hold shared_ptr<http_resource>.
189193
// The two public register_resource overloads (unique_ptr / shared_ptr)

test/Makefile.am

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,7 @@ TESTS = $(check_PROGRAMS)
344344
@VALGRIND_CHECK_RULES@
345345
VALGRIND_SUPPRESSIONS_FILES = libhttpserver.supp
346346
EXTRA_DIST = libhttpserver.supp \
347+
tsan.supp \
347348
PERFORMANCE.md \
348349
v1_baseline/README.md \
349350
v1_baseline/v1_constants.hpp \

test/tsan.supp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# ThreadSanitizer suppressions for libhttpserver tests.
2+
#
3+
# Each entry below is a documented libstdc++ benign race; suppress at the
4+
# library boundary only — never inside libhttpserver code. New races in
5+
# libhttpserver itself MUST be fixed, not suppressed.
6+
7+
# libstdc++ lazily fills std::ctype<char>::_M_narrow_ok / _M_narrow on the
8+
# first call to narrow() (commonly the first std::regex compile). The cache
9+
# write is idempotent — every initialiser computes the same 256-byte table —
10+
# but the guard flag is not declared atomic, so TSan flags concurrent writers
11+
# even though the resulting state is identical. Reported as a libstdc++ bug
12+
# upstream (gcc bug #58938 and related); benign in practice.
13+
race:std::ctype<char>::narrow
14+
race:std::ctype<char>::_M_narrow_init

0 commit comments

Comments
 (0)