From 660072df1379757b41a21c2dcc289b3ae926ded0 Mon Sep 17 00:00:00 2001
From: Steven Murray <steven.murray@cern.ch>
Date: Tue, 6 Dec 2016 10:47:41 +0100
Subject: [PATCH] Bare minimum database reconnect logic

---
 rdbms/Conn.cpp     | 14 ++++++++++++++
 rdbms/Conn.hpp     | 24 ++++++++++++++++++++++++
 rdbms/ConnPool.cpp | 22 ++++++++++++++++++----
 rdbms/ConnPool.hpp |  7 +++++--
 rdbms/OcciConn.cpp | 35 +++++++++++++++++++++++++++++++++++
 rdbms/OcciConn.hpp | 10 ++++++++++
 rdbms/OcciStmt.cpp | 17 ++++++-----------
 7 files changed, 112 insertions(+), 17 deletions(-)

diff --git a/rdbms/Conn.cpp b/rdbms/Conn.cpp
index f70c14b855..75462ba85c 100644
--- a/rdbms/Conn.cpp
+++ b/rdbms/Conn.cpp
@@ -67,5 +67,19 @@ void Conn::executeNonQuery(const std::string &sql, const Stmt::AutocommitMode au
   }
 }
 
+//------------------------------------------------------------------------------
+// setHealthy
+//------------------------------------------------------------------------------
+void Conn::setHealthy(const bool value) {
+  m_healthy = value;
+}
+
+//------------------------------------------------------------------------------
+// getHealthy
+//------------------------------------------------------------------------------
+bool Conn::getHealthy() const {
+  return m_healthy;
+}
+
 } // namespace rdbms
 } // namespace cta
diff --git a/rdbms/Conn.hpp b/rdbms/Conn.hpp
index 7a9f08f051..684b97e58c 100644
--- a/rdbms/Conn.hpp
+++ b/rdbms/Conn.hpp
@@ -20,6 +20,7 @@
 
 #include "Stmt.hpp"
 
+#include <atomic>
 #include <list>
 #include <memory>
 #include <string>
@@ -93,6 +94,29 @@ public:
    */
   virtual std::list<std::string> getTableNames() = 0;
 
+  /**
+   * Returns true if the connection is healthy.
+   *
+   * @return True if the connection is healthy.
+   */
+  bool getHealthy() const;
+
+protected:
+
+  /**
+   * Sets the status of the connection to be either healthy or not healthy.
+   *
+   * @param value True if the connection is healthy.
+   */
+  void setHealthy(const bool value);
+
+private:
+
+  /**
+   * True if the connection's state is healthy.
+   */
+  std::atomic<bool> m_healthy{true};
+
 }; // class Conn
 
 } // namespace rdbms
diff --git a/rdbms/ConnPool.cpp b/rdbms/ConnPool.cpp
index ed75b79df1..e9101aa26e 100644
--- a/rdbms/ConnPool.cpp
+++ b/rdbms/ConnPool.cpp
@@ -69,10 +69,24 @@ PooledConn ConnPool::getConn() {
 // returnConn
 //------------------------------------------------------------------------------
 void ConnPool::returnConn(Conn *const conn) {
-  conn->commit();
-  std::unique_lock<std::mutex> lock(m_connsMutex);
-  m_conns.emplace_back(conn);
-  m_connsCv.notify_one();
+  // If the connection is healthy
+  if(conn->getHealthy()) {
+
+    // Commit the connection and put it back in the pool
+    conn->commit();
+    std::unique_lock<std::mutex> lock(m_connsMutex);
+    m_conns.emplace_back(conn);
+    m_connsCv.notify_one();
+
+  // Else the connection is not healthy
+  } else {
+
+    // Close the connection and put a brand new one in the pool
+    delete conn;
+    std::unique_lock<std::mutex> lock(m_connsMutex);
+    m_conns.push_back(m_connFactory.create());
+    m_connsCv.notify_one();
+  }
 }
 
 } // namespace rdbms
diff --git a/rdbms/ConnPool.hpp b/rdbms/ConnPool.hpp
index 7cf1c0c020..c3fa7ce853 100644
--- a/rdbms/ConnPool.hpp
+++ b/rdbms/ConnPool.hpp
@@ -60,8 +60,11 @@ private:
   friend PooledConn;
 
   /**
-   * Calls commit on the specified database connection and returns it to the
-   * pool.
+   * If the specified database connection is healthy, then this method calls
+   * commit on the connection and returns it to the pool.
+   *
+   * If the specified database connection is no healthy, then this method
+   * closes the connection and creates a new one in the connection pool.
    *
    * @param conn The connection to be commited and returned to the pool.
    */
diff --git a/rdbms/OcciConn.cpp b/rdbms/OcciConn.cpp
index a379fab4a4..9b8cd0a804 100644
--- a/rdbms/OcciConn.cpp
+++ b/rdbms/OcciConn.cpp
@@ -138,5 +138,40 @@ std::list<std::string> OcciConn::getTableNames() {
   }
 }
 
+//------------------------------------------------------------------------------
+// updateHealth
+//------------------------------------------------------------------------------
+void OcciConn::updateHealth(const oracle::occi::SQLException &ex) {
+  using namespace oracle;
+
+  // Error codes that identify an unhealthy connection
+  // The majority of these error codes were learnt from CASTOR
+  switch(ex.getErrorCode()) {
+  case    28:
+  case  1003:
+  case  1008:
+  case  1012:
+  case  1033:
+  case  1089:
+  case  2392:
+  case  2399:
+  case  3113:
+  case  3114:
+  case  3135:
+  case 12170:
+  case 12541:
+  case 12571:
+  case 24338:
+  case 12537:
+  case 25401:
+  case 25409:
+  case 32102:
+    setHealthy(false);
+    break;
+  default:
+    break;
+  };
+}
+
 } // namespace rdbms
 } // namespace cta
diff --git a/rdbms/OcciConn.hpp b/rdbms/OcciConn.hpp
index d922bf7ce2..59c01ee0fa 100644
--- a/rdbms/OcciConn.hpp
+++ b/rdbms/OcciConn.hpp
@@ -105,6 +105,8 @@ public:
 
 private:
 
+  friend OcciStmt;
+
   /**
    * Mutex used to serialize access to this object.
    */
@@ -120,6 +122,14 @@ private:
    */
   oracle::occi::Connection *m_conn;
 
+  /**
+   * Determines whether or not the specified Oracle exception affects the status
+   * of the database connection and updates it accordingly.
+   *
+   * @param ex The Oracle exception.
+   */
+  void updateHealth(const oracle::occi::SQLException &ex);
+
 }; // class OcciConn
 
 } // namespace rdbms
diff --git a/rdbms/OcciStmt.cpp b/rdbms/OcciStmt.cpp
index af81221ba2..2aaf5583aa 100644
--- a/rdbms/OcciStmt.cpp
+++ b/rdbms/OcciStmt.cpp
@@ -23,7 +23,6 @@
 #include "rdbms/OcciStmt.hpp"
 
 #include <cstring>
-#include <iostream>
 #include <map>
 #include <sstream>
 #include <stdexcept>
@@ -181,11 +180,9 @@ std::unique_ptr<Rset> OcciStmt::executeQuery() {
 
   try {
     return cta::make_unique<OcciRset>(*this, m_stmt->executeQuery());
-  } catch(exception::Exception &ex) {
-    throw exception::Exception(std::string(__FUNCTION__) + " failed for SQL statement " + getSql() + ": " +
-      ex.getMessage().str());
-  } catch(std::exception &se) {
-    throw exception::Exception(std::string(__FUNCTION__) + " failed for SQL statement " + getSql() + ": " + se.what());
+  } catch(occi::SQLException &ex) {
+    m_conn.updateHealth(ex);
+    throw exception::Exception(std::string(__FUNCTION__) + " failed for SQL statement " + getSql() + ": " + ex.what());
   }
 }
 
@@ -197,11 +194,9 @@ void OcciStmt::executeNonQuery() {
 
   try {
     m_stmt->executeUpdate();
-  } catch(exception::Exception &ex) {
-    throw exception::Exception(std::string(__FUNCTION__) + " failed for SQL statement " + getSql() + ": " +
-      ex.getMessage().str());
-  } catch(std::exception &se) {
-    throw exception::Exception(std::string(__FUNCTION__) + " failed for SQL statement " + getSql() + ": " + se.what());
+  } catch(occi::SQLException &ex) {
+    m_conn.updateHealth(ex);
+    throw exception::Exception(std::string(__FUNCTION__) + " failed for SQL statement " + getSql() + ": " + ex.what());
   }
 }
 
-- 
GitLab