From 0ee22c2145482571014d843ffa899ef579317eb7 Mon Sep 17 00:00:00 2001
From: Thomas Bruederli <thomas@roundcube.net>
Date: Thu, 12 Sep 2013 11:17:07 -0400
Subject: [PATCH] Retry queries on deadlock errors from InnoDB row-level locking (MySQL)

---
 program/lib/Roundcube/rcube_db_mysql.php |   25 ++++++++++++
 program/lib/Roundcube/rcube_db.php       |   36 ++++++++++++-----
 2 files changed, 50 insertions(+), 11 deletions(-)

diff --git a/program/lib/Roundcube/rcube_db.php b/program/lib/Roundcube/rcube_db.php
index 8520700..e66226f 100644
--- a/program/lib/Roundcube/rcube_db.php
+++ b/program/lib/Roundcube/rcube_db.php
@@ -386,17 +386,7 @@
         $result = $this->dbh->query($query);
 
         if ($result === false) {
-            $error = $this->dbh->errorInfo();
-
-            if (empty($this->options['ignore_key_errors']) || $error[0] != '23000') {
-                $this->db_error = true;
-                $this->db_error_msg = sprintf('[%s] %s', $error[1], $error[2]);
-
-                rcube::raise_error(array('code' => 500, 'type' => 'db',
-                    'line' => __LINE__, 'file' => __FILE__,
-                    'message' => $this->db_error_msg . " (SQL Query: $query)"
-                    ), true, false);
-            }
+            $result = $this->handle_error($query);
         }
 
         $this->last_result = $result;
@@ -405,6 +395,30 @@
     }
 
     /**
+     * Helper method to handle DB errors.
+     * This by default logs the error but could be overriden by a driver implementation
+     *
+     * @param string Query that triggered the error
+     * @return mixed Result to be stored and returned
+     */
+    protected function handle_error($query)
+    {
+        $error = $this->dbh->errorInfo();
+
+        if (empty($this->options['ignore_key_errors']) || $error[0] != '23000') {
+            $this->db_error = true;
+            $this->db_error_msg = sprintf('[%s] %s', $error[1], $error[2]);
+
+            rcube::raise_error(array('code' => 500, 'type' => 'db',
+                'line' => __LINE__, 'file' => __FILE__,
+                'message' => $this->db_error_msg . " (SQL Query: $query)"
+                ), true, false);
+        }
+
+        return false;
+    }
+
+    /**
      * Get number of affected rows for the last query
      *
      * @param mixed $result Optional query handle
diff --git a/program/lib/Roundcube/rcube_db_mysql.php b/program/lib/Roundcube/rcube_db_mysql.php
index 6fa5ad7..24f9ce1 100644
--- a/program/lib/Roundcube/rcube_db_mysql.php
+++ b/program/lib/Roundcube/rcube_db_mysql.php
@@ -179,4 +179,29 @@
         return isset($this->variables[$varname]) ? $this->variables[$varname] : $default;
     }
 
+    /**
+     * Handle DB errors, re-issue the query on deadlock errors from InnoDB row-level locking
+     *
+     * @param string Query that triggered the error
+     * @return mixed Result to be stored and returned
+     */
+    protected function handle_error($query)
+    {
+        $error = $this->dbh->errorInfo();
+
+        // retry after "Deadlock found when trying to get lock" errors
+        $retries = 2;
+        while ($error[1] == 1213 && $retries >= 0) {
+            usleep(50000);  // wait 50 ms
+            $result = $this->dbh->query($query);
+            if ($result !== false) {
+                return $result;
+            }
+            $error = $this->dbh->errorInfo();
+            $retries--;
+        }
+
+        return parent::handle_error($query);
+    }
+
 }

--
Gitblit v1.9.1