Improve SQLite busy handling
This commit is contained in:
parent
34b12bad59
commit
fd86dd93dd
3 changed files with 43 additions and 31 deletions
|
@ -265,7 +265,7 @@ AC_CHECK_FUNCS([setresuid setreuid lchown])
|
|||
|
||||
|
||||
# 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
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
#include <sqlite3.h>
|
||||
|
||||
#include <atomic>
|
||||
|
||||
namespace nix {
|
||||
|
||||
[[noreturn]] void throwSQLiteError(sqlite3 * db, const format & f)
|
||||
|
@ -13,27 +15,10 @@ namespace nix {
|
|||
if (!path) path = "(in-memory)";
|
||||
|
||||
if (err == SQLITE_BUSY || err == SQLITE_PROTOCOL) {
|
||||
if (err == SQLITE_PROTOCOL)
|
||||
printError("warning: SQLite database ‘%s’ is busy (SQLITE_PROTOCOL)", path);
|
||||
else {
|
||||
static bool warned = false;
|
||||
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);
|
||||
throw SQLiteBusy(
|
||||
err == SQLITE_PROTOCOL
|
||||
? fmt("SQLite database ‘%s’ is busy (SQLITE_PROTOCOL)", path)
|
||||
: fmt("SQLite database ‘%s’ is busy", path));
|
||||
}
|
||||
else
|
||||
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)
|
||||
{
|
||||
retrySQLite<void>([&]() {
|
||||
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();
|
||||
assert(!stmt);
|
||||
if (sqlite3_prepare_v2(db, s.c_str(), -1, &stmt, 0) != SQLITE_OK)
|
||||
throwSQLiteError(db, "creating statement");
|
||||
if (sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, 0) != SQLITE_OK)
|
||||
throwSQLiteError(db, fmt("creating statement ‘%s’", sql));
|
||||
this->db = db;
|
||||
this->sql = sql;
|
||||
}
|
||||
|
||||
SQLiteStmt::~SQLiteStmt()
|
||||
{
|
||||
try {
|
||||
if (stmt && sqlite3_finalize(stmt) != SQLITE_OK)
|
||||
throwSQLiteError(db, "finalizing statement");
|
||||
throwSQLiteError(db, fmt("finalizing statement ‘%s’", sql));
|
||||
} catch (...) {
|
||||
ignoreException();
|
||||
}
|
||||
|
@ -132,14 +120,14 @@ void SQLiteStmt::Use::exec()
|
|||
int r = step();
|
||||
assert(r != SQLITE_ROW);
|
||||
if (r != SQLITE_DONE)
|
||||
throwSQLiteError(stmt.db, "executing SQLite statement");
|
||||
throwSQLiteError(stmt.db, fmt("executing SQLite statement ‘%s’", stmt.sql));
|
||||
}
|
||||
|
||||
bool SQLiteStmt::Use::next()
|
||||
{
|
||||
int r = step();
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -30,8 +30,9 @@ struct SQLiteStmt
|
|||
{
|
||||
sqlite3 * db = 0;
|
||||
sqlite3_stmt * stmt = 0;
|
||||
std::string sql;
|
||||
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);
|
||||
~SQLiteStmt();
|
||||
operator sqlite3_stmt * () { return stmt; }
|
||||
|
@ -94,6 +95,8 @@ MakeError(SQLiteBusy, SQLiteError);
|
|||
|
||||
[[noreturn]] void throwSQLiteError(sqlite3 * db, const format & f);
|
||||
|
||||
void handleSQLiteBusy(const SQLiteBusy & e);
|
||||
|
||||
/* Convenience function for retrying a SQLite transaction when the
|
||||
database is busy. */
|
||||
template<typename T>
|
||||
|
@ -103,6 +106,7 @@ T retrySQLite(std::function<T()> fun)
|
|||
try {
|
||||
return fun();
|
||||
} catch (SQLiteBusy & e) {
|
||||
handleSQLiteBusy(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue