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
diff --git a/src/main/cpp/asyncappender.cpp b/src/main/cpp/asyncappender.cpp
index cec9f35..2427cb6 100644
--- a/src/main/cpp/asyncappender.cpp
+++ b/src/main/cpp/asyncappender.cpp
@@ -227,7 +227,7 @@
 	/**
 	 * 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 @@
 	if (priv->bufferSize <= 0)
 	{
 		priv->appenders.appendLoopOnAppenders(event, p);
+		return;
 	}
 
 	// Get a copy of this thread's diagnostic context
@@ -296,6 +297,7 @@
 		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 @@
 			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 @@
 
 		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 @@
 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 @@
 			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 @@
 		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 @@
 				}
 			}
 		}
+		++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 206efc1..28b2c9e 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 @@
 		{
 			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 @@
 
 };
 
-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 @@
 		LOGUNIT_TEST(testMultiThread);
 		LOGUNIT_TEST(testBadAppender);
 		LOGUNIT_TEST(testBufferOverflowBehavior);
+		LOGUNIT_TEST(testLoggingAppender);
 #if LOG4CXX_HAS_DOMCONFIGURATOR
 		LOGUNIT_TEST(testConfiguration);
 #endif
@@ -322,9 +328,9 @@
 		 */
 		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 @@
 			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 @@
 				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()
 		{