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.
|
# 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
|
||||||
|
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
retrySQLite<void>([&]() {
|
||||||
if (sqlite3_exec(db, stmt.c_str(), 0, 0, 0) != SQLITE_OK)
|
if (sqlite3_exec(db, stmt.c_str(), 0, 0, 0) != SQLITE_OK)
|
||||||
throwSQLiteError(db, format("executing SQLite statement ‘%s’") % stmt);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue