Handle SQLITE_BUSY errors gracefully
authorRogier <rogier777@gmail.com>
Wed, 23 Dec 2015 10:38:50 +0000 (11:38 +0100)
committerest31 <MTest31@outlook.com>
Tue, 29 Dec 2015 23:08:56 +0000 (00:08 +0100)
This allows other applications (e.g. minetestmapper) to interrogate
the database while minetest is running, without causing an almost
certain minetest crash.

src/database-sqlite3.cpp
src/database-sqlite3.h

index da3e2c86b931ba11d3c3dd57c48ae615dae6e6dd..d2af042e6d666d86a80bb910ca5a3b3305a4aa60 100644 (file)
@@ -31,10 +31,19 @@ SQLite format specification:
 #include "filesys.h"
 #include "exceptions.h"
 #include "settings.h"
+#include "porting.h"
 #include "util/string.h"
 
 #include <cassert>
 
+// When to print messages when the database is being held locked by another process
+// Note: I've seen occasional delays of over 250ms while running minetestmapper.
+#define BUSY_INFO_TRESHOLD     100     // Print first informational message after 100ms.
+#define BUSY_WARNING_TRESHOLD  250     // Print warning message after 250ms. Lag is increased.
+#define BUSY_ERROR_TRESHOLD    1000    // Print error message after 1000ms. Significant lag.
+#define BUSY_FATAL_TRESHOLD    3000    // Allow SQLITE_BUSY to be returned, which will cause a minetest crash.
+#define BUSY_ERROR_INTERVAL    10000   // Safety net: report again every 10 seconds
+
 
 #define SQLRES(s, r) \
        if ((s) != (r)) { \
@@ -56,6 +65,51 @@ SQLite format specification:
                         sqlite3_errmsg(m_database)); \
        }
 
+int Database_SQLite3::busyHandler(void *data, int count)
+{
+       s64 &first_time = reinterpret_cast<s64 *>(data)[0];
+       s64 &prev_time = reinterpret_cast<s64 *>(data)[1];
+       s64 cur_time = getTimeMs();
+
+       if (count == 0) {
+               first_time = cur_time;
+               prev_time = first_time;
+       } else {
+               while (cur_time < prev_time)
+                       cur_time += s64(1)<<32;
+       }
+
+       if (cur_time - first_time < BUSY_INFO_TRESHOLD) {
+               ; // do nothing
+       } else if (cur_time - first_time >= BUSY_INFO_TRESHOLD &&
+                       prev_time - first_time < BUSY_INFO_TRESHOLD) {
+               infostream << "SQLite3 database has been locked for "
+                       << cur_time - first_time << " ms." << std::endl;
+       } else if (cur_time - first_time >= BUSY_WARNING_TRESHOLD &&
+                       prev_time - first_time < BUSY_WARNING_TRESHOLD) {
+               warningstream << "Sqlite3 database has been locked for "
+                       << cur_time - first_time << " ms." << std::endl;
+       } else if (cur_time - first_time >= BUSY_ERROR_TRESHOLD &&
+                       prev_time - first_time < BUSY_ERROR_TRESHOLD) {
+               errorstream << "SQLite3 database has been locked for "
+                       << cur_time - first_time << " ms; this causes lag." << std::endl;
+       } else if (cur_time - first_time >= BUSY_FATAL_TRESHOLD &&
+                       prev_time - first_time < BUSY_FATAL_TRESHOLD) {
+               errorstream << "Sqlite3 database has been locked for "
+                       << cur_time - first_time << " ms - giving up!" << std::endl;
+       } else if ((cur_time - first_time) / BUSY_ERROR_INTERVAL !=
+                       (prev_time - first_time) / BUSY_ERROR_INTERVAL) {
+               // Safety net: keep reporting every BUSY_ERROR_INTERVAL
+               errorstream << "SQLite3 database has been locked for "
+                       << (cur_time - first_time) / 1000 << " seconds!" << std::endl;
+       }
+
+       prev_time = cur_time;
+
+       // Make sqlite transaction fail if delay exceeds BUSY_FATAL_TRESHOLD
+       return cur_time - first_time < BUSY_FATAL_TRESHOLD;
+}
+
 
 Database_SQLite3::Database_SQLite3(const std::string &savedir) :
        m_initialized(false),
@@ -107,6 +161,12 @@ void Database_SQLite3::openDatabase()
                throw FileNotGoodException("Cannot open database file");
        }
 
+       if (sqlite3_busy_handler(m_database, sqlite3BusyHandler, busy_handler_data) != SQLITE_OK) {
+               errorstream << "SQLite3 database failed to set busy handler: "
+                       << sqlite3_errmsg(m_database) << std::endl;
+               throw FileNotGoodException("Failed to set busy handler for sqlite connection");
+       }
+
        if (needs_create) {
                createDatabase();
        }
index a775742be83ba1ef241b3575147a4858e61b7c9d..04a1825d98368cb65ff0bf60b71b8e46353ee259 100644 (file)
@@ -63,6 +63,10 @@ private:
        sqlite3_stmt *m_stmt_delete;
        sqlite3_stmt *m_stmt_begin;
        sqlite3_stmt *m_stmt_end;
+
+       s64 m_busy_handler_data[2];
+
+       static int busyHandler(void *data, int count);
 };
 
 #endif