https://bugs.internet2.edu/jira/browse/CPPXT-54
authorcantor <cantor@de75baf8-a10c-0410-a50a-987c0e22f00f>
Tue, 30 Mar 2010 03:11:59 +0000 (03:11 +0000)
committercantor <cantor@de75baf8-a10c-0410-a50a-987c0e22f00f>
Tue, 30 Mar 2010 03:11:59 +0000 (03:11 +0000)
git-svn-id: https://svn.middleware.georgetown.edu/cpp-xmltooling/branches/REL_1@725 de75baf8-a10c-0410-a50a-987c0e22f00f

xmltooling/util/ReloadableXMLFile.cpp
xmltooling/util/ReloadableXMLFile.h

index db20c57..ca8ad07 100644 (file)
@@ -1,5 +1,5 @@
 /*
- *  Copyright 2001-2009 Internet2
+ *  Copyright 2001-2010 Internet2
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 #include "util/XMLConstants.h"
 #include "util/XMLHelper.h"
 
+#if defined(XMLTOOLING_LOG4SHIB)
+# include <log4shib/NDC.hh>
+#elif defined(XMLTOOLING_LOG4CPP)
+# include <log4cpp/NDC.hh>
+#endif
+
 #include <fstream>
 #include <sys/types.h>
 #include <sys/stat.h>
@@ -42,6 +48,7 @@ using namespace xmltooling;
 using namespace xercesc;
 using namespace std;
 
+static const XMLCh id[] =               UNICODE_LITERAL_2(i,d);
 static const XMLCh uri[] =              UNICODE_LITERAL_3(u,r,i);
 static const XMLCh url[] =              UNICODE_LITERAL_3(u,r,l);
 static const XMLCh path[] =             UNICODE_LITERAL_4(p,a,t,h);
@@ -54,13 +61,9 @@ static const XMLCh reloadInterval[] =   UNICODE_LITERAL_14(r,e,l,o,a,d,I,n,t,e,r
 static const XMLCh backingFilePath[] =  UNICODE_LITERAL_15(b,a,c,k,i,n,g,F,i,l,e,P,a,t,h);
 
 
-ReloadableXMLFile::~ReloadableXMLFile()
-{
-    delete m_lock;
-}
-
 ReloadableXMLFile::ReloadableXMLFile(const DOMElement* e, Category& log)
-    : m_root(e), m_local(true), m_validate(false), m_filestamp(0), m_reloadInterval(0), m_lock(NULL), m_log(log)
+    : m_root(e), m_local(true), m_validate(false), m_filestamp(0), m_reloadInterval(0), m_lock(NULL), m_log(log),
+        m_shutdown(false), m_reload_wait(NULL), m_reload_thread(NULL)
 {
 #ifdef _DEBUG
     NDC ndc("ReloadableXMLFile");
@@ -138,23 +141,162 @@ ReloadableXMLFile::ReloadableXMLFile(const DOMElement* e, Category& log)
             }
             m_filestamp = time(NULL);   // assume it gets loaded initially
         }
+
+        if (m_lock) {
+            m_reload_wait = CondWait::create();
+            m_reload_thread = Thread::create(&reload_fn, this);
+        }
     }
     else {
         log.debug("no resource uri/path/name supplied, will load inline configuration");
     }
+
+    source = e->getAttributeNS(NULL, id);
+    if (source && *source) {
+        auto_ptr_char tempid(source);
+        m_id = tempid.get();
+    }
+}
+
+ReloadableXMLFile::~ReloadableXMLFile()
+{
+    if (m_reload_thread) {
+        // Shut down the reload thread and let it know.
+        m_shutdown = true;
+        m_reload_wait->signal();
+        m_reload_thread->join(NULL);
+        delete m_reload_thread;
+        delete m_reload_wait;
+    }
+
+    delete m_lock;
+}
+
+void* ReloadableXMLFile::reload_fn(void* pv)
+{
+    ReloadableXMLFile* r = reinterpret_cast<ReloadableXMLFile*>(pv);
+
+#ifndef WIN32
+    // First, let's block all signals
+    Thread::mask_all_signals();
+#endif
+
+    if (!r->m_id.empty()) {
+        string threadid("[");
+        threadid += r->m_id + ']';
+        logging::NDC::push(threadid);
+    }
+
+#ifdef _DEBUG
+    NDC ndc("reload");
+#endif
+
+    auto_ptr<Mutex> mutex(Mutex::create());
+    mutex->lock();
+
+    if (r->m_local)
+        r->m_log.info("reload thread started...running when signaled");
+    else
+        r->m_log.info("reload thread started...running every %d seconds", r->m_reloadInterval);
+
+    while (!r->m_shutdown) {
+        if (r->m_local)
+            r->m_reload_wait->wait(mutex.get());
+        else
+            r->m_reload_wait->timedwait(mutex.get(), r->m_reloadInterval);
+        if (r->m_shutdown)
+            break;
+
+        try {
+            r->m_log.info("reloading %s resource...", r->m_local ? "local" : "remote");
+            pair<bool,DOMElement*> ret = r->background_load();
+            if (ret.first)
+                ret.second->getOwnerDocument()->release();
+        }
+        catch (long& ex) {
+            if (ex == HTTPResponse::XMLTOOLING_HTTP_STATUS_NOTMODIFIED) {
+                r->m_log.info("remote resource (%s) unchanged from cached version", r->m_source.c_str());
+            }
+            else {
+                // Shouldn't happen, we should only get codes intended to be gracefully handled.
+                r->m_log.crit("maintaining existing configuration, remote resource fetch returned atypical status code (%d)", ex);
+            }
+        }
+        catch (exception& ex) {
+            r->m_log.crit("maintaining existing configuration, error reloading resource (%s): %s", r->m_source.c_str(), ex.what());
+        }
+    }
+
+    r->m_log.info("reload thread finished");
+
+    mutex->unlock();
+
+    if (!r->m_id.empty()) {
+        logging::NDC::pop();
+    }
+
+    return NULL;
+}
+
+Lockable* ReloadableXMLFile::lock()
+{
+    if (!m_lock)
+        return this;
+
+    m_lock->rdlock();
+
+    if (m_local) {
+    // Check if we need to refresh.
+#ifdef WIN32
+        struct _stat stat_buf;
+        if (_stat(m_source.c_str(), &stat_buf) != 0)
+            return this;
+#else
+        struct stat stat_buf;
+        if (stat(m_source.c_str(), &stat_buf) != 0)
+            return this;
+#endif
+        if (m_filestamp >= stat_buf.st_mtime)
+            return this;
+
+        // Elevate lock and recheck.
+        m_log.debug("timestamp of local resource changed, elevating to a write lock");
+        m_lock->unlock();
+        m_lock->wrlock();
+        if (m_filestamp >= stat_buf.st_mtime) {
+            // Somebody else handled it, just downgrade.
+            m_log.debug("update of local resource handled by another thread, downgrading lock");
+            m_lock->unlock();
+            m_lock->rdlock();
+            return this;
+        }
+
+        // Update the timestamp regardless.
+        m_filestamp = stat_buf.st_mtime;
+        m_log.info("change detected, signaling reload thread...");
+        m_reload_wait->signal();
+    }
+
+    return this;
+}
+
+void ReloadableXMLFile::unlock()
+{
+    if (m_lock)
+        m_lock->unlock();
 }
 
 pair<bool,DOMElement*> ReloadableXMLFile::load(bool backup)
 {
 #ifdef _DEBUG
-    NDC ndc("init");
+    NDC ndc("load");
 #endif
 
     try {
         if (m_source.empty()) {
             // Data comes from the DOM we were handed.
             m_log.debug("loading inline configuration...");
-            return make_pair(false,XMLHelper::getFirstChildElement(m_root));
+            return make_pair(false, XMLHelper::getFirstChildElement(m_root));
         }
         else {
             // Data comes from a file we have to parse.
@@ -167,7 +309,7 @@ pair<bool,DOMElement*> ReloadableXMLFile::load(bool backup)
             if (m_local || backup) {
                 auto_ptr_XMLCh widenit(backup ? m_backing.c_str() : m_source.c_str());
                 LocalFileInputSource src(widenit.get());
-                Wrapper4InputSource dsrc(&src,false);
+                Wrapper4InputSource dsrc(&src, false);
                 if (m_validate)
                     doc=XMLToolingConfig::getConfig().getValidatingParser().parse(dsrc);
                 else
@@ -175,7 +317,7 @@ pair<bool,DOMElement*> ReloadableXMLFile::load(bool backup)
             }
             else {
                 URLInputSource src(m_root, NULL, &m_cacheTag);
-                Wrapper4InputSource dsrc(&src,false);
+                Wrapper4InputSource dsrc(&src, false);
                 if (m_validate)
                     doc=XMLToolingConfig::getConfig().getValidatingParser().parse(dsrc);
                 else
@@ -208,7 +350,7 @@ pair<bool,DOMElement*> ReloadableXMLFile::load(bool backup)
                 }
             }
 
-            return make_pair(true,doc->getDocumentElement());
+            return make_pair(true, doc->getDocumentElement());
         }
     }
     catch (XMLException& e) {
@@ -228,101 +370,16 @@ pair<bool,DOMElement*> ReloadableXMLFile::load(bool backup)
     }
 }
 
-Lockable* ReloadableXMLFile::lock()
-{
-    if (!m_lock)
-        return this;
-
-    m_lock->rdlock();
-
-    // Check if we need to refresh.
-    if (m_local) {
-#ifdef WIN32
-        struct _stat stat_buf;
-        if (_stat(m_source.c_str(), &stat_buf) != 0)
-            return this;
-#else
-        struct stat stat_buf;
-        if (stat(m_source.c_str(), &stat_buf) != 0)
-            return this;
-#endif
-        if (m_filestamp>=stat_buf.st_mtime)
-            return this;
-
-        // Elevate lock and recheck.
-        m_log.debug("timestamp of local resource changed, elevating to a write lock");
-        m_lock->unlock();
-        m_lock->wrlock();
-        if (m_filestamp>=stat_buf.st_mtime) {
-            // Somebody else handled it, just downgrade.
-            m_log.debug("update of local resource handled by another thread, downgrading lock");
-            m_lock->unlock();
-            m_lock->rdlock();
-            return this;
-        }
-
-        // Update the timestamp regardless. No point in repeatedly trying.
-        m_filestamp=stat_buf.st_mtime;
-        m_log.info("change detected, reloading local resource...");
-    }
-    else {
-        time_t now = time(NULL);
-
-        // Time to reload? If we have no data, filestamp is zero
-        // and there's no way current time is less than the interval.
-        if (now - m_filestamp < m_reloadInterval)
-            return this;
-
-        // Elevate lock and recheck.
-        m_log.debug("reload interval for remote resource elapsed, elevating to a write lock");
-        m_lock->unlock();
-        m_lock->wrlock();
-        if (now - m_filestamp < m_reloadInterval) {
-            // Somebody else handled it, just downgrade.
-            m_log.debug("update of remote resource handled by another thread, downgrading lock");
-            m_lock->unlock();
-            m_lock->rdlock();
-            return this;
-        }
-
-        m_filestamp = now;
-        m_log.info("reloading remote resource...");
-    }
-
-    // Do this once...
-    try {
-        // At this point we're holding the write lock, so make sure we pop it.
-        SharedLock lockwrap(m_lock,false);
-        pair<bool,DOMElement*> ret=load();
-        if (ret.first)
-            ret.second->getOwnerDocument()->release();
-    }
-    catch (long& ex) {
-        if (ex == HTTPResponse::XMLTOOLING_HTTP_STATUS_NOTMODIFIED) {
-            m_log.info("remote resource (%s) unchanged from cached version", m_source.c_str());
-        }
-        else {
-            // Shouldn't happen, we should only get codes intended to be gracefully handled.
-            m_log.crit("maintaining existing configuration, remote resource fetch returned atypical status code (%d)", ex);
-        }
-    }
-    catch (exception& ex) {
-        m_log.crit("maintaining existing configuration, error reloading resource (%s): %s", m_source.c_str(), ex.what());
-    }
-
-    // If we made it here, the swap may or may not have worked, but we need to relock.
-    m_log.debug("attempt to update resource complete, relocking");
-    m_lock->rdlock();
-    return this;
-}
-
-void ReloadableXMLFile::unlock()
+pair<bool,DOMElement*> ReloadableXMLFile::load()
 {
-    if (m_lock)
-        m_lock->unlock();
+    return load(false);
 }
 
-pair<bool,DOMElement*> ReloadableXMLFile::load()
+pair<bool,DOMElement*> ReloadableXMLFile::background_load()
 {
-    return load(false);
+    // If this method isn't overridden, we acquire a write lock
+    // and just call the old override.
+    m_lock->wrlock();
+    SharedLock locker(m_lock, false);
+    return load();
 }
index 8ae62cf..70aca5a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- *  Copyright 2001-2009 Internet2
+ *  Copyright 2001-2010 Internet2
  * 
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -32,7 +32,9 @@
 
 namespace xmltooling {
 
+    class XMLTOOL_API CondWait;
     class XMLTOOL_API RWLock;
+    class XMLTOOL_API Thread;
 
     /**
      * Base class for file-based XML configuration.
@@ -57,6 +59,8 @@ namespace xmltooling {
          *  <dd>enables periodic refresh of remote file</dd>
          *  <dt>backingFilePath</dt>
          *  <dd>location for backup of remote resource</dd>
+         *  <dt>id</dt>
+         *  <dd>identifies the plugin instance for logging purposes</dd>
          * </dl>
          * 
          * @param e     DOM to supply configuration
@@ -72,6 +76,25 @@ namespace xmltooling {
          * <p>This method is called to load configuration material
          * initially and any time a change is detected. The base version
          * performs basic parsing duties and returns the result.
+         *
+         * <p>This method is not called with the object locked, so actual
+         * modification of implementation state requires explicit locking within
+         * the method override.
+         * 
+         * @return a pair consisting of a flag indicating whether to take ownership of
+         *      the document, and the root element of the tree to load
+         */
+        virtual std::pair<bool,xercesc::DOMElement*> background_load();
+
+        /**
+         * Basic load/parse of configuration material.
+         * 
+         * <p>The base version performs basic parsing duties and returns the result.
+         * Subclasses should override the new background_load() method and perform
+         * their own locking in conjunction with use of this method.
+         *
+         * <p>Subclasses that continue to override this method will function, but
+         * a write lock will be acquired and held for the entire operation.
          * 
          * @return a pair consisting of a flag indicating whether to take ownership of
          *      the document, and the root element of the tree to load
@@ -93,7 +116,7 @@ namespace xmltooling {
         /** Path to backup copy for remote resource. */
         std::string m_backing;
         
-        /** Last modification of local resource or reload of remote resource. */
+        /** Last modification of local resource. */
         time_t m_filestamp;
 
         /** Time in seconds to wait before trying for new copy of remote resource. */
@@ -108,12 +131,21 @@ namespace xmltooling {
         /** Logging object. */
         logging::Category& m_log;
 
+        /** Plugin identifier. */
+        std::string m_id;
+
     public:
         Lockable* lock();
         void unlock();
 
     private:
         std::pair<bool,xercesc::DOMElement*> load(bool backup);
+
+        // Used to manage background reload/refresh.
+        bool m_shutdown;
+        CondWait* m_reload_wait;
+        Thread* m_reload_thread;
+        static void* reload_fn(void*);
     };
 
 };