Author: eelco
Date: Sun Dec 5 18:23:19 2010
New Revision: 24979
URL: https://svn.nixos.org/websvn/nix/?rev=24979&sc=1
Log:
* Retry a transaction if SQLite returns SQLITE_BUSY. This can happen
even with a very long busy timeout, because SQLITE_BUSY is also
returned to resolve deadlocks. This should get rid of random
"database is locked" errors. This is kind of hard to test though.
* Fix a horrible bug in deleteFromStore(): deletePathWrapped() should
be called after committing the transaction, not before, because the
commit might not succeed.
Modified:
nix/branches/sqlite/src/libstore/local-store.cc
Modified: nix/branches/sqlite/src/libstore/local-store.cc
==============================================================================
--- nix/branches/sqlite/src/libstore/local-store.cc Sun Dec 5 18:22:14
2010 (r24978)
+++ nix/branches/sqlite/src/libstore/local-store.cc Sun Dec 5 18:23:19
2010 (r24979)
@@ -22,22 +22,31 @@
namespace nix {
-
-class SQLiteError : public Error
+
+MakeError(SQLiteError, Error);
+MakeError(SQLiteBusy, SQLiteError);
+
+
+static void throwSQLiteError(sqlite3 * db, const format & f)
+ __attribute__ ((noreturn));
+
+static void throwSQLiteError(sqlite3 * db, const format & f)
{
-public:
- SQLiteError(sqlite3 * db, const format & f)
- : Error(format("%1%: %2%") % f.str() % sqlite3_errmsg(db))
- {
+ int err = sqlite3_errcode(db);
+ if (err == SQLITE_BUSY) {
+ printMsg(lvlError, "warning: SQLite database is busy");
+ throw SQLiteBusy(format("%1%: %2%") % f.str() % sqlite3_errmsg(db));
}
-};
+ else
+ throw SQLiteError(format("%1%: %2%") % f.str() % sqlite3_errmsg(db));
+}
SQLite::~SQLite()
{
try {
if (db && sqlite3_close(db) != SQLITE_OK)
- throw SQLiteError(db, "closing database");
+ throwSQLiteError(db, "closing database");
} catch (...) {
ignoreException();
}
@@ -49,7 +58,7 @@
checkInterrupt();
assert(!stmt);
if (sqlite3_prepare_v2(db, s.c_str(), -1, &stmt, 0) != SQLITE_OK)
- throw SQLiteError(db, "creating statement");
+ throwSQLiteError(db, "creating statement");
this->db = db;
}
@@ -58,7 +67,7 @@
{
assert(stmt);
if (sqlite3_reset(stmt) != SQLITE_OK)
- throw SQLiteError(db, "resetting statement");
+ throwSQLiteError(db, "resetting statement");
curArg = 1;
}
@@ -67,7 +76,7 @@
{
try {
if (stmt && sqlite3_finalize(stmt) != SQLITE_OK)
- throw SQLiteError(db, "finalizing statement");
+ throwSQLiteError(db, "finalizing statement");
} catch (...) {
ignoreException();
}
@@ -77,28 +86,28 @@
void SQLiteStmt::bind(const string & value)
{
if (sqlite3_bind_text(stmt, curArg++, value.c_str(), -1, SQLITE_TRANSIENT)
!= SQLITE_OK)
- throw SQLiteError(db, "binding argument");
+ throwSQLiteError(db, "binding argument");
}
void SQLiteStmt::bind(int value)
{
if (sqlite3_bind_int(stmt, curArg++, value) != SQLITE_OK)
- throw SQLiteError(db, "binding argument");
+ throwSQLiteError(db, "binding argument");
}
void SQLiteStmt::bind64(long long value)
{
if (sqlite3_bind_int64(stmt, curArg++, value) != SQLITE_OK)
- throw SQLiteError(db, "binding argument");
+ throwSQLiteError(db, "binding argument");
}
void SQLiteStmt::bind()
{
if (sqlite3_bind_null(stmt, curArg++) != SQLITE_OK)
- throw SQLiteError(db, "binding argument");
+ throwSQLiteError(db, "binding argument");
}
@@ -132,14 +141,14 @@
SQLiteTxn(sqlite3 * db) : active(false) {
this->db = db;
if (sqlite3_exec(db, "begin;", 0, 0, 0) != SQLITE_OK)
- throw SQLiteError(db, "starting transaction");
+ throwSQLiteError(db, "starting transaction");
active = true;
}
void commit()
{
if (sqlite3_exec(db, "commit;", 0, 0, 0) != SQLITE_OK)
- throw SQLiteError(db, "committing transaction");
+ throwSQLiteError(db, "committing transaction");
active = false;
}
@@ -147,7 +156,7 @@
{
try {
if (active && sqlite3_exec(db, "rollback;", 0, 0, 0) != SQLITE_OK)
- throw SQLiteError(db, "aborting transaction");
+ throwSQLiteError(db, "aborting transaction");
} catch (...) {
ignoreException();
}
@@ -289,10 +298,10 @@
throw Error("cannot open SQLite database");
if (sqlite3_busy_timeout(db, 60 * 60 * 1000) != SQLITE_OK)
- throw SQLiteError(db, "setting timeout");
+ throwSQLiteError(db, "setting timeout");
if (sqlite3_exec(db, "pragma foreign_keys = 1;", 0, 0, 0) != SQLITE_OK)
- throw SQLiteError(db, "enabling foreign keys");
+ throwSQLiteError(db, "enabling foreign keys");
/* !!! check whether sqlite has been built with foreign key
support */
@@ -303,7 +312,7 @@
crashes. */
string syncMode = queryBoolSetting("fsync-metadata", true) ? "normal" :
"off";
if (sqlite3_exec(db, ("pragma synchronous = " + syncMode + ";").c_str(),
0, 0, 0) != SQLITE_OK)
- throw SQLiteError(db, "setting synchronous mode");
+ throwSQLiteError(db, "setting synchronous mode");
/* Set the SQLite journal mode. WAL mode is fastest, but doesn't
seem entirely stable at the moment (Oct. 2010). Thus, use
@@ -314,31 +323,31 @@
SQLiteStmt stmt;
stmt.create(db, "pragma main.journal_mode;");
if (sqlite3_step(stmt) != SQLITE_ROW)
- throw SQLiteError(db, "querying journal mode");
+ throwSQLiteError(db, "querying journal mode");
prevMode = string((const char *) sqlite3_column_text(stmt, 0));
}
if (prevMode != mode &&
sqlite3_exec(db, ("pragma main.journal_mode = " + mode + ";").c_str(),
0, 0, 0) != SQLITE_OK)
- throw SQLiteError(db, "setting journal mode");
+ throwSQLiteError(db, "setting journal mode");
/* Increase the auto-checkpoint interval to 8192 pages. This
seems enough to ensure that instantiating the NixOS system
derivation is done in a single fsync(). */
if (sqlite3_exec(db, "pragma wal_autocheckpoint = 8192;", 0, 0, 0) !=
SQLITE_OK)
- throw SQLiteError(db, "setting autocheckpoint interval");
+ throwSQLiteError(db, "setting autocheckpoint interval");
/* Initialise the database schema, if necessary. */
if (create) {
#include "schema.sql.hh"
if (sqlite3_exec(db, (const char *) schema, 0, 0, 0) != SQLITE_OK)
- throw SQLiteError(db, "initialising database schema");
+ throwSQLiteError(db, "initialising database schema");
}
/* Backwards compatibility with old (pre-release) databases. Can
remove this eventually. */
if (sqlite3_table_column_metadata(db, 0, "ValidPaths", "narSize", 0, 0, 0,
0, 0) != SQLITE_OK) {
if (sqlite3_exec(db, "alter table ValidPaths add column narSize
integer" , 0, 0, 0) != SQLITE_OK)
- throw SQLiteError(db, "adding column narSize");
+ throwSQLiteError(db, "adding column narSize");
}
/* Prepare SQL statements. */
@@ -460,7 +469,7 @@
else
stmtRegisterValidPath.bind(); // null
if (sqlite3_step(stmtRegisterValidPath) != SQLITE_DONE)
- throw SQLiteError(db, format("registering valid path `%1%' in
database") % info.path);
+ throwSQLiteError(db, format("registering valid path `%1%' in
database") % info.path);
unsigned long long id = sqlite3_last_insert_rowid(db);
/* If this is a derivation, then store the derivation outputs in
@@ -475,7 +484,7 @@
stmtAddDerivationOutput.bind(i->first);
stmtAddDerivationOutput.bind(i->second.path);
if (sqlite3_step(stmtAddDerivationOutput) != SQLITE_DONE)
- throw SQLiteError(db, format("adding derivation output for
`%1%' in database") % info.path);
+ throwSQLiteError(db, format("adding derivation output for
`%1%' in database") % info.path);
}
}
@@ -489,7 +498,7 @@
stmtAddReference.bind(referrer);
stmtAddReference.bind(reference);
if (sqlite3_step(stmtAddReference) != SQLITE_DONE)
- throw SQLiteError(db, "adding reference to database");
+ throwSQLiteError(db, "adding reference to database");
}
@@ -498,15 +507,23 @@
assert(info.hash.type == htSHA256);
ValidPathInfo info2(info);
if (info2.registrationTime == 0) info2.registrationTime = time(0);
-
- SQLiteTxn txn(db);
-
- unsigned long long id = addValidPath(info2);
- foreach (PathSet::const_iterator, i, info2.references)
- addReference(id, queryValidPathId(*i));
-
- txn.commit();
+ while (1) {
+ try {
+ SQLiteTxn txn(db);
+
+ unsigned long long id = addValidPath(info2);
+
+ foreach (PathSet::const_iterator, i, info2.references)
+ addReference(id, queryValidPathId(*i));
+
+ txn.commit();
+ break;
+ } catch (SQLiteBusy & e) {
+ /* Retry; the `txn' destructor will roll back the current
+ transaction. */
+ }
+ }
}
@@ -517,7 +534,7 @@
stmtRegisterFailedPath.bind(path);
stmtRegisterFailedPath.bind(time(0));
if (sqlite3_step(stmtRegisterFailedPath) != SQLITE_DONE)
- throw SQLiteError(db, format("registering failed path `%1%'") % path);
+ throwSQLiteError(db, format("registering failed path `%1%'") % path);
}
@@ -527,7 +544,7 @@
stmtHasPathFailed.bind(path);
int res = sqlite3_step(stmtHasPathFailed);
if (res != SQLITE_DONE && res != SQLITE_ROW)
- throw SQLiteError(db, "querying whether path failed");
+ throwSQLiteError(db, "querying whether path failed");
return res == SQLITE_ROW;
}
@@ -545,7 +562,7 @@
}
if (r != SQLITE_DONE)
- throw SQLiteError(db, "error querying failed paths");
+ throwSQLiteError(db, "error querying failed paths");
return res;
}
@@ -559,7 +576,7 @@
SQLiteStmtUse use(stmtClearFailedPath);
stmtClearFailedPath.bind(*i);
if (sqlite3_step(stmtClearFailedPath) != SQLITE_DONE)
- throw SQLiteError(db, format("clearing failed path `%1%' in
database") % *i);
+ throwSQLiteError(db, format("clearing failed path `%1%' in
database") % *i);
}
txn.commit();
@@ -594,7 +611,7 @@
int r = sqlite3_step(stmtQueryPathInfo);
if (r == SQLITE_DONE) throw Error(format("path `%1%' is not valid") %
path);
- if (r != SQLITE_ROW) throw SQLiteError(db, "querying path in database");
+ if (r != SQLITE_ROW) throwSQLiteError(db, "querying path in database");
info.id = sqlite3_column_int(stmtQueryPathInfo, 0);
@@ -622,7 +639,7 @@
}
if (r != SQLITE_DONE)
- throw SQLiteError(db, format("error getting references of `%1%'") %
path);
+ throwSQLiteError(db, format("error getting references of `%1%'") %
path);
return info;
}
@@ -635,7 +652,7 @@
int res = sqlite3_step(stmtQueryPathInfo);
if (res == SQLITE_ROW) return sqlite3_column_int(stmtQueryPathInfo, 0);
if (res == SQLITE_DONE) throw Error(format("path `%1%' is not valid") %
path);
- throw SQLiteError(db, "querying path in database");
+ throwSQLiteError(db, "querying path in database");
}
@@ -645,7 +662,7 @@
stmtQueryPathInfo.bind(path);
int res = sqlite3_step(stmtQueryPathInfo);
if (res != SQLITE_DONE && res != SQLITE_ROW)
- throw SQLiteError(db, "querying path in database");
+ throwSQLiteError(db, "querying path in database");
return res == SQLITE_ROW;
}
@@ -665,7 +682,7 @@
}
if (r != SQLITE_DONE)
- throw SQLiteError(db, "error getting valid paths");
+ throwSQLiteError(db, "error getting valid paths");
return res;
}
@@ -695,7 +712,7 @@
}
if (r != SQLITE_DONE)
- throw SQLiteError(db, format("error getting references of `%1%'") %
path);
+ throwSQLiteError(db, format("error getting references of `%1%'") %
path);
}
@@ -721,7 +738,7 @@
}
if (r != SQLITE_DONE)
- throw SQLiteError(db, format("error getting valid derivers of `%1%'")
% path);
+ throwSQLiteError(db, format("error getting valid derivers of `%1%'") %
path);
return derivers;
}
@@ -743,7 +760,7 @@
}
if (r != SQLITE_DONE)
- throw SQLiteError(db, format("error getting outputs of `%1%'") % path);
+ throwSQLiteError(db, format("error getting outputs of `%1%'") % path);
return outputs;
}
@@ -890,7 +907,7 @@
stmtInvalidatePath.bind(path);
if (sqlite3_step(stmtInvalidatePath) != SQLITE_DONE)
- throw SQLiteError(db, format("invalidating path `%1%' in database") %
path);
+ throwSQLiteError(db, format("invalidating path `%1%' in database") %
path);
/* Note that the foreign key constraints on the Refs table take
care of deleting the references entries for `path'. */
@@ -1248,20 +1265,25 @@
assertStorePath(path);
- SQLiteTxn txn(db);
+ while (1) {
+ try {
+ SQLiteTxn txn(db);
- if (isValidPath(path)) {
- PathSet referrers; queryReferrers(path, referrers);
- referrers.erase(path); /* ignore self-references */
- if (!referrers.empty())
- throw PathInUse(format("cannot delete path `%1%' because it is in
use by `%2%'")
- % path % showPaths(referrers));
- invalidatePath(path);
- }
+ if (isValidPath(path)) {
+ PathSet referrers; queryReferrers(path, referrers);
+ referrers.erase(path); /* ignore self-references */
+ if (!referrers.empty())
+ throw PathInUse(format("cannot delete path `%1%' because
it is in use by `%2%'")
+ % path % showPaths(referrers));
+ invalidatePath(path);
+ }
+ txn.commit();
+ break;
+ } catch (SQLiteBusy & e) { };
+ }
+
deletePathWrapped(path, bytesFreed, blocksFreed);
-
- txn.commit();
}
_______________________________________________
nix-commits mailing list
[email protected]
http://mail.cs.uu.nl/mailman/listinfo/nix-commits