Blob Blame History Raw
From 24413dc44b0637d6c64e6b2105c2bcf1b99849a5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Dan=20Vr=C3=A1til?= <dvratil@redhat.com>
Date: Sun, 6 Apr 2014 19:50:38 +0200
Subject: [PATCH 13/16] Disable global transaction mutex for QSQLITE3 and
 enable transaction recording

Our QSQLITE3 driver now supports concurrency, so we don't need to serialize
transactions in DataStore anymore. It is however still needed for the
QSQLITE driver shipped with Qt.

Secondary, concurrency support also means possible transactions deadlocks and
timeouts, so we also need to enable transaction recording and replaying for
the QSQLITE3 backend.
---
 server/src/storage/datastore.cpp    | 18 +++++++++++-------
 server/src/storage/datastore.h      |  2 +-
 server/src/storage/dbtype.cpp       |  5 +++++
 server/src/storage/dbtype.h         |  3 +++
 server/src/storage/querybuilder.cpp | 20 ++++++++++++++++----
 server/src/storage/querybuilder.h   |  2 +-
 6 files changed, 37 insertions(+), 13 deletions(-)

diff --git a/server/src/storage/datastore.cpp b/server/src/storage/datastore.cpp
index 57d1e4e..0f04fa5 100644
--- a/server/src/storage/datastore.cpp
+++ b/server/src/storage/datastore.cpp
@@ -61,8 +61,8 @@ using namespace Akonadi::Server;
 static QMutex sTransactionMutex;
 bool DataStore::s_hasForeignKeyConstraints = false;
 
-#define TRANSACTION_MUTEX_LOCK if ( DbType::type( m_database ) == DbType::Sqlite ) sTransactionMutex.lock()
-#define TRANSACTION_MUTEX_UNLOCK if ( DbType::type( m_database ) == DbType::Sqlite ) sTransactionMutex.unlock()
+#define TRANSACTION_MUTEX_LOCK if ( DbType::isSystemSQLite( m_database ) ) sTransactionMutex.lock()
+#define TRANSACTION_MUTEX_UNLOCK if ( DbType::isSystemSQLite( m_database ) ) sTransactionMutex.unlock()
 
 /***************************************************************************
  *   DataStore                                                           *
@@ -1083,23 +1083,27 @@ QDateTime DataStore::dateTimeToQDateTime( const QByteArray &dateTime )
 
 void DataStore::addQueryToTransaction( const QSqlQuery &query, bool isBatch )
 {
-  DbType::Type dbType = DbType::type( m_database );
   // This is used for replaying deadlocked transactions, so only record queries
   // for backends that support concurrent transactions.
-  if ( !inTransaction() || ( dbType != DbType::MySQL && dbType != DbType::PostgreSQL ) ) {
+  if ( !inTransaction() || DbType::isSystemSQLite( m_database ) ) {
     return;
   }
 
   m_transactionQueries.append( qMakePair( query, isBatch ) );
 }
 
-QSqlQuery DataStore::retryLastTransaction()
+QSqlQuery DataStore::retryLastTransaction( bool rollbackFirst )
 {
-  DbType::Type dbType = DbType::type( m_database );
-  if ( !inTransaction() || ( dbType != DbType::MySQL && dbType != DbType::PostgreSQL ) ) {
+  if ( !inTransaction() || DbType::isSystemSQLite( m_database ) ) {
     return QSqlQuery();
   }
 
+  if ( rollbackFirst ) {
+    // In some cases the SQL database won't rollback the failed transaction, so
+    // we need to do it manually
+    m_database.driver()->rollbackTransaction();
+  }
+
   // The database has rolled back the actual transaction, so reset the counter
   // to 0 and start a new one in beginTransaction(). Then restore the level
   // because this has to be completely transparent to the original caller
diff --git a/server/src/storage/datastore.h b/server/src/storage/datastore.h
index 8b4a2b7..8a0fe01 100644
--- a/server/src/storage/datastore.h
+++ b/server/src/storage/datastore.h
@@ -317,7 +317,7 @@ protected:
      * @return Returns an invalid query when error occurs, or the last replayed
      *         query on success.
      */
-    QSqlQuery retryLastTransaction();
+    QSqlQuery retryLastTransaction( bool rollbackFirst );
 
   private Q_SLOTS:
     void sendKeepAliveQuery();
diff --git a/server/src/storage/dbtype.cpp b/server/src/storage/dbtype.cpp
index 495f532..7df2fb1 100644
--- a/server/src/storage/dbtype.cpp
+++ b/server/src/storage/dbtype.cpp
@@ -39,3 +39,8 @@ DbType::Type DbType::typeForDriverName( const QString &driverName )
   }
   return Unknown;
 }
+
+bool DbType::isSystemSQLite( const QSqlDatabase &db )
+{
+  return db.driverName() == QLatin1String( "QSQLITE" );
+}
diff --git a/server/src/storage/dbtype.h b/server/src/storage/dbtype.h
index a95a833..3595604 100644
--- a/server/src/storage/dbtype.h
+++ b/server/src/storage/dbtype.h
@@ -42,6 +42,9 @@ namespace DbType
   /** Returns the type for the given driver name. */
   Type typeForDriverName( const QString &driverName );
 
+  /** Returns true when using QSQLITE driver shipped with Qt, FALSE otherwise */
+  bool isSystemSQLite( const QSqlDatabase &db );
+
 } // namespace DbType
 } // namespace Server
 } // namespace Akonadi
diff --git a/server/src/storage/querybuilder.cpp b/server/src/storage/querybuilder.cpp
index 0abad4a..0530b11 100644
--- a/server/src/storage/querybuilder.cpp
+++ b/server/src/storage/querybuilder.cpp
@@ -320,10 +320,10 @@ QString QueryBuilder::buildQuery()
   return statement;
 }
 
-bool QueryBuilder::retryLastTransaction()
+bool QueryBuilder::retryLastTransaction( bool rollback )
 {
 #ifndef QUERYBUILDER_UNITTEST
-  mQuery = DataStore::self()->retryLastTransaction();
+  mQuery = DataStore::self()->retryLastTransaction( rollback );
   return !mQuery.lastError().isValid();
 #else
   return true;
@@ -400,9 +400,21 @@ bool QueryBuilder::exec()
         akDebug() << mQuery.lastError().text();
         return retryLastTransaction();
       }
+    } else if ( mDatabaseType == DbType::Sqlite && !DbType::isSystemSQLite( DataStore::self()->database() ) ) {
+      const int error = mQuery.lastError().number();
+      if ( error == 6 /* SQLITE_LOCKED */ ) {
+        akDebug() << "QueryBuilder::exec(): database reported transaction deadlock, retrying transaction";
+        akDebug() << mQuery.lastError().text();
+        return retryLastTransaction();
+      } else if ( error == 5 /* SQLITE_BUSY */ ) {
+        akDebug() << "QueryBuilder::exec(): database reported transaction timeout, retrying transaction";
+        akDebug() << mQuery.lastError().text();
+        return retryLastTransaction( true );
+      }
     } else if ( mDatabaseType == DbType::Sqlite ) {
-      // We can't have a transaction deadlock in SQLite, because it does not support
-      // concurrent transactions and DataStore serializes them through a global lock.
+      // We can't have a transaction deadlock in SQLite when using driver shipped
+      // with Qt, because it does not support concurrent transactions and DataStore
+      // serializes them through a global lock.
     }
 
     akError() << "DATABASE ERROR:";
diff --git a/server/src/storage/querybuilder.h b/server/src/storage/querybuilder.h
index 235a099..b380f93 100644
--- a/server/src/storage/querybuilder.h
+++ b/server/src/storage/querybuilder.h
@@ -244,7 +244,7 @@ class QueryBuilder
      */
     void sqliteAdaptUpdateJoin( Query::Condition &cond );
 
-    bool retryLastTransaction();
+    bool retryLastTransaction( bool rollback = false);
 
   private:
     QString mTable;
-- 
1.9.0