From 222aaf76eb4e407d3f45ca9d335b2f6f2af753ab Mon Sep 17 00:00:00 2001
From: Sebastien Ponce <sponcec3@cern.ch>
Date: Tue, 12 Jun 2012 08:19:00 +0000
Subject: [PATCH] Better implementation of the CastorConfiguration class. No
 inheritance from map anymore, simpler locks, and proper checks that the lock
 taking succeed

---
 castor/common/CastorConfiguration.cpp | 137 +++++++++++++++++---------
 castor/common/CastorConfiguration.hpp |  65 ++++++------
 2 files changed, 121 insertions(+), 81 deletions(-)

diff --git a/castor/common/CastorConfiguration.cpp b/castor/common/CastorConfiguration.cpp
index a77fd7b400..2b11d7ef7d 100644
--- a/castor/common/CastorConfiguration.cpp
+++ b/castor/common/CastorConfiguration.cpp
@@ -51,7 +51,7 @@ castor::common::CastorConfiguration::getConfig(std::string fileName)
 // constructor
 //------------------------------------------------------------------------------
 castor::common::CastorConfiguration::CastorConfiguration(std::string fileName)
-  throw (castor::exception::Exception) : m_fileName(fileName), m_lastUpdateTime(-1) {
+  throw (castor::exception::Exception) : m_fileName(fileName), m_lastUpdateTime(0) {
   // create internal read write lock
   int rc = pthread_rwlock_init(&m_lock, NULL);
   if (0 != rc) {
@@ -76,69 +76,118 @@ castor::common::CastorConfiguration::getConfEnt(const std::string &category,
                                                 const std::string &key)
   throw (castor::exception::Exception) {
   // check whether we need to reload the configuration
-  checkAndRenewConfig();
-  // get read lock. No need to be fancy, just let it auto-release on exit.
-  smartReadLockTaker srl(&m_lock);
-  // get the category
-  Configuration::const_iterator catIt = find(category);
-  if (end() == catIt) {
-    castor::exception::NoEntry e;
+  if (isStale()) {
+    tryToRenewConfig();
+  }
+  // get read lock
+  int rc = pthread_rwlock_rdlock(&m_lock);
+  if (0 != rc) {
+    castor::exception::Exception e(rc);
     throw e;
   }
   // get the entry
-  ConfCategory::const_iterator entIt = catIt->second.find(key);
-  if (catIt->second.end() == entIt) {
-    castor::exception::NoEntry e;
+  try {
+    std::map<std::string, ConfCategory>::const_iterator catIt = m_config.find(category);
+    if (m_config.end() == catIt) {
+      castor::exception::NoEntry e;
+      throw e;
+    }
+    // get the entry
+    ConfCategory::const_iterator entIt = catIt->second.find(key);
+    if (catIt->second.end() == entIt) {
+      castor::exception::NoEntry e;
+      throw e;
+    }
+    // release the lock
+    pthread_rwlock_unlock(&m_lock);
+    return entIt->second;
+  } catch (...) {
+    // release the lock
+    pthread_rwlock_unlock(&m_lock);
+    throw;
+  }
+}
+
+//------------------------------------------------------------------------------
+// isStale
+//------------------------------------------------------------------------------
+bool castor::common::CastorConfiguration::isStale()
+  throw (castor::exception::Exception) {
+  // get read lock
+  int rc = pthread_rwlock_rdlock(&m_lock);
+  if (0 != rc) {
+    castor::exception::Exception e(rc);
     throw e;
   }
-  return entIt->second;
+  try {
+    // get the timeout
+    int timeout = getTimeoutNolock();
+    // release the lock
+    pthread_rwlock_unlock(&m_lock);
+    // return whether we should renew  
+    return time(0) > m_lastUpdateTime + timeout;
+  } catch (...) {
+    // release the lock
+    pthread_rwlock_unlock(&m_lock);
+    throw;
+  }   
 }
 
 //------------------------------------------------------------------------------
-// checkAndRenewConfig
+// tryToRenewConfig
 //------------------------------------------------------------------------------
-void castor::common::CastorConfiguration::checkAndRenewConfig()
+void castor::common::CastorConfiguration::tryToRenewConfig()
   throw (castor::exception::Exception) {
-  // take read only lock
-  smartReadLockTaker srl(&m_lock);
-  // get the timeout
-  time_t timeout = 300; // default to 300s = 5mn
-  std::string &stimeout = Configuration::operator[]("Config")["ExpirationDelay"];
+  // we should probably renew. First take the write lock.
+  int rc = pthread_rwlock_wrlock(&m_lock);
+  if (0 != rc) {
+    castor::exception::Exception e(rc);
+    throw e;
+  }
+  // now check that we should really renew, because someone may have done it
+  // while we waited for the lock
+  try {
+    if (time(0) > m_lastUpdateTime + getTimeoutNolock()) {
+      // now we should really renew
+      renewConfigNolock();
+    }
+  } catch (...) {
+    // release the lock
+    pthread_rwlock_unlock(&m_lock);
+    throw;
+  }
+  // release the lock
+  pthread_rwlock_unlock(&m_lock);
+  return;
+}
+
+//------------------------------------------------------------------------------
+// getTimeoutNolock
+//------------------------------------------------------------------------------
+int castor::common::CastorConfiguration::getTimeoutNolock()
+  throw (castor::exception::Exception) {
+  // start with the default (300s = 5mn)
+  int timeout = 300;
+  // get value from config
+  std::string &stimeout = m_config["Config"]["ExpirationDelay"];
   if ("" != stimeout) {
     // parse the timeout into an integer
     timeout = atoi(stimeout.c_str());
   } else {
-    // set default to 300s = 5mn into the configuration
-    Configuration::operator[]("Config")["ExpirationDelay"] = "300";
-  }
-  // release read only lock
-  srl.release();
-  // check whether we should renew  
-  time_t currentTime = time(0);
-  if (currentTime < m_lastUpdateTime + timeout) {
-    // no need to renew
-    return;  
-  }
-  // we should probably renew. First take the write lock.
-  // No need to be fancy, just let it auto-release on exit.
-  smartWriteLockTaker swl(&m_lock);
-  // now check that we should really renew, because someone may have done it
-  // while we waited for the lock
-  if (currentTime < m_lastUpdateTime + timeout) {
-    // indeed, someone renew it for us, so give up
-    return;
+    // no value found in config, set default to 300s = 5mn into the configuration
+    m_config["Config"]["ExpirationDelay"] = "300";
   }
-  // now we should really renew
-  renewConfig();
+  // return timeout
+  return timeout;
 }
 
 //------------------------------------------------------------------------------
-// renewConfig
+// renewConfigNolock
 //------------------------------------------------------------------------------
-void castor::common::CastorConfiguration::renewConfig()
+void castor::common::CastorConfiguration::renewConfigNolock()
   throw (castor::exception::Exception) {
   // reset the config
-  clear();
+  m_config.clear();
 
   // try to open the configuration file, throwing an exception if there is a
   // failure
@@ -168,7 +217,7 @@ void castor::common::CastorConfiguration::renewConfig()
     while (sline.get() == ' '){}; sline.unget(); // skip spaces
     std::string value;
     std::getline(sline, value, '#');
-    Configuration::operator[](category)[key] = value;
+    m_config[category][key] = value;
   }
   m_lastUpdateTime = time(0);
 }
diff --git a/castor/common/CastorConfiguration.hpp b/castor/common/CastorConfiguration.hpp
index 4e4fd15edb..6ea9fbe8e5 100644
--- a/castor/common/CastorConfiguration.hpp
+++ b/castor/common/CastorConfiguration.hpp
@@ -39,23 +39,14 @@ namespace castor {
      */
     typedef std::map<std::string, std::string> ConfCategory;
 
-    /**
-     * represents a CASTOR configuration
-     */
-    typedef std::map<std::string, ConfCategory> Configuration;
-
     /**
      * a class representing the configuration of castor.
      * This configurations is obtained from the local file given in the
      * constructor and will be updated regularly. The time between two
      * updates is taken from the Config/ExpirationDelay entry of the
      * configuration itself and defaults to 5mn if no such entry is found
-     *
-     * Objects of this class behave like duoble level dictionnaries. This
-     * means that you can look for entry A/B using obj["A"]["B"] or loop
-     * over the entries of a given category using an iterator in obj["A"]
      */
-    class CastorConfiguration : public Configuration {
+    class CastorConfiguration {
 
     public:
 
@@ -92,16 +83,32 @@ namespace castor {
     private:
 
       /**
-       * checks whether we should update our configuration.
-       * If yes, do the update
+       * check whether the configuration should be renewed
+       */
+      bool isStale() throw (castor::exception::Exception);
+
+      /**
+       * tries to renew the configuration.
+       * That is : take the write lock to do it, check whether it's needed
+       * and do it only if needed before releasing the lock
        */
-      void checkAndRenewConfig() throw (castor::exception::Exception);
+      void tryToRenewConfig() throw (castor::exception::Exception);
 
       /**
-       * renew the configuration. Should be called only while
-       * holding the write lock
+       * gets current timeout value (in seconds)
+       * this function does not take any lock while reading the
+       * configuration. So it should never be called without holding
+       * a read or a write lock
        */
-      void renewConfig() throw (castor::exception::Exception);
+      int getTimeoutNolock() throw (castor::exception::Exception);
+
+      /**
+       * renews the configuration
+       * this function does not take any lock while renewing the
+       * configuration. So it should never be called without holding
+       * the write lock
+       */
+      void renewConfigNolock() throw (castor::exception::Exception);
 
     private:
 
@@ -116,29 +123,13 @@ namespace castor {
       time_t m_lastUpdateTime;
 
       /**
-       * Scoped lock allowing safe lock release in all execution pathes
+       * the dictionnary of configuration items
+       * actually a dictionnary of ConfCategories, which are dictionnaries of entries
        */
-      class smartLockTaker {
-      protected:
-        smartLockTaker(pthread_rwlock_t *lk): m_reld(false), m_lock(lk) {};
-        bool m_reld;
-        pthread_rwlock_t *m_lock;
-      public:
-        void release() { if (!m_reld) pthread_rwlock_unlock(m_lock); m_reld=true; }
-        ~smartLockTaker() { release(); }
-      };
-
-      class smartReadLockTaker: public smartLockTaker {
-      public:
-        smartReadLockTaker(pthread_rwlock_t *lk): smartLockTaker(lk) { pthread_rwlock_rdlock(m_lock); }
-      };
-
-      class smartWriteLockTaker: public smartLockTaker {
-      public:
-        smartWriteLockTaker(pthread_rwlock_t *lk): smartLockTaker(lk) { pthread_rwlock_wrlock(m_lock); }
-      };
+      std::map<std::string, ConfCategory> m_config;
+
       /**
-       * lock to garantee safe access to the configuration
+       * lock to garantee safe access to the configuration, lastUpdateTime and timeout
        */
       pthread_rwlock_t m_lock;
 
-- 
GitLab