[codeface] [PATCH] Treat MySQL error 2013 in DB manager

  • From: Claus Hunsen <hunsen@xxxxxxxxxxxxxxxxx>
  • To: codeface@xxxxxxxxxxxxx
  • Date: Thu, 19 Jan 2017 14:26:23 +0100

The connection to the DB via the DB manager can fail not only with error
2006, but also with error 2013 "Lost connection to MySQL server during
query". To treat this kind of error now, an automatic reconnect is
attempted.

If the connection cannot be restored via a ping operation after error
2006 or 2013, the connection will automatically run into a timeout
later.

Reformat the whole file to fix indentation (remove tabs, fix indentation
size).

Signed-off-by: Claus Hunsen <hunsen@xxxxxxxxxxxxxxxxx>
---
 codeface/dbmanager.py | 92 ++++++++++++++++++++++++++++-----------------------
 1 file changed, 50 insertions(+), 42 deletions(-)

diff --git a/codeface/dbmanager.py b/codeface/dbmanager.py
index 3dd97cf..a0f51e9 100644
--- a/codeface/dbmanager.py
+++ b/codeface/dbmanager.py
@@ -19,9 +19,12 @@
 
 import MySQLdb as mdb
 from datetime import datetime
-from logging import getLogger; log = getLogger(__name__)
+from logging import getLogger;
 from contextlib import contextmanager
 
+# create logger
+log = getLogger(__name__)
+
 @contextmanager
 def _log_db_error(action, args=None):
     try:
@@ -33,9 +36,10 @@ def _log_db_error(action, args=None):
             except:
                 pass
         log.critical('MySQL error {e[0]} during "{action}": {e[1]}'
-                ''.format(e=e.args, action=action))
+                     ''.format(e=e.args, action=action))
         raise
 
+
 class DBManager:
     """This class provides an interface to the codeface sql database."""
 
@@ -50,7 +54,7 @@ class DBManager:
             log.debug(
                 "Establishing MySQL connection to "
                 "{c[dbuser]}@{c[dbhost]}:{c[dbport]}, DB '{c[dbname]}'"
-                .format(c=conf))
+                    .format(c=conf))
         except mdb.Error as e:
             log.critical(
                 "Failed to establish MySQL connection to "
@@ -60,8 +64,8 @@ class DBManager:
             raise
         self.cur = self.con.cursor()
 
-       max_packet_size = 1024*1024*256
-       self.doExec("SET GLOBAL max_allowed_packet=%s", (max_packet_size,))
+        max_packet_size = 1024 * 1024 * 256
+        self.doExec("SET GLOBAL max_allowed_packet=%s", (max_packet_size,))
 
     def __del__(self):
         if self.con != None:
@@ -78,11 +82,14 @@ class DBManager:
                     return res
                 except mdb.OperationalError as dbe:
                     log.info("DBE args: " + str(dbe.args))
-                    if dbe.args[0] == 1213: # Deadlock! retry...
+                    if dbe.args[0] == 1213:  # Deadlock! retry...
                         log.warning("Recoverable deadlock in MySQL - 
retrying.")
-                    elif dbe.args[0] == 2006: # Server gone away...
+                    elif dbe.args[0] == 2006:  # Server gone away...
                         log.warning("MySQL Server gone away, trying to 
reconnect.")
                         self.con.ping(True)
+                    elif dbe.args[0] == 2013:  # Lost connection to MySQL 
server during query...
+                        log.warning("Lost connection to MySQL server during 
query, trying to reconnect.")
+                        self.con.ping(True)
                     else:
                         raise
 
@@ -110,16 +117,16 @@ class DBManager:
         if self.cur.rowcount == 0:
             # Project is not contained in the database
             log.devinfo("Creating new project {}/{}".
-                    format(name, analysisMethod))
+                        format(name, analysisMethod))
             self.doExecCommit("INSERT INTO project (name, analysisMethod) " +
-                        "VALUES (%s, %s);", (name, analysisMethod))
+                              "VALUES (%s, %s);", (name, analysisMethod))
             self.doExec("SELECT id FROM project WHERE name=%s;", (name,))
         elif self.cur.rowcount > 1:
             raise Exception("Duplicate projects {}/{} in database!".
-                    format(name, analysisMethod))
+                            format(name, analysisMethod))
         pid = self.doFetchAll()[0][0]
         log.devinfo("Using project {}/{} with ID {}".
-                format(name, analysisMethod, pid))
+                    format(name, analysisMethod, pid))
         return pid
 
     def get_project(self, pid):
@@ -161,7 +168,7 @@ class DBManager:
                     WHERE projectId={}".format(pid))
         if self.cur.rowcount == 0:
             raise Exception("Persons from project {} not found!".format(pid))
-        return(self.doFetchAll())
+        return (self.doFetchAll())
 
     def getTagID(self, projectID, tag, type):
         """Determine the ID of a tag, given its textual form and the type"""
@@ -169,16 +176,16 @@ class DBManager:
                     "AND tag=%s AND type=%s", (projectID, tag, type))
         if self.cur.rowcount != 1:
             raise Exception("Tag '{}' of type {} is {} times in the DB!".
-                    format(tag, type, self.cur.rowcount))
+                            format(tag, type, self.cur.rowcount))
         return self.doFetchAll()[0][0]
 
     def getCommitId(self, projectId, commitHash):
         self.doExec("SELECT id FROM commit" +
-                   " WHERE commitHash=%s AND projectId=%s"
-                   ,(commitHash, projectId))
+                    " WHERE commitHash=%s AND projectId=%s"
+                    , (commitHash, projectId))
         if self.cur.rowcount == 0:
             raise Exception("Commit from project {} not found!".
-                    format(projectId))
+                            format(projectId))
         return self.doFetchAll()[0][0]
 
     def getRevisionID(self, projectID, tag):
@@ -194,8 +201,8 @@ class DBManager:
                     (projectID, revisionIDs[0], revisionIDs[1]))
         if self.cur.rowcount != 1:
             raise Exception("Release range from '{r[0]}' to '{r[1]}' is {c} "
-                    "times in the DB!".
-                    format(r=revisionIDs, c=self.cur.rowcount))
+                            "times in the DB!".
+                            format(r=revisionIDs, c=self.cur.rowcount))
         return self.doFetchAll()[0][0]
 
     def get_release_range(self, project_id, range_id):
@@ -212,7 +219,7 @@ class DBManager:
         return ranges[0]
 
     def update_release_timeline(self, project, tagging, revs, rcs,
-            recreate_project=False):
+                                recreate_project=False):
         '''
         For a project, update the release timeline table with the given
         revisions. If existing releases/rcs from the timeline are not in
@@ -233,7 +240,7 @@ class DBManager:
             tags = [tag for (tag,) in self.doFetchAll()]
             if len(set(tags)) != len(tags):
                 log.error("Database corrupted: Duplicate release entries in "
-                        "release_timeline! Recreating project.")
+                          "release_timeline! Recreating project.")
                 recreate_project = True
             if len(tags) == 0:
                 recreate_project = True
@@ -266,14 +273,14 @@ class DBManager:
         # of a previous period
         if not recreate_project:
             for i, tag in enumerate(rctags):
-                if i+1 >= len(rcs):
+                if i + 1 >= len(rcs):
                     log.warning("List of release candidates to analyse "
                                 "was shortened.")
                     break
-                if rcs[i+1] != tag:
+                if rcs[i + 1] != tag:
                     log.error("Release candidate number {} changed tag "
                               "from {} to {}. Recreating project.".
-                              format(i, tag, rcs[i+1]))
+                              format(i, tag, rcs[i + 1]))
                     recreate_project = True
                     break
 
@@ -287,25 +294,25 @@ class DBManager:
                 "WHERE release_range.projectId=%s ORDER BY release_range.id",
                 (pid,))
             ranges = self.doFetchAll()
-            if len(set(ranges)) != len(tags)-1:
+            if len(set(ranges)) != len(tags) - 1:
                 log.error("Database corrupted: Number of release ranges"
                           " does not match number of release tags!")
                 recreate_project = True
 
             for i, (start, end, rc) in enumerate(self.doFetchAll()):
-                if i+1 >= len(revs) or recreate_project:
+                if i + 1 >= len(revs) or recreate_project:
                     # List of revisions to analyse was shortened
                     break
-                if (start, end) != (revs[i], revs[i+1]):
+                if (start, end) != (revs[i], revs[i + 1]):
                     log.error("Release range {} changed from {} to {}."
                               " Recreating project.".
-                              format(i, (start, end), (revs[i], revs[i+1])))
+                              format(i, (start, end), (revs[i], revs[i + 1])))
                     recreate_project = True
                     break
-                if rc != rcs[i+1]:
+                if rc != rcs[i + 1]:
                     log.error("Release candidate {} changed from {} to {}."
                               " Recreating project.".
-                              format(i, rc, rcs[i+1]))
+                              format(i, rc, rcs[i + 1]))
                     recreate_project = True
                     break
 
@@ -314,7 +321,7 @@ class DBManager:
             # This should ripple through the database and delete
             # all referencing entries for project
             log.warning("Deleting and re-creating project {}/{}.".
-                    format(project, tagging))
+                        format(project, tagging))
             self.doExecCommit("DELETE FROM `project` WHERE id=%s", (pid,))
             pid = self.getProjectID(project, tagging)
             tags = []
@@ -331,15 +338,15 @@ class DBManager:
                 previous_rev = tags[-1]
             for rev, rc in zip(revs, rcs)[len(tags):]:
                 self.doExecCommit("INSERT INTO release_timeline "
-                                    "(type, tag, projectId) "
-                                    "VALUES (%s, %s, %s)",
-                                    ("release", rev, pid))
+                                  "(type, tag, projectId) "
+                                  "VALUES (%s, %s, %s)",
+                                  ("release", rev, pid))
 
                 if previous_rev is not None and rc:
                     self.doExecCommit("INSERT INTO release_timeline "
-                                        "(type, tag, projectId) "
-                                        "VALUES (%s, %s, %s)",
-                                        ("rc", rc, pid))
+                                      "(type, tag, projectId) "
+                                      "VALUES (%s, %s, %s)",
+                                      ("rc", rc, pid))
 
                 if previous_rev is not None:
                     startID = self.getRevisionID(pid, previous_rev)
@@ -349,17 +356,18 @@ class DBManager:
                     else:
                         rcID = "NULL"
                     self.doExecCommit("INSERT INTO release_range "
-                                        "(releaseStartId, releaseEndId, "
-                                        "projectId, releaseRCStartId) "
-                                        "VALUES (%s, %s, %s, %s)",
-                                        (startID, endID, pid, rcID))
+                                      "(releaseStartId, releaseEndId, "
+                                      "projectId, releaseRCStartId) "
+                                      "VALUES (%s, %s, %s, %s)",
+                                      (startID, endID, pid, rcID))
                     new_ranges_to_process.append(self.getReleaseRangeID(pid,
-                            (startID, endID)))
+                                                                        
(startID, endID)))
                 previous_rev = rev
         # now we are in a well-defined state.
         # Return the ids of the release ranges we have to process
         return new_ranges_to_process
 
+
 def tstamp_to_sql(tstamp):
     """Convert a Unix timestamp into an SQL compatible DateTime string"""
-    return(datetime.utcfromtimestamp(tstamp).strftime("%Y-%m-%d %H:%M:%S"))
+    return (datetime.utcfromtimestamp(tstamp).strftime("%Y-%m-%d %H:%M:%S"))
-- 
2.11.0


Other related posts:

  • » [codeface] [PATCH] Treat MySQL error 2013 in DB manager - Claus Hunsen