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

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


The following commit(s) were added to refs/heads/next_stable by this push:
     new 7fedcdb  Allow automatic configuration to be used with a 
multi-threaded application
7fedcdb is described below

commit 7fedcdbfd82fb87044d1f24324176652e6c0e815
Author: Stephen Webb <swebb2066.com>
AuthorDate: Sat Jan 1 16:01:39 2022 +1100

    Allow automatic configuration to be used with a multi-threaded application
    
    Add a unit test to check automatic configuration works
    
    Serialize initialization of the single RepositorySelector object.
---
 src/main/cpp/aprinitializer.cpp                    | 18 +++++
 src/main/cpp/hierarchy.cpp                         | 38 ++++-------
 src/main/cpp/logmanager.cpp                        | 27 +++-----
 src/main/include/log4cxx/helpers/aprinitializer.h  | 35 ++++++++--
 src/main/include/log4cxx/hierarchy.h               |  2 -
 src/main/include/log4cxx/logmanager.h              |  1 -
 src/test/cpp/CMakeLists.txt                        | 12 +++-
 src/test/cpp/autoconfiguretestcase.cpp             | 78 ++++++++++++++++++++++
 .../resources/input/autoConfigureTest.properties   | 25 +++++++
 9 files changed, 184 insertions(+), 52 deletions(-)

diff --git a/src/main/cpp/aprinitializer.cpp b/src/main/cpp/aprinitializer.cpp
index 0bd91d2..8831412 100644
--- a/src/main/cpp/aprinitializer.cpp
+++ b/src/main/cpp/aprinitializer.cpp
@@ -127,3 +127,21 @@ void APRInitializer::unregisterCleanup(FileWatchdog* 
watchdog)
        }
 }
 
+void APRInitializer::addObject(size_t key, const ObjectPtr& pObject)
+{
+#if APR_HAS_THREADS
+       std::unique_lock<std::mutex> lock(this->mutex);
+#endif
+    this->objects[key] = pObject;
+}
+
+const ObjectPtr& APRInitializer::findOrAddObject(size_t key, 
std::function<ObjectPtr()> creator)
+{
+#if APR_HAS_THREADS
+       std::unique_lock<std::mutex> lock(this->mutex);
+#endif
+    auto pItem = this->objects.find(key);
+    if (this->objects.end() == pItem)
+        pItem = this->objects.emplace(key, creator()).first;
+    return pItem->second;
+}
diff --git a/src/main/cpp/hierarchy.cpp b/src/main/cpp/hierarchy.cpp
index 807f94e..3d79a23 100644
--- a/src/main/cpp/hierarchy.cpp
+++ b/src/main/cpp/hierarchy.cpp
@@ -67,6 +67,7 @@ struct Hierarchy::HierarchyPrivate
        log4cxx::helpers::Pool pool;
        mutable std::mutex mutex;
        bool configured;
+       mutable std::mutex configuredMutex;
 
        spi::LoggerFactoryPtr defaultFactory;
        spi::HierarchyEventListenerList listeners;
@@ -292,17 +293,15 @@ LoggerPtr Hierarchy::getRootLogger() const
 
 bool Hierarchy::isDisabled(int level) const
 {
-       bool currentlyConfigured;
+       if (!m_priv->configured) // auto-configuration required?
        {
-               std::unique_lock<std::mutex> lock(m_priv->mutex);
-               currentlyConfigured = m_priv->configured;
-       }
-
-       if (!currentlyConfigured)
-       {
-               std::shared_ptr<Hierarchy> nonconstThis = 
std::const_pointer_cast<Hierarchy>(shared_from_this());
-               DefaultConfigurator::configure(
-                       nonconstThis);
+               std::unique_lock<std::mutex> lock(m_priv->configuredMutex);
+               if (!m_priv->configured)
+               {
+                       std::shared_ptr<Hierarchy> nonconstThis = 
std::const_pointer_cast<Hierarchy>(shared_from_this());
+                       DefaultConfigurator::configure(nonconstThis);
+                       m_priv->configured = true;
+               }
        }
 
        return m_priv->thresholdInt > level;
@@ -432,29 +431,20 @@ void Hierarchy::updateChildren(ProvisionNode& pn, 
LoggerPtr logger)
 
 void Hierarchy::setConfigured(bool newValue)
 {
-       std::unique_lock<std::mutex> lock(m_priv->mutex);
-       m_priv->configured = newValue;
+       std::unique_lock<std::mutex> lock(m_priv->configuredMutex, 
std::try_to_lock);
+       if (lock.owns_lock()) // Not being auto-configured?
+               m_priv->configured = newValue;
 }
 
 bool Hierarchy::isConfigured()
 {
+       std::unique_lock<std::mutex> lock(m_priv->configuredMutex); // Blocks 
while auto-configuration is active
        return m_priv->configured;
 }
 
 HierarchyPtr Hierarchy::create()
 {
        HierarchyPtr ret( new Hierarchy() );
-       ret->configureRoot();
+       ret->m_priv->root->setHierarchy(ret);
        return ret;
 }
-
-void Hierarchy::configureRoot()
-{
-       // This should really be done in the constructor, but in order to fix
-       // LOGCXX-322 we need to turn the repositroy into a weak_ptr, and we
-       // can't use weak_from_this() in the constructor.
-       if ( !m_priv->root->getLoggerRepository().lock() )
-       {
-               m_priv->root->setHierarchy(shared_from_this());
-       }
-}
diff --git a/src/main/cpp/logmanager.cpp b/src/main/cpp/logmanager.cpp
index c7b7d8e..e060ea8 100644
--- a/src/main/cpp/logmanager.cpp
+++ b/src/main/cpp/logmanager.cpp
@@ -47,28 +47,19 @@ using namespace log4cxx::helpers;
 IMPLEMENT_LOG4CXX_OBJECT(DefaultRepositorySelector)
 
 void* LogManager::guard = 0;
-spi::RepositorySelectorPtr LogManager::repositorySelector;
-
 
 RepositorySelectorPtr LogManager::getRepositorySelector()
 {
-       //
-       //     call to initialize APR and trigger "start" of logging clock
-       //
-       APRInitializer::initialize();
-
-       if (!repositorySelector)
-       {
-               LoggerRepositoryPtr hierarchy = Hierarchy::create();
-               RepositorySelectorPtr selector(new 
DefaultRepositorySelector(hierarchy));
-               repositorySelector = selector;
-       }
-
-       return repositorySelector;
+       auto result = APRInitializer::getUnique<spi::RepositorySelector>( []() 
-> ObjectPtr
+               {
+                       LoggerRepositoryPtr hierarchy = Hierarchy::create();
+                       return ObjectPtr(new 
DefaultRepositorySelector(hierarchy));
+               }
+       );
+       return result;
 }
 
-void LogManager::setRepositorySelector(spi::RepositorySelectorPtr selector,
-       void* guard1)
+void LogManager::setRepositorySelector(spi::RepositorySelectorPtr selector, 
void* guard1)
 {
        if ((LogManager::guard != 0) && (LogManager::guard != guard1))
        {
@@ -81,7 +72,7 @@ void 
LogManager::setRepositorySelector(spi::RepositorySelectorPtr selector,
        }
 
        LogManager::guard = guard1;
-       LogManager::getRepositorySelector() = selector;
+       APRInitializer::setUnique<spi::RepositorySelector>(selector);
 }
 
 
diff --git a/src/main/include/log4cxx/helpers/aprinitializer.h 
b/src/main/include/log4cxx/helpers/aprinitializer.h
index fccd18e..38ea5c6 100644
--- a/src/main/include/log4cxx/helpers/aprinitializer.h
+++ b/src/main/include/log4cxx/helpers/aprinitializer.h
@@ -22,6 +22,7 @@
        #error "aprinitializer.h should only be included by log4cxx 
implementation"
 #endif
 
+#include <log4cxx/helpers/object.h>
 #include <list>
 
 extern "C" {
@@ -31,6 +32,7 @@ extern "C" {
 
 #include <apr_time.h>
 #include <mutex>
+#include <functional>
 
 namespace log4cxx
 {
@@ -47,25 +49,46 @@ class APRInitializer
                static bool isDestructed;
 
                /**
-                *  Register a FileWatchdog for deletion prior to
-                *    APR termination.  FileWatchdog must be
+                *  Register a FileWatchdog for deletion prior to termination.
+                *    FileWatchdog must be
                 *    allocated on heap and not deleted elsewhere.
                 */
                static void registerCleanup(FileWatchdog* watchdog);
                static void unregisterCleanup(FileWatchdog* watchdog);
+               /**
+                *  Store a single instance type ObjectPtr for deletion prior 
to termination
+                */
+               template <class T> static void setUnique(const 
std::shared_ptr<T>& pObject)
+               {
+                       getInstance().addObject(typeid(T).hash_code(), pObject);
+               }
+               /**
+                *  Fetch or add a single instance type ObjectPtr for deletion 
prior to termination
+                */
+               template <class T> static std::shared_ptr<T> 
getUnique(std::function<ObjectPtr()> creator)
+               {
+                       return 
cast<T>(getInstance().findOrAddObject(typeid(T).hash_code(), creator));
+               }
+
 
-       private:
+       private: // Constructors
                APRInitializer();
-               APRInitializer(const APRInitializer&);
-               APRInitializer& operator=(const APRInitializer&);
+               APRInitializer(const APRInitializer&) = delete;
+               APRInitializer& operator=(const APRInitializer&) = delete;
+       private: // Modifiers
+               void addObject(size_t key, const ObjectPtr& pObject);
+               const ObjectPtr& findOrAddObject(size_t key, 
std::function<ObjectPtr()> creator);
+       private: // Attributes
                apr_pool_t* p;
                std::mutex mutex;
                std::list<FileWatchdog*> watchdogs;
                apr_time_t startTime;
                apr_threadkey_t* tlsKey;
+               std::map<size_t, ObjectPtr> objects;
+       private: // Class methods
                static APRInitializer& getInstance();
 
-       public:
+       public: // Destructor
                ~APRInitializer();
 };
 } // namespace helpers
diff --git a/src/main/include/log4cxx/hierarchy.h 
b/src/main/include/log4cxx/hierarchy.h
index 0cf6963..72120bd 100644
--- a/src/main/include/log4cxx/hierarchy.h
+++ b/src/main/include/log4cxx/hierarchy.h
@@ -269,8 +269,6 @@ class LOG4CXX_EXPORT Hierarchy :
                Hierarchy& operator=(const Hierarchy&);
 
                void updateChildren(ProvisionNode& pn, LoggerPtr logger);
-
-               void configureRoot();
 };
 
 }  //namespace log4cxx
diff --git a/src/main/include/log4cxx/logmanager.h 
b/src/main/include/log4cxx/logmanager.h
index 7262861..d347f9a 100644
--- a/src/main/include/log4cxx/logmanager.h
+++ b/src/main/include/log4cxx/logmanager.h
@@ -50,7 +50,6 @@ class LOG4CXX_EXPORT LogManager
 {
        private:
                static void* guard;
-               static spi::RepositorySelectorPtr repositorySelector;
                static spi::RepositorySelectorPtr getRepositorySelector();
 
        public:
diff --git a/src/test/cpp/CMakeLists.txt b/src/test/cpp/CMakeLists.txt
index eb981b9..eef0e59 100644
--- a/src/test/cpp/CMakeLists.txt
+++ b/src/test/cpp/CMakeLists.txt
@@ -17,6 +17,7 @@ find_program(GZIP_APP gzip REQUIRED)
 
 # Tests defined in this directory
 set(ALL_LOG4CXX_TESTS
+    autoconfiguretestcase
     asyncappendertestcase
     consoleappendertestcase
     decodingtest
@@ -86,13 +87,14 @@ if( WIN32 )
   endforeach()
 endif( WIN32 )
 
+get_filename_component(UNIT_TEST_WORKING_DIR ../resources ABSOLUTE)
 foreach(testName IN LISTS ALL_LOG4CXX_TESTS)
     target_compile_definitions(${testName} PRIVATE 
${LOG4CXX_COMPILE_DEFINITIONS} ${APR_COMPILE_DEFINITIONS} 
${APR_UTIL_COMPILE_DEFINITIONS} )
     target_include_directories(${testName} PRIVATE ${CMAKE_CURRENT_LIST_DIR} 
$<TARGET_PROPERTY:log4cxx,INCLUDE_DIRECTORIES>)
     target_link_libraries(${testName} PRIVATE testingFramework 
testingUtilities log4cxx ${APR_LIBRARIES} ${APR_SYSTEM_LIBS} Threads::Threads)
     add_test(NAME ${testName}
         COMMAND ${testName} -v
-        WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/../resources
+        WORKING_DIRECTORY ${UNIT_TEST_WORKING_DIR}
     )
     set_tests_properties( ${testName} PROPERTIES TIMEOUT 120 )
 
@@ -101,6 +103,10 @@ foreach(testName IN LISTS ALL_LOG4CXX_TESTS)
             set_tests_properties(socketservertestcase PROPERTIES
                 ENVIRONMENT 
"SOCKET_SERVER_PARAMETER_FILE=${START_SOCKET_SERVER_PARAMETER_FILE};PATH=${ESCAPED_PATH}"
             )
+        elseif(${testName} STREQUAL autoconfiguretestcase)
+            set_tests_properties(autoconfiguretestcase PROPERTIES
+                ENVIRONMENT 
"LOG4CXX_CONFIGURATION=${UNIT_TEST_WORKING_DIR}/input/autoConfigureTest.properties;PATH=${ESCAPED_PATH}"
+            )
         else()
            set_tests_properties(${testName} PROPERTIES
                 ENVIRONMENT 
"TOTO=wonderful;key1=value1;key2=value2;PATH=${ESCAPED_PATH}"
@@ -111,6 +117,10 @@ foreach(testName IN LISTS ALL_LOG4CXX_TESTS)
             set_tests_properties(socketservertestcase PROPERTIES
                 ENVIRONMENT 
"SOCKET_SERVER_PARAMETER_FILE=${START_SOCKET_SERVER_PARAMETER_FILE}"
             )
+        elseif(${testName} STREQUAL autoconfiguretestcase)
+            set_tests_properties(autoconfiguretestcase PROPERTIES
+                ENVIRONMENT 
"LOG4CXX_CONFIGURATION=${UNIT_TEST_WORKING_DIR}/input/autoConfigureTest.properties"
+            )
         else()
            set_tests_properties(${testName} PROPERTIES
                 ENVIRONMENT "TOTO=wonderful;key1=value1;key2=value2"
diff --git a/src/test/cpp/autoconfiguretestcase.cpp 
b/src/test/cpp/autoconfiguretestcase.cpp
new file mode 100644
index 0000000..4953e2e
--- /dev/null
+++ b/src/test/cpp/autoconfiguretestcase.cpp
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "logunit.h"
+#include <log4cxx/logger.h>
+#include <log4cxx/logmanager.h>
+#include <log4cxx/helpers/loglog.h>
+#include <log4cxx/file.h>
+#include <thread>
+
+#define LOGUNIT_TEST_THREADS(testName, threadCount) \
+       class testName ## ThreadTestRegistration { \
+               public: \
+                       testName ## ThreadTestRegistration() { \
+                               ThisFixture::getSuite()->addTest(#testName, 
&testName ## ThreadTestRegistration :: run); \
+                       } \
+                       static void run(abts_case* tc, void*) { \
+                               std::vector<std::thread> threads; \
+                               for (auto i = threadCount; 0 < i; --i) \
+                                       threads.emplace_back( [tc]() { \
+                                               
LogUnit::runTest<ThisFixture>(tc, &ThisFixture::testName); \
+                                       } ); \
+                               while (!threads.empty()) { \
+                                       threads.back().join(); \
+                                       threads.pop_back(); \
+                               } \
+                       } \
+       } register ## testName ## ThreadTest
+
+using namespace log4cxx;
+
+LOGUNIT_CLASS(AutoConfigureTestCase)
+{
+       LOGUNIT_TEST_SUITE(AutoConfigureTestCase);
+       LOGUNIT_TEST_THREADS(test1, 4);
+       LOGUNIT_TEST(test2);
+       LOGUNIT_TEST_SUITE_END();
+#ifdef _DEBUG
+       struct Fixture
+       {
+               Fixture() {
+                       helpers::LogLog::setInternalDebugging(true);
+               }
+       } suiteFixture;
+#endif
+public:
+       void test1()
+       {
+               auto debugLogger = 
LogManager::getLogger(LOG4CXX_STR("AutoConfig.test1"));
+               LOGUNIT_ASSERT(debugLogger);
+               LOGUNIT_ASSERT(!debugLogger->isDebugEnabled());
+               auto rep = LogManager::getLoggerRepository();
+               LOGUNIT_ASSERT(rep);
+               LOGUNIT_ASSERT(rep->isConfigured());
+       }
+
+       void test2()
+       {
+               auto debugLogger = 
Logger::getLogger(LOG4CXX_STR("AutoConfig.test2"));
+               LOGUNIT_ASSERT(debugLogger);
+               LOGUNIT_ASSERT(debugLogger->isDebugEnabled());
+       }
+};
+
+LOGUNIT_TEST_SUITE_REGISTRATION(AutoConfigureTestCase);
diff --git a/src/test/resources/input/autoConfigureTest.properties 
b/src/test/resources/input/autoConfigureTest.properties
new file mode 100644
index 0000000..1187d13
--- /dev/null
+++ b/src/test/resources/input/autoConfigureTest.properties
@@ -0,0 +1,25 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+log4j.rootCategory=INFO, A1
+
+log4j.appender.A1=org.apache.log4j.FileAppender
+log4j.appender.A1.File=output/autoConfigureTest.log
+log4j.appender.A1.Append=false
+log4j.appender.A1.layout=org.apache.log4j.PatternLayout
+log4j.appender.A1.layout.ConversionPattern=%m%n
+
+#log4j.logger.AutoConfig.test1=DEBUG
+log4j.logger.AutoConfig.test2=DEBUG

Reply via email to