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

swebb2066 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/logging-log4cxx.git


The following commit(s) were added to refs/heads/master by this push:
     new 2dc44dc6 Prevent a possible fault when multiple appenders concurrently 
format an async logging request (#732)
2dc44dc6 is described below

commit 2dc44dc6948b919b34d507318990cb5d702e8f9b
Author: Stephen Webb <[email protected]>
AuthorDate: Thu Aug 20 15:21:53 2026 +1000

    Prevent a possible fault when multiple appenders concurrently format an 
async logging request (#732)
---
 src/main/cpp/loggingevent.cpp            | 28 +++++++++++----
 src/test/cpp/CMakeLists.txt              |  1 +
 src/test/cpp/asyncappenderracestress.cpp | 58 ++++++++++++++++++++++++++++++++
 3 files changed, 81 insertions(+), 6 deletions(-)

diff --git a/src/main/cpp/loggingevent.cpp b/src/main/cpp/loggingevent.cpp
index ceae144c..9bce733f 100644
--- a/src/main/cpp/loggingevent.cpp
+++ b/src/main/cpp/loggingevent.cpp
@@ -16,6 +16,7 @@
  */
 
 #include <chrono>
+#include <mutex>
 #include <log4cxx/spi/loggingevent.h>
 #include <log4cxx/ndc.h>
 
@@ -147,15 +148,30 @@ struct LoggingEvent::LoggingEventPrivate
         */
        helpers::AsyncBuffer messageAppender;
 
+       /** Ensures the deferred message is rendered exactly once.
+        *
+        *  A LoggingEvent may be shared between threads, e.g. when it is
+        *  queued to an AsyncAppender dispatcher while the logging thread
+        *  formats the same event for a synchronous appender.  Without this,
+        *  two threads could pass the empty() check concurrently: one clears
+        *  the closure vector while the other iterates it, and both
+        *  move-assign \c message - a concurrent free/assign of the same
+        *  heap buffer.
+        */
+       std::once_flag renderOnce;
+
        void renderMessage()
        {
-               if (!this->messageAppender.empty())
+               std::call_once(this->renderOnce, [this]()
                {
-                       helpers::LogCharMessageBuffer buf;
-                       this->messageAppender.renderMessage(buf);
-                       this->message = buf.extract_str(buf);
-                       this->messageAppender.clear();
-               }
+                       if (!this->messageAppender.empty())
+                       {
+                               helpers::LogCharMessageBuffer buf;
+                               this->messageAppender.renderMessage(buf);
+                               this->message = buf.extract_str(buf);
+                               this->messageAppender.clear();
+                       }
+               });
        }
 };
 
diff --git a/src/test/cpp/CMakeLists.txt b/src/test/cpp/CMakeLists.txt
index a23347b8..913a5512 100644
--- a/src/test/cpp/CMakeLists.txt
+++ b/src/test/cpp/CMakeLists.txt
@@ -127,6 +127,7 @@ if(LOG4CXX_TEST_ONLY_BUILD)
   list(APPEND TEST_COMPILE_DEFINITIONS
       "ENABLE_FAILING_APPENDER_SIMULATION_TESTING=1"
       "ENABLE_2GB_STRING_TESTING=1"
+      "ENABLE_4000_THREAD_SANITIZER_TESTING=1"
       "LOGLOG_THRESHOLD=1"
   )
 endif()
diff --git a/src/test/cpp/asyncappenderracestress.cpp 
b/src/test/cpp/asyncappenderracestress.cpp
index ef0e9eba..f9e37716 100644
--- a/src/test/cpp/asyncappenderracestress.cpp
+++ b/src/test/cpp/asyncappenderracestress.cpp
@@ -18,6 +18,9 @@
 #include "logunit.h"
 
 #include <log4cxx/asyncappender.h>
+#include <log4cxx/level.h>
+#include <log4cxx/helpers/asyncbuffer.h>
+#include <log4cxx/spi/loggingevent.h>
 
 #include <atomic>
 #include <memory>
@@ -30,6 +33,9 @@ LOGUNIT_CLASS(AsyncAppenderRaceStressTestCase)
        LOGUNIT_TEST_SUITE(AsyncAppenderRaceStressTestCase);
        LOGUNIT_TEST(raceGetSetBufferSize);
        LOGUNIT_TEST(raceGetSetBlocking);
+#if ENABLE_4000_THREAD_SANITIZER_TESTING
+       LOGUNIT_TEST(raceDeferredMessageRendering);
+#endif
        LOGUNIT_TEST_SUITE_END();
 
 public:
@@ -111,6 +117,58 @@ public:
                writer.join();
                reader.join();
        }
+
+       // A LoggingEvent created via the deferred-render (AsyncBuffer) API may 
be
+       // rendered concurrently, e.g. by an AsyncAppender dispatcher thread 
and by
+       // the logging thread when the event is also routed to a synchronous
+       // appender.  The message must be rendered exactly once: both threads 
must
+       // observe the same, complete message.
+       //
+       // Expected behavior:
+       // - Without single-shot rendering, ThreadSanitizer reports data races 
in
+       //   renderMessage (and the assertions below can observe torn messages).
+       // - With renderMessage guarded by std::call_once, ThreadSanitizer is 
clean
+       //   and both threads observe identical messages.
+       void raceDeferredMessageRendering()
+       {
+               for (int i = 0; i < 2000; ++i)
+               {
+                       helpers::AsyncBuffer buf;
+                       buf << "test message " << i;
+                       auto event = std::make_shared<spi::LoggingEvent>
+                               ( LOG4CXX_STR("test.logger")
+                               , Level::getInfo()
+                               , LOG4CXX_LOCATION
+                               , std::move(buf)
+                               );
+
+                       std::atomic<int> ready{ 0 };
+                       std::atomic<bool> start{ false };
+                       LogString first, second;
+
+                       std::thread t1([&]()
+                       {
+                               ready.fetch_add(1, std::memory_order_release);
+                               while (!start.load(std::memory_order_acquire)) 
{}
+                               first = event->getRenderedMessage();
+                       });
+                       std::thread t2([&]()
+                       {
+                               ready.fetch_add(1, std::memory_order_release);
+                               while (!start.load(std::memory_order_acquire)) 
{}
+                               second = event->getRenderedMessage();
+                       });
+
+                       while (ready.load(std::memory_order_acquire) != 2) {}
+                       start.store(true, std::memory_order_release);
+
+                       t1.join();
+                       t2.join();
+
+                       LOGUNIT_ASSERT(!first.empty());
+                       LOGUNIT_ASSERT_EQUAL(first, second);
+               }
+       }
 };
 
 LOGUNIT_TEST_SUITE_REGISTRATION(AsyncAppenderRaceStressTestCase);

Reply via email to