Improve SQLite busy handling

This commit is contained in:
Eelco Dolstra 2017-02-28 13:59:11 +01:00
parent 34b12bad59
commit fd86dd93dd
No known key found for this signature in database
GPG key ID: 8170B4726D7198DE
3 changed files with 43 additions and 31 deletions

View file

@ -265,7 +265,7 @@ AC_CHECK_FUNCS([setresuid setreuid lchown])
# Nice to have, but not essential. # Nice to have, but not essential.
AC_CHECK_FUNCS([strsignal posix_fallocate nanosleep sysconf]) AC_CHECK_FUNCS([strsignal posix_fallocate sysconf])
# This is needed if bzip2 is a static library, and the Nix libraries # This is needed if bzip2 is a static library, and the Nix libraries

View file

@ -3,6 +3,8 @@
#include <sqlite3.h> #include <sqlite3.h>
#include <atomic>
namespace nix { namespace nix {
[[noreturn]] void throwSQLiteError(sqlite3 * db, const format & f) [[noreturn]] void throwSQLiteError(sqlite3 * db, const format & f)
@ -13,27 +15,10 @@ namespace nix {
if (!path) path = "(in-memory)"; if (!path) path = "(in-memory)";
if (err == SQLITE_BUSY || err == SQLITE_PROTOCOL) { if (err == SQLITE_BUSY || err == SQLITE_PROTOCOL) {
if (err == SQLITE_PROTOCOL) throw SQLiteBusy(
printError("warning: SQLite database %s is busy (SQLITE_PROTOCOL)", path); err == SQLITE_PROTOCOL
else { ? fmt("SQLite database %s is busy (SQLITE_PROTOCOL)", path)
static bool warned = false; : fmt("SQLite database %s is busy", path));
if (!warned) {
printError("warning: SQLite database %s is busy", path);
warned = true;
}
}
/* Sleep for a while since retrying the transaction right away
is likely to fail again. */
checkInterrupt();
#if HAVE_NANOSLEEP
struct timespec t;
t.tv_sec = 0;
t.tv_nsec = (random() % 100) * 1000 * 1000; /* <= 0.1s */
nanosleep(&t, 0);
#else
sleep(1);
#endif
throw SQLiteBusy("%s: %s (in %s)", f.str(), sqlite3_errstr(err), path);
} }
else else
throw SQLiteError("%s: %s (in %s)", f.str(), sqlite3_errstr(err), path); throw SQLiteError("%s: %s (in %s)", f.str(), sqlite3_errstr(err), path);
@ -58,24 +43,27 @@ SQLite::~SQLite()
void SQLite::exec(const std::string & stmt) void SQLite::exec(const std::string & stmt)
{ {
if (sqlite3_exec(db, stmt.c_str(), 0, 0, 0) != SQLITE_OK) retrySQLite<void>([&]() {
throwSQLiteError(db, format("executing SQLite statement %s") % stmt); if (sqlite3_exec(db, stmt.c_str(), 0, 0, 0) != SQLITE_OK)
throwSQLiteError(db, format("executing SQLite statement %s") % stmt);
});
} }
void SQLiteStmt::create(sqlite3 * db, const string & s) void SQLiteStmt::create(sqlite3 * db, const string & sql)
{ {
checkInterrupt(); checkInterrupt();
assert(!stmt); assert(!stmt);
if (sqlite3_prepare_v2(db, s.c_str(), -1, &stmt, 0) != SQLITE_OK) if (sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, 0) != SQLITE_OK)
throwSQLiteError(db, "creating statement"); throwSQLiteError(db, fmt("creating statement %s", sql));
this->db = db; this->db = db;
this->sql = sql;
} }
SQLiteStmt::~SQLiteStmt() SQLiteStmt::~SQLiteStmt()
{ {
try { try {
if (stmt && sqlite3_finalize(stmt) != SQLITE_OK) if (stmt && sqlite3_finalize(stmt) != SQLITE_OK)
throwSQLiteError(db, "finalizing statement"); throwSQLiteError(db, fmt("finalizing statement %s", sql));
} catch (...) { } catch (...) {
ignoreException(); ignoreException();
} }
@ -132,14 +120,14 @@ void SQLiteStmt::Use::exec()
int r = step(); int r = step();
assert(r != SQLITE_ROW); assert(r != SQLITE_ROW);
if (r != SQLITE_DONE) if (r != SQLITE_DONE)
throwSQLiteError(stmt.db, "executing SQLite statement"); throwSQLiteError(stmt.db, fmt("executing SQLite statement %s", stmt.sql));
} }
bool SQLiteStmt::Use::next() bool SQLiteStmt::Use::next()
{ {
int r = step(); int r = step();
if (r != SQLITE_DONE && r != SQLITE_ROW) if (r != SQLITE_DONE && r != SQLITE_ROW)
throwSQLiteError(stmt.db, "executing SQLite query"); throwSQLiteError(stmt.db, fmt("executing SQLite query %s", stmt.sql));
return r == SQLITE_ROW; return r == SQLITE_ROW;
} }
@ -186,4 +174,24 @@ SQLiteTxn::~SQLiteTxn()
} }
} }
void handleSQLiteBusy(const SQLiteBusy & e)
{
static std::atomic<time_t> lastWarned{0};
time_t now = time(0);
if (now > lastWarned + 10) {
lastWarned = now;
printError("warning: %s", e.what());
}
/* Sleep for a while since retrying the transaction right away
is likely to fail again. */
checkInterrupt();
struct timespec t;
t.tv_sec = 0;
t.tv_nsec = (random() % 100) * 1000 * 1000; /* <= 0.1s */
nanosleep(&t, 0);
}
} }

View file

@ -30,8 +30,9 @@ struct SQLiteStmt
{ {
sqlite3 * db = 0; sqlite3 * db = 0;
sqlite3_stmt * stmt = 0; sqlite3_stmt * stmt = 0;
std::string sql;
SQLiteStmt() { } SQLiteStmt() { }
SQLiteStmt(sqlite3 * db, const std::string & s) { create(db, s); } SQLiteStmt(sqlite3 * db, const std::string & sql) { create(db, sql); }
void create(sqlite3 * db, const std::string & s); void create(sqlite3 * db, const std::string & s);
~SQLiteStmt(); ~SQLiteStmt();
operator sqlite3_stmt * () { return stmt; } operator sqlite3_stmt * () { return stmt; }
@ -94,6 +95,8 @@ MakeError(SQLiteBusy, SQLiteError);
[[noreturn]] void throwSQLiteError(sqlite3 * db, const format & f); [[noreturn]] void throwSQLiteError(sqlite3 * db, const format & f);
void handleSQLiteBusy(const SQLiteBusy & e);
/* Convenience function for retrying a SQLite transaction when the /* Convenience function for retrying a SQLite transaction when the
database is busy. */ database is busy. */
template<typename T> template<typename T>
@ -103,6 +106,7 @@ T retrySQLite(std::function<T()> fun)
try { try {
return fun(); return fun();
} catch (SQLiteBusy & e) { } catch (SQLiteBusy & e) {
handleSQLiteBusy(e);
} }
} }
} }