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 a4a292fe Improve AsyncAppender unit test reliability (#544)
a4a292fe is described below

commit a4a292fe793a3df1facfca797500f63a8e3ca15a
Author: Stephen Webb <[email protected]>
AuthorDate: Sun Sep 28 10:04:32 2025 +1000

    Improve AsyncAppender unit test reliability (#544)
    
    * Use a separate test for logging from an appender
    
    * Check the discard logic corresponds to the documented behaviour
    
    * Add debug to show which messages were discarded
    
    * Ensure appender logged events are discarded when the buffer is full
    
    * Ensure only appender logged events are discarded when Blocking mode is on
    
    * Do not wait on the dispatch thread when an attached appender logged the 
event
---
 src/main/cpp/asyncappender.cpp         |  26 ++++++-
 src/test/cpp/asyncappendertestcase.cpp | 138 ++++++++++++++++++++++++++++-----
 2 files changed, 141 insertions(+), 23 deletions(-)

diff --git a/src/main/cpp/asyncappender.cpp b/src/main/cpp/asyncappender.cpp
index cec9f359..2427cb62 100644
--- a/src/main/cpp/asyncappender.cpp
+++ b/src/main/cpp/asyncappender.cpp
@@ -227,7 +227,7 @@ struct AsyncAppender::AsyncAppenderPriv : public 
AppenderSkeleton::AppenderSkele
        /**
         * Used to ensure the dispatch thread does not wait when a logging 
thread is waiting.
        */
-       int blockedCount{0};
+       alignas(hardware_constructive_interference_size) int blockedCount{0};
 };
 
 
@@ -285,6 +285,7 @@ void AsyncAppender::append(const spi::LoggingEventPtr& 
event, Pool& p)
        if (priv->bufferSize <= 0)
        {
                priv->appenders.appendLoopOnAppenders(event, p);
+               return;
        }
 
        // Get a copy of this thread's diagnostic context
@@ -296,6 +297,7 @@ void AsyncAppender::append(const spi::LoggingEventPtr& 
event, Pool& p)
                if (!priv->dispatcher.joinable())
                        priv->dispatcher = 
ThreadUtility::instance()->createThread( LOG4CXX_STR("AsyncAppender"), 
&AsyncAppender::dispatch, this );
        }
+       bool isDispatchThread = (priv->dispatcher.get_id() == 
std::this_thread::get_id());
        while (true)
        {
                auto pendingCount = priv->eventCount - priv->dispatchedCount;
@@ -305,7 +307,7 @@ void AsyncAppender::append(const spi::LoggingEventPtr& 
event, Pool& p)
                        auto oldEventCount = priv->eventCount++;
                        auto index = oldEventCount % priv->buffer.size();
                        // Wait for a free slot
-                       while (priv->bufferSize <= oldEventCount - 
priv->dispatchedCount)
+                       while (priv->bufferSize <= oldEventCount - 
priv->dispatchedCount && !isDispatchThread)
                                std::this_thread::yield(); // Allow the 
dispatch thread to free a slot
                        // Write to the ring buffer
                        priv->buffer[index] = 
AsyncAppenderPriv::EventData{event, pendingCount};
@@ -334,7 +336,7 @@ void AsyncAppender::append(const spi::LoggingEventPtr& 
event, Pool& p)
 
                if (priv->blocking
                        && !priv->closed
-                       && (priv->dispatcher.get_id() != 
std::this_thread::get_id()) )
+                       && !isDispatchThread)
                {
                        ++priv->blockedCount;
                        priv->bufferNotFull.wait(lock, [this]()
@@ -525,6 +527,9 @@ DiscardSummary::createEvent(::LOG4CXX_NS::helpers::Pool& p,
 void AsyncAppender::dispatch()
 {
        size_t discardCount = 0;
+       size_t iterationCount = 0;
+       size_t waitCount = 0;
+       size_t blockedCount = 0;
        std::vector<size_t> pendingCountHistogram(priv->bufferSize, 0);
        bool isActive = true;
 
@@ -537,6 +542,7 @@ void AsyncAppender::dispatch()
                        std::this_thread::yield(); // Wait a bit
                if (priv->dispatchedCount == priv->commitCount)
                {
+                       ++waitCount;
                        std::unique_lock<std::mutex> lock(priv->bufferMutex);
                        priv->bufferNotEmpty.wait(lock, [this]() -> bool
                                { return 0 < priv->blockedCount || 
priv->dispatchedCount != priv->commitCount || priv->closed; }
@@ -556,6 +562,7 @@ void AsyncAppender::dispatch()
                priv->bufferNotFull.notify_all();
                {
                        std::lock_guard<std::mutex> lock(priv->bufferMutex);
+                       blockedCount += priv->blockedCount;
                        for (auto discardItem : priv->discardMap)
                        {
                                
events.push_back(discardItem.second.createEvent(p));
@@ -587,11 +594,24 @@ void AsyncAppender::dispatch()
                                }
                        }
                }
+               ++iterationCount;
        }
        if (LogLog::isDebugEnabled())
        {
                Pool p;
                LogString msg(LOG4CXX_STR("AsyncAppender"));
+#ifdef _DEBUG
+               msg += LOG4CXX_STR(" iterationCount ");
+               StringHelper::toString(iterationCount, p, msg);
+               msg += LOG4CXX_STR(" waitCount ");
+               StringHelper::toString(waitCount, p, msg);
+               msg += LOG4CXX_STR(" blockedCount ");
+               StringHelper::toString(blockedCount, p, msg);
+               msg += LOG4CXX_STR(" commitCount ");
+               StringHelper::toString(priv->commitCount, p, msg);
+#endif
+               msg += LOG4CXX_STR(" dispatchedCount ");
+               StringHelper::toString(priv->dispatchedCount, p, msg);
                msg += LOG4CXX_STR(" discardCount ");
                StringHelper::toString(discardCount, p, msg);
                msg += LOG4CXX_STR(" pendingCountHistogram");
diff --git a/src/test/cpp/asyncappendertestcase.cpp 
b/src/test/cpp/asyncappendertestcase.cpp
index 206efc1f..28b2c9e6 100644
--- a/src/test/cpp/asyncappendertestcase.cpp
+++ b/src/test/cpp/asyncappendertestcase.cpp
@@ -18,7 +18,7 @@
 #define NOMINMAX
 #include "logunit.h"
 
-#include <log4cxx/logger.h>
+#include <log4cxx/loggerinstance.h>
 #include <log4cxx/logmanager.h>
 #include <log4cxx/simplelayout.h>
 #include "vectorappender.h"
@@ -87,18 +87,6 @@ class BlockableVectorAppender : public VectorAppender
                {
                        std::lock_guard<std::mutex> lock( blocker );
                        VectorAppender::append(event, p);
-
-                       //
-                       //   if fatal, echo messages for testLoggingInDispatcher
-                       //
-                       if (event->getLevel() == Level::getInfo())
-                       {
-                               LoggerPtr logger = 
Logger::getLoggerLS(event->getLoggerName());
-                               LOG4CXX_LOGLS(logger, Level::getError(), 
event->getMessage());
-                               LOG4CXX_LOGLS(logger, Level::getWarn(), 
event->getMessage());
-                               LOG4CXX_LOGLS(logger, Level::getInfo(), 
event->getMessage());
-                               LOG4CXX_LOGLS(logger, Level::getDebug(), 
event->getMessage());
-                       }
                }
 
                std::mutex& getBlocker()
@@ -108,7 +96,24 @@ class BlockableVectorAppender : public VectorAppender
 
 };
 
-LOG4CXX_PTR_DEF(BlockableVectorAppender);
+/**
+ * An appender that adds logging events
+ */
+class LoggingVectorAppender : public VectorAppender
+{
+       LoggerInstancePtr logger{ "LoggingVectorAppender" };
+       void append(const spi::LoggingEventPtr& event, log4cxx::helpers::Pool& 
p) override
+       {
+               VectorAppender::append(event, p);
+               if (event->getMessage() == LOG4CXX_STR("Hello, World"))
+               {
+                       LOG4CXX_LOGLS(logger, Level::getError(), 
LOG4CXX_STR("Some example error"));
+                       LOG4CXX_LOGLS(logger, Level::getWarn(), 
LOG4CXX_STR("Some example warning"));
+                       LOG4CXX_LOGLS(logger, Level::getInfo(), 
LOG4CXX_STR("Some information"));
+                       LOG4CXX_LOGLS(logger, Level::getDebug(), 
LOG4CXX_STR("Some detailed data"));
+               }
+       }
+};
 
 /**
  * Tests of AsyncAppender.
@@ -128,6 +133,7 @@ class AsyncAppenderTestCase : public 
AppenderSkeletonTestCase
                LOGUNIT_TEST(testMultiThread);
                LOGUNIT_TEST(testBadAppender);
                LOGUNIT_TEST(testBufferOverflowBehavior);
+               LOGUNIT_TEST(testLoggingAppender);
 #if LOG4CXX_HAS_DOMCONFIGURATOR
                LOGUNIT_TEST(testConfiguration);
 #endif
@@ -322,9 +328,9 @@ class AsyncAppenderTestCase : public 
AppenderSkeletonTestCase
                 */
                void testBufferOverflowBehavior()
                {
-                       BlockableVectorAppenderPtr blockableAppender = 
BlockableVectorAppenderPtr(new BlockableVectorAppender());
+                       auto blockableAppender = 
std::make_shared<BlockableVectorAppender>();
                        
blockableAppender->setName(LOG4CXX_STR("async-blockableVector"));
-                       AsyncAppenderPtr async = AsyncAppenderPtr(new 
AsyncAppender());
+                       auto async = std::make_shared<AsyncAppender>();
                        
async->setName(LOG4CXX_STR("async-testBufferOverflowBehavior"));
                        async->addAppender(blockableAppender);
                        async->setBufferSize(5);
@@ -332,7 +338,7 @@ class AsyncAppenderTestCase : public 
AppenderSkeletonTestCase
                        async->setBlocking(false);
                        Pool p;
                        async->activateOptions(p);
-                       LoggerPtr rootLogger = Logger::getRootLogger();
+                       auto rootLogger = Logger::getRootLogger();
                        rootLogger->addAppender(async);
                        LOG4CXX_INFO(rootLogger, "Hello, World"); // This 
causes the dispatch thread creation
                        std::this_thread::sleep_for( std::chrono::milliseconds( 
10 ) ); // Wait for the dispatch thread  to be ready
@@ -347,16 +353,108 @@ class AsyncAppenderTestCase : public 
AppenderSkeletonTestCase
                                LOG4CXX_ERROR(rootLogger, "That's all folks.");
                        }
                        async->close();
-                       const std::vector<spi::LoggingEventPtr>& events = 
blockableAppender->getVector();
+
+                       // Check which messages were output
+                       auto& events = blockableAppender->getVector();
                        LOGUNIT_ASSERT(!events.empty());
+                       LOGUNIT_ASSERT(events.size() <= 142);
                        LoggingEventPtr initialEvent = events.front();
-                       LoggingEventPtr discardEvent = events.back();
                        LOGUNIT_ASSERT(initialEvent->getMessage() == 
LOG4CXX_STR("Hello, World"));
-                       LOGUNIT_ASSERT(discardEvent->getMessage().substr(0, 10) 
== LOG4CXX_STR("Discarded "));
+                       std::map<LevelPtr, int> levelCount;
+                       int discardMessageCount{ 0 };
+                       LoggingEventPtr discardEvent;
+                       for (auto& e : events)
+                       {
+                               ++levelCount[e->getLevel()];
+                               if (e->getMessage().substr(0, 10) == 
LOG4CXX_STR("Discarded "))
+                               {
+                                       ++discardMessageCount;
+                                       discardEvent = e;
+                               }
+                       }
+                       if (helpers::LogLog::isDebugEnabled())
+                       {
+                               LogString msg{ LOG4CXX_STR("messageCounts:") };
+                               for (auto& item : levelCount)
+                               {
+                                       msg += LOG4CXX_STR(" ");
+                                       msg += item.first->toString();
+                                       msg += LOG4CXX_STR(" ");
+                                       StringHelper::toString(item.second, p, 
msg);
+                               }
+                               msg += LOG4CXX_STR(" ");
+                               msg += LOG4CXX_STR("Discarded ");
+                               StringHelper::toString(discardMessageCount, p, 
msg);
+                               helpers::LogLog::debug(msg);
+                       }
+                       // Check this test has activated the discard logic
+                       LOGUNIT_ASSERT(1 <= discardMessageCount);
+                       LOGUNIT_ASSERT(5 < levelCount[Level::getInfo()]);
+                       // Check the discard message is the logging event of 
the highest level
+                       LOGUNIT_ASSERT_EQUAL(levelCount[Level::getError()], 1);
+                       LOGUNIT_ASSERT_EQUAL(discardEvent->getLevel(), 
Level::getError());
+                       // Check the discard message does not have location info
                        
LOGUNIT_ASSERT_EQUAL(log4cxx::spi::LocationInfo::getLocationUnavailable().getClassName(),
                                
discardEvent->getLocationInformation().getClassName());
                }
 
+               /**
+                * Tests behavior when the appender attached to a AsyncAppender 
adds logging events
+                */
+               void testLoggingAppender()
+               {
+                       auto loggingAppender = 
std::make_shared<LoggingVectorAppender>();
+                       
loggingAppender->setName(LOG4CXX_STR("loggingAppender"));
+                       auto async = std::make_shared<AsyncAppender>();
+                       async->setName(LOG4CXX_STR("withLoggingAppender"));
+                       async->addAppender(loggingAppender);
+                       async->setBufferSize(5);
+                       async->setLocationInfo(true);
+                       Pool p;
+                       async->activateOptions(p);
+                       auto rootLogger = Logger::getRootLogger();
+                       rootLogger->addAppender(async);
+                       LOG4CXX_INFO(rootLogger, "Hello, World"); // This 
causes the dispatch thread creation
+                       std::this_thread::sleep_for( std::chrono::milliseconds( 
10 ) ); // Wait for the dispatch thread  to be ready
+                       for (int i = 0; i < 10; i++)
+                       {
+                               LOG4CXX_INFO(rootLogger, "Hello, World");
+                       }
+                       LOG4CXX_INFO(rootLogger, "Bye bye World");
+                       std::this_thread::sleep_for( std::chrono::milliseconds( 
10 ) ); // Wait for the dispatch thread take the above events
+                       async->close();
+                       auto& events = loggingAppender->getVector();
+                       std::map<LevelPtr, int> levelCount;
+                       int discardMessageCount{ 0 };
+                       for (auto& e : events)
+                       {
+                               ++levelCount[e->getLevel()];
+                               auto message = e->getMessage();
+                               if (message.substr(0, 10) == 
LOG4CXX_STR("Discarded "))
+                               {
+                                       ++discardMessageCount;
+                                       auto isAppenderMessage = (message.npos 
== message.find(LOG4CXX_STR("World")));
+                                       LOGUNIT_ASSERT(isAppenderMessage);
+                               }
+                       }
+                       if (helpers::LogLog::isDebugEnabled())
+                       {
+                               LogString msg{ LOG4CXX_STR("messageCounts:") };
+                               for (auto& item : levelCount)
+                               {
+                                       msg += LOG4CXX_STR(" ");
+                                       msg += item.first->toString();
+                                       msg += LOG4CXX_STR(" ");
+                                       StringHelper::toString(item.second, p, 
msg);
+                               }
+                               msg += LOG4CXX_STR(" ");
+                               msg += LOG4CXX_STR("Discarded ");
+                               StringHelper::toString(discardMessageCount, p, 
msg);
+                               helpers::LogLog::debug(msg);
+                       }
+                       LOGUNIT_ASSERT(12 < events.size());
+               }
+
 #if LOG4CXX_HAS_DOMCONFIGURATOR
                void testConfiguration()
                {

Reply via email to