Skip to content

Commit d7fc123

Browse files
etrclaude
andcommitted
CI: median-gate stress test on non-Linux; helgrind --history-level=full
threadsafety_stress adversarial latency gate: the p95<20x-baseline bound relies on the TASK-080 CPU-pinning stabilisation, which is Linux-only (no working affinity API on macOS/Windows). On the oversubscribed hosted macOS runners the p95 tail is uncontrolled (asymmetric P/E cores, QoS-based ulock wakeups): observed overall_median ~1.1x baseline but p95 ~40x. The median proves the registration algorithm is healthy — only the tail is environmental. Keep the strict p95 gate on Linux (where pinning gives it regression bite) and gate on the overall MEDIAN (10x) on non-Linux: a real O(n) regression shifts the whole distribution incl. median, so the bound still catches it while ignoring the platform tail. Validated locally on macOS (median ratio ~1.02x, passes). p95/p99 still printed as diagnostics. helgrind: override ax_valgrind_check's --history-level=approx default with --history-level=full. approx drops the happens-before history, so helgrind could not see the synchronisation MHD_start_daemon's pthread_create establishes between main-thread pre-start setup and the worker threads reading that immutable-after-start state — producing a flood of benign "data race" reports on libhttpserver frames whose conflicting access was "the start of the thread". full tracks the complete graph and proves those reads ordered, removing the false positives at the source (no suppression of libhttpserver frames, per DR-008). drd residue to follow. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NpysYDDJac63yz2mZKKiDf
1 parent bc08387 commit d7fc123

2 files changed

Lines changed: 38 additions & 0 deletions

File tree

test/Makefile.am

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -689,6 +689,19 @@ TESTS = $(check_PROGRAMS)
689689
# removed when the umbrella went clean.)
690690

691691
@VALGRIND_CHECK_RULES@
692+
# Override the ax_valgrind_check default (--history-level=approx). `approx`
693+
# drops the full happens-before history, so helgrind cannot see the
694+
# synchronisation that MHD_start_daemon()'s pthread_create establishes
695+
# between the main thread's pre-start setup and the worker threads that read
696+
# that (immutable-after-start) state during dispatch. That produced a flood
697+
# of benign "data race" reports on libhttpserver frames (resolve_method_callback,
698+
# get_allowed_methods, method_set::contains, webserver::get_bound_port, ...)
699+
# whose conflicting access was literally "the start of the thread". `full`
700+
# tracks the complete happens-before graph and correctly proves those reads
701+
# are ordered after setup, eliminating the false positives at the source.
702+
# This assignment follows @VALGRIND_CHECK_RULES@, so it wins over the macro's
703+
# conditional (?=) default.
704+
VALGRIND_helgrind_FLAGS = --history-level=full
692705
# TASK-088: helgrind + drd race-detector suppression files join the shared
693706
# VALGRIND_SUPPRESSIONS_FILES list. Suppression blocks are tool-tagged
694707
# (Memcheck:/Helgrind:/drd:), so each tool ignores blocks meant for another;

test/integ/threadsafety_stress.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1058,6 +1058,7 @@ LT_BEGIN_AUTO_TEST(threadsafety_stress_suite,
10581058
// every round's [STATS] line and gate-check the worst observed p95
10591059
// — that mirrors what a real per-CI-run gate would see.
10601060
int64_t worst_p95 = 0;
1061+
int64_t worst_median = 0;
10611062
int64_t worst_baseline = 0;
10621063
int rounds_ran = 0;
10631064
int register_ok_first_round = 0;
@@ -1094,6 +1095,9 @@ LT_BEGIN_AUTO_TEST(threadsafety_stress_suite,
10941095
worst_p95 = r.p95;
10951096
worst_baseline = baseline;
10961097
}
1098+
if (r.median >= worst_median) {
1099+
worst_median = r.median;
1100+
}
10971101
}
10981102

10991103
// Acceptance: must have made meaningful progress.
@@ -1156,7 +1160,28 @@ LT_BEGIN_AUTO_TEST(threadsafety_stress_suite,
11561160
}
11571161
LT_CHECK(rounds_ran > 0);
11581162
if (rounds_ran > 0) {
1163+
#if defined(__linux__) && !defined(_WIN32)
1164+
// Linux CI runs with the TASK-080 stabilisation stack (CPU pinning
1165+
// via HTTPSERVER_STRESS_PIN_CPU), which tames the lock-wait tail so
1166+
// the p95 gate is a reliable O(n)-regression detector at 20×.
11591167
LT_CHECK_LT(worst_p95, worst_baseline * 20);
1168+
#else
1169+
// Non-Linux runners (macOS, Windows) have NO working CPU-affinity
1170+
// API — pin_current_thread_to_cpu() above is a Linux-only no-op
1171+
// elsewhere — so the p95 tail is dominated by uncontrolled cross-core
1172+
// migration (asymmetric P/E cores on Apple Silicon) and QoS-based
1173+
// mutex wakeups on oversubscribed hosted runners. That tail is
1174+
// environmental, not algorithmic: on macos-latest the overall median
1175+
// holds at ~1.1× baseline while p95 balloons to ~40×. The p95 gate
1176+
// therefore has no regression bite here, only false alarms, so we
1177+
// gate on the OVERALL MEDIAN instead. The median reflects the
1178+
// registration algorithm's health and is robust to the tail: a real
1179+
// O(n) regression at 15k items shifts the whole distribution (median
1180+
// included) by orders of magnitude, so a 10× median bound still
1181+
// catches it while ignoring the platform tail. p95/p99 remain printed
1182+
// above as forensic diagnostics.
1183+
LT_CHECK_LT(worst_median, worst_baseline * 10);
1184+
#endif
11601185
}
11611186
LT_END_AUTO_TEST(adversarial_segments_registration_no_latency_spike)
11621187

0 commit comments

Comments
 (0)