This is an automated email from the ASF dual-hosted git repository.

bneradt pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git


The following commit(s) were added to refs/heads/master by this push:
     new 0c18d46bc6 Fix an intermittent cache unit test deadlock at exit 
(#13535)
0c18d46bc6 is described below

commit 0c18d46bc6fcc311ac94d2ada472f6611ea4a01d
Author: Brian Neradt <[email protected]>
AuthorDate: Tue Aug 11 13:14:24 2026 -0500

    Fix an intermittent cache unit test deadlock at exit (#13535)
    
    The cache unit test harness starts the event and net processors but
    never stops them, so every test binary reaches exit() with ET_NET
    threads still running. Static destruction then frees globals out from
    under those threads: the records table in RecCore.cc is destroyed while
    a still-initializing event thread reads it through RecGetRecordInt(),
    and the ts::Metrics storage blob is released while NetHandler's activity
    loop increments a counter into it. Both are heap-use-after-frees, and
    under ASan the reporting thread races the exiting main thread. Usually
    the process dies first and the report is truncated to two lines with a
    zero exit status, so ctest reports a pass; occasionally the report
    deadlocks instead and the test hangs until ctest times it out after
    1500 seconds. The short tests that never touch the cache lose this race
    most often, which is why CacheAggregateWriteBuffer and CacheStripe are
    the ones that fail.
    
    This addresses the deadlock at its source by giving the harness's Catch2
    listener a testRunEnded hook that shuts the event system down and joins
    the event threads before the test binary returns from main. Once the
    threads are gone, static destruction has no concurrent reader to race,
    so neither use-after-free can be reported and the ASan reporting
    deadlock cannot arise.
    
    Co-authored-by: Claude Opus 5 <[email protected]>
---
 src/iocore/cache/unit_tests/main.cc | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/src/iocore/cache/unit_tests/main.cc 
b/src/iocore/cache/unit_tests/main.cc
index 6b76c504d2..d07bb32f12 100644
--- a/src/iocore/cache/unit_tests/main.cc
+++ b/src/iocore/cache/unit_tests/main.cc
@@ -162,6 +162,20 @@ struct EventProcessorListener : Catch::EventListenerBase {
     std::string src_dir       = std::string(TS_ABS_TOP_SRCDIR) + 
"/src/iocore/cache/unit_tests/etc/";
     Layout::get()->sysconfdir = std::move(src_dir);
   }
+
+  // Every test binary using this harness reaches exit() with the event threads
+  // still running, so stop them and wait for them before static destruction
+  // frees the globals they read.
+  void
+  testRunEnded(Catch::TestRunStats const & /* stats ATS_UNUSED */) override
+  {
+    TSSystemState::shut_down_event_system();
+    for (EThread *ethread : eventProcessor.active_ethreads()) {
+      if (ethread->tid != ink_thread_null()) {
+        ink_thread_join(ethread->tid);
+      }
+    }
+  }
 };
 CATCH_REGISTER_LISTENER(EventProcessorListener);
 

Reply via email to