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

wkaras 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 b4dcdec7db Avoid race conditions in Regex.cc. (#11416)
b4dcdec7db is described below

commit b4dcdec7db4f8de6ed3f41623faadf8ddc3b7e55
Author: Walt Karas <[email protected]>
AuthorDate: Wed Jun 5 12:41:25 2024 -0400

    Avoid race conditions in Regex.cc. (#11416)
---
 src/tsutil/DbgCtl.cc | 60 +++++++++++++++++++++++++++++-----------------------
 src/tsutil/Regex.cc  | 55 ++++++-----------------------------------------
 2 files changed, 39 insertions(+), 76 deletions(-)

diff --git a/src/tsutil/DbgCtl.cc b/src/tsutil/DbgCtl.cc
index 99f35a0b1a..0263c31139 100644
--- a/src/tsutil/DbgCtl.cc
+++ b/src/tsutil/DbgCtl.cc
@@ -150,6 +150,8 @@ DbgCtl::_new_reference(char const *tag)
   DebugInterface *p = DebugInterface::get_instance();
   debug_assert(tag != nullptr);
 
+  _TagData *new_tag_data{nullptr};
+
   // DbgCtl instances may be declared as static objects in the destructors of 
objects not destroyed till program exit.
   // So, we must handle the case where the construction of such instances of 
DbgCtl overlaps with the destruction of
   // other instances of DbgCtl.  That is why it is important to make sure the 
reference count is non-zero before
@@ -157,42 +159,46 @@ DbgCtl::_new_reference(char const *tag)
   // the Registry, the new Registry will not be destroyed before the mutex in 
the new Registry is locked.
 
   ++_RegistryAccessor::registry_reference_count;
+  {
+    _RegistryAccessor ra;
 
-  // There is a mutex in the C/C++ runtime that both dlopen() and 
_cxa_thread_atexit() lock while running.
-  // Creating a _RegistryAccessor instance locks the registry mutex.  If the 
subsequent code in this function triggers
-  // the construction of a thread_local variable (with a non-trivial 
destructor), the following deadlock scenario is
-  // possible:
-  // 1.  Thread 1 calls a DbgCtl constructor, which locks the registry mutex, 
but then is suspended.
-  // 2.  Thread 2 calls dlopen() for a plugin, locking the runtime mutex.  It 
then executes the constructor for a
-  //     statically allocated DbgCtl object, which blocks on locking the 
registry mutex.
-  // 3.  Thread 1 resumes, and calls member functions of the derived class of 
DebugInterface.  If this causes the
-  //     the construction of a thread_local variable with a non-trivial 
destructor, _cxa_thread_atexit() will be called
-  //     to set up a call of the variable's destructor at thread exit.  The 
call to _cxa_thread_atexit() will block on
-  //     the runtime mutex (held by Thread 2).  So Thread 1 holds the registry 
mutex and is blocked waiting for the
-  //     runtime mutex.  And Thread 2 holds the runtime mutex and is blocked 
waiting for the registry mutex.  Deadlock.
-  //
-  // This deadlock is avoided by having the thread_local variable register its 
destruction in a non-thread_local class.
-  _RegistryAccessor ra;
+    auto &d{ra.data()};
 
-  auto &d{ra.data()};
+    if (auto it = d.map.find(tag); it != d.map.end()) {
+      return &*it;
+    }
 
-  if (auto it = d.map.find(tag); it != d.map.end()) {
-    return &*it;
-  }
+    auto sz = std::strlen(tag);
 
-  auto sz = std::strlen(tag);
+    debug_assert(sz > 0);
 
-  debug_assert(sz > 0);
+    char *t = new char[sz + 1]; // Deleted by ~Registry().
+    std::memcpy(t, tag, sz + 1);
+    _TagData new_elem{t, false};
 
-  char *t = new char[sz + 1]; // Deleted by ~Registry().
-  std::memcpy(t, tag, sz + 1);
-  _TagData new_elem{t, p && p->debug_tag_activated(tag)};
+    auto res = d.map.insert(new_elem);
 
-  auto res = d.map.insert(new_elem);
+    debug_assert(res.second);
+
+    new_tag_data = &*res.first;
+  }
+  new_tag_data->second = p && p->debug_tag_activated(tag);
 
-  debug_assert(res.second);
+  // It is important that debug_tag_activated() is NOT called while the ra 
object exists, and the registry mutex is
+  // locked.  There is a mutex in the C/C++ runtime that both dlopen() and 
_cxa_thread_atexit() lock while running.
+  // Creating a _RegistryAccessor instance locks the registry mutex.  If the 
subsequent code in this function triggers
+  // the construction of a thread_local variable (with a non-trivial 
destructor), with the registry mutex locked, the
+  // following deadlock scenario is possible:
+  // 1.  Thread 1 calls a DbgCtl constructor, which locks the registry mutex, 
but then is suspended.
+  // 2.  Thread 2 calls dlopen() for a plugin, locking the runtime mutex.  It 
then executes the constructor for a
+  //     statically allocated DbgCtl object, which blocks on locking the 
registry mutex.
+  // 3.  Thread 1 resumes, and calls a function that causes the the 
construction of a thread_local variable with a
+  //     non-trivial destructor.  This causes a call to _cxa_thread_atexit(), 
to set up a call of the variable's
+  //     destructor at thread exit.  The call to _cxa_thread_atexit() will 
block on the runtime mutex (held by Thread 2).
+  //     So Thread 1 holds the registry mutex and is blocked waiting for the 
runtime mutex.  And Thread 2 holds the
+  //     runtime mutex and is blocked waiting for the registry mutex.  
Deadlock.
 
-  return &*res.first;
+  return new_tag_data;
 }
 
 void
diff --git a/src/tsutil/Regex.cc b/src/tsutil/Regex.cc
index c5beb88e5c..7dd02b6fe5 100644
--- a/src/tsutil/Regex.cc
+++ b/src/tsutil/Regex.cc
@@ -51,19 +51,6 @@ my_free(void *ptr, void * /*caller*/)
   free(ptr);
 }
 
-class RegexContext; // defined below
-class RegexContextCleanup
-{
-public:
-  void push_back(RegexContext *ctx);
-  ~RegexContextCleanup();
-
-private:
-  std::vector<RegexContext *> _contexts;
-  std::mutex                  _mutex;
-};
-RegexContextCleanup regex_context_cleanup;
-
 //----------------------------------------------------------------------------
 class RegexContext
 {
@@ -71,20 +58,11 @@ public:
   static RegexContext *
   get_instance()
   {
-    if (_shutdown == true) {
-      return nullptr;
-    }
-
-    if (_regex_context == nullptr) {
-      _regex_context = new RegexContext();
-      regex_context_cleanup.push_back(_regex_context);
-    }
-    return _regex_context;
+    thread_local RegexContext ctx;
+    return &ctx;
   }
   ~RegexContext()
   {
-    _shutdown = true;
-
     if (_general_context != nullptr) {
       pcre2_general_context_free(_general_context);
     }
@@ -123,33 +101,12 @@ private:
     _jit_stack       = pcre2_jit_stack_create(4096, 1024 * 1024, nullptr); // 
1 page min and 1MB max
     pcre2_jit_stack_assign(_match_context, nullptr, _jit_stack);
   }
-  pcre2_general_context            *_general_context = nullptr;
-  pcre2_compile_context            *_compile_context = nullptr;
-  pcre2_match_context              *_match_context   = nullptr;
-  pcre2_jit_stack                  *_jit_stack       = nullptr;
-  thread_local static RegexContext *_regex_context;
-  static bool                       _shutdown; // flag to indicate destructor 
was called, so no new instances can be created
+  pcre2_general_context *_general_context = nullptr;
+  pcre2_compile_context *_compile_context = nullptr;
+  pcre2_match_context   *_match_context   = nullptr;
+  pcre2_jit_stack       *_jit_stack       = nullptr;
 };
 
-thread_local RegexContext *RegexContext::_regex_context = nullptr;
-bool                       RegexContext::_shutdown      = false;
-
-//----------------------------------------------------------------------------
-
-RegexContextCleanup::~RegexContextCleanup()
-{
-  std::lock_guard<std::mutex> guard(_mutex);
-  for (auto ctx : _contexts) {
-    delete ctx;
-  }
-}
-void
-RegexContextCleanup::push_back(RegexContext *ctx)
-{
-  std::lock_guard<std::mutex> guard(_mutex);
-  _contexts.push_back(ctx);
-}
-
 } // namespace
 
 //----------------------------------------------------------------------------

Reply via email to