* Wrap deleteFromStore() in a transaction. Otherwise there might be a

race with other processes that add new referrers to a path,
  resulting in the garbage collector crashing with "foreign key
  constraint failed".  (Nix/4)
* Make --gc --print-dead etc. interruptible.
This commit is contained in:
Eelco Dolstra 2010-10-14 15:55:51 +00:00
parent bfa6ee7d91
commit 64fd29855a
2 changed files with 13 additions and 8 deletions

View file

@ -421,7 +421,7 @@ struct LocalStore::GCState
}; };
static bool doDelete(GCOptions::GCAction action) static bool shouldDelete(GCOptions::GCAction action)
{ {
return action == GCOptions::gcDeleteDead return action == GCOptions::gcDeleteDead
|| action == GCOptions::gcDeleteSpecific; || action == GCOptions::gcDeleteSpecific;
@ -438,6 +438,8 @@ bool LocalStore::isActiveTempFile(const GCState & state,
bool LocalStore::tryToDelete(GCState & state, const Path & path) bool LocalStore::tryToDelete(GCState & state, const Path & path)
{ {
checkInterrupt();
if (!pathExists(path)) return true; if (!pathExists(path)) return true;
if (state.deleted.find(path) != state.deleted.end()) return true; if (state.deleted.find(path) != state.deleted.end()) return true;
if (state.live.find(path) != state.live.end()) return false; if (state.live.find(path) != state.live.end()) return false;
@ -516,7 +518,7 @@ bool LocalStore::tryToDelete(GCState & state, const Path & path)
} }
/* The path is garbage, so delete it. */ /* The path is garbage, so delete it. */
if (doDelete(state.options.action)) { if (shouldDelete(state.options.action)) {
printMsg(lvlInfo, format("deleting `%1%'") % path); printMsg(lvlInfo, format("deleting `%1%'") % path);
unsigned long long bytesFreed, blocksFreed; unsigned long long bytesFreed, blocksFreed;
@ -625,7 +627,7 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
vector<Path> entries_(entries.begin(), entries.end()); vector<Path> entries_(entries.begin(), entries.end());
random_shuffle(entries_.begin(), entries_.end()); random_shuffle(entries_.begin(), entries_.end());
if (doDelete(state.options.action)) if (shouldDelete(state.options.action))
printMsg(lvlError, format("deleting garbage...")); printMsg(lvlError, format("deleting garbage..."));
else else
printMsg(lvlError, format("determining live/dead paths...")); printMsg(lvlError, format("determining live/dead paths..."));

View file

@ -298,11 +298,10 @@ void LocalStore::openDB(bool create)
if (sqlite3_exec(db, ("pragma synchronous = " + syncMode + ";").c_str(), 0, 0, 0) != SQLITE_OK) if (sqlite3_exec(db, ("pragma synchronous = " + syncMode + ";").c_str(), 0, 0, 0) != SQLITE_OK)
throw SQLiteError(db, "setting synchronous mode"); throw SQLiteError(db, "setting synchronous mode");
/* Set the SQLite journal mode. The default is write-ahead /* Set the SQLite journal mode. WAL mode is fastest, but doesn't
logging since it's the fastest and supports more concurrency. seem entirely stable at the moment (Oct. 2010). Thus, use
The downside is that it doesn't work over NFS, so allow truncate mode by default. */
truncate mode alternatively. */ string mode = queryBoolSetting("use-sqlite-wal", false) ? "wal" : "truncate";
string mode = queryBoolSetting("use-sqlite-wal", true) ? "wal" : "truncate";
string prevMode; string prevMode;
{ {
SQLiteStmt stmt; SQLiteStmt stmt;
@ -1220,6 +1219,8 @@ void LocalStore::deleteFromStore(const Path & path, unsigned long long & bytesFr
assertStorePath(path); assertStorePath(path);
SQLiteTxn txn(db);
if (isValidPath(path)) { if (isValidPath(path)) {
PathSet referrers; queryReferrers(path, referrers); PathSet referrers; queryReferrers(path, referrers);
referrers.erase(path); /* ignore self-references */ referrers.erase(path); /* ignore self-references */
@ -1230,6 +1231,8 @@ void LocalStore::deleteFromStore(const Path & path, unsigned long long & bytesFr
} }
deletePathWrapped(path, bytesFreed, blocksFreed); deletePathWrapped(path, bytesFreed, blocksFreed);
txn.commit();
} }