* Automatically upgrade the Berkeley DB environment if necessary.

This commit is contained in:
Eelco Dolstra 2005-05-09 15:25:47 +00:00
parent 88dea78cdf
commit 8f57634c14
2 changed files with 134 additions and 107 deletions

View File

@ -165,127 +165,152 @@ static int my_fsync(int fd)
} }
void Database::open(const string & path) void Database::open2(const string & path, bool removeOldEnv)
{ {
if (env) throw Error(format("environment already open")); if (env) throw Error(format("environment already open"));
try { debug(format("opening database environment"));
debug(format("opening database environment"));
/* Create the database environment object. */ /* Create the database environment object. */
DbEnv * env = 0; /* !!! close on error */ DbEnv * env = 0; /* !!! close on error */
env = new DbEnv(0); env = new DbEnv(0);
/* Smaller log files. */ /* Smaller log files. */
env->set_lg_bsize(32 * 1024); /* default */ env->set_lg_bsize(32 * 1024); /* default */
env->set_lg_max(256 * 1024); /* must be > 4 * lg_bsize */ env->set_lg_max(256 * 1024); /* must be > 4 * lg_bsize */
/* Write the log, but don't sync. This protects transactions /* Write the log, but don't sync. This protects transactions
against application crashes, but if the system crashes, against application crashes, but if the system crashes, some
some transactions may be undone. An acceptable risk, I transactions may be undone. An acceptable risk, I think. */
think. */ env->set_flags(DB_TXN_WRITE_NOSYNC | DB_LOG_AUTOREMOVE, 1);
env->set_flags(DB_TXN_WRITE_NOSYNC | DB_LOG_AUTOREMOVE, 1);
/* Increase the locking limits. If you ever get `Dbc::get: Cannot
/* Increase the locking limits. If you ever get `Dbc::get: allocate memory' or similar, especially while running
Cannot allocate memory' or similar, especially while `nix-store --verify', just increase the following number, then
running `nix-store --verify', just increase the following run db_recover on the database to remove the existing DB
number, then run db_recover on the database to remove the environment (since changes only take effect on new
existing DB environment (since changes only take effect on environments). */
new environments). */ env->set_lk_max_locks(10000);
env->set_lk_max_locks(10000); env->set_lk_max_lockers(10000);
env->set_lk_max_lockers(10000); env->set_lk_max_objects(10000);
env->set_lk_max_objects(10000); env->set_lk_detect(DB_LOCK_DEFAULT);
env->set_lk_detect(DB_LOCK_DEFAULT);
/* Dangerous, probably, but from the docs it *seems* that BDB
/* Dangerous, probably, but from the docs it *seems* that BDB shouldn't sync when DB_TXN_WRITE_NOSYNC is used, but it still
shouldn't sync when DB_TXN_WRITE_NOSYNC is used, but it fsync()s sometimes. */
still fsync()s sometimes. */ db_env_set_func_fsync(my_fsync);
db_env_set_func_fsync(my_fsync);
/* The following code provides automatic recovery of the /* The following code provides automatic recovery of the database
database environment. Recovery is necessary when a process environment. Recovery is necessary when a process dies while
dies while it has the database open. To detect this, it has the database open. To detect this, processes atomically
processes atomically increment a counter when they open the increment a counter when they open the database, and decrement
database, and decrement it when they close it. If we see it when they close it. If we see that counter is > 0 but no
that counter is > 0 but no processes are accessing the processes are accessing the database---determined by attempting
database---determined by attempting to obtain a write lock to obtain a write lock on a lock file on which all accessors
on a lock file on which all accessors have a read lock---we have a read lock---we must run recovery. Note that this also
must run recovery. Note that this also ensures that we ensures that we only run recovery when there are no other
only run recovery when there are no other accessors (which accessors (which could cause database corruption). */
could cause database corruption). */
/* !!! close fdAccessors / fdLock on exception */ /* !!! close fdAccessors / fdLock on exception */
/* Open the accessor count file. */ /* Open the accessor count file. */
string accessorsPath = path + "/accessor_count"; string accessorsPath = path + "/accessor_count";
fdAccessors = ::open(accessorsPath.c_str(), O_RDWR | O_CREAT, 0666); fdAccessors = ::open(accessorsPath.c_str(), O_RDWR | O_CREAT, 0666);
if (fdAccessors == -1) if (fdAccessors == -1)
if (errno == EACCES) if (errno == EACCES)
throw DbNoPermission( throw DbNoPermission(
format("permission denied to database in `%1%'") % accessorsPath); format("permission denied to database in `%1%'") % accessorsPath);
else else
throw SysError(format("opening file `%1%'") % accessorsPath); throw SysError(format("opening file `%1%'") % accessorsPath);
/* Open the lock file. */ /* Open the lock file. */
string lockPath = path + "/access_lock"; string lockPath = path + "/access_lock";
fdLock = ::open(lockPath.c_str(), O_RDWR | O_CREAT, 0666); fdLock = ::open(lockPath.c_str(), O_RDWR | O_CREAT, 0666);
if (fdLock == -1) if (fdLock == -1)
throw SysError(format("opening lock file `%1%'") % lockPath); throw SysError(format("opening lock file `%1%'") % lockPath);
/* Try to acquire a write lock. */ /* Try to acquire a write lock. */
debug(format("attempting write lock on `%1%'") % lockPath); debug(format("attempting write lock on `%1%'") % lockPath);
if (lockFile(fdLock, ltWrite, false)) { /* don't wait */ if (lockFile(fdLock, ltWrite, false)) { /* don't wait */
debug(format("write lock granted")); debug(format("write lock granted"));
/* We have a write lock, which means that there are no /* We have a write lock, which means that there are no other
other readers or writers. */ readers or writers. */
int n = getAccessorCount(fdAccessors); if (removeOldEnv) {
printMsg(lvlError, "removing old Berkeley DB database environment...");
if (n != 0) { env->remove(path.c_str(), DB_FORCE);
printMsg(lvlTalkative, return;
format("accessor count is %1%, running recovery") % n); }
/* Open the environment after running recovery. */ int n = getAccessorCount(fdAccessors);
openEnv(env, path, DB_RECOVER);
} if (n != 0) {
printMsg(lvlTalkative,
format("accessor count is %1%, running recovery") % n);
else /* Open the environment after running recovery. */
/* Open the environment normally. */ openEnv(env, path, DB_RECOVER);
openEnv(env, path, 0); }
setAccessorCount(fdAccessors, 1); else
/* Downgrade to a read lock. */
debug(format("downgrading to read lock on `%1%'") % lockPath);
lockFile(fdLock, ltRead, true);
} else {
/* There are other accessors. */
debug(format("write lock refused"));
/* Acquire a read lock. */
debug(format("acquiring read lock on `%1%'") % lockPath);
lockFile(fdLock, ltRead, true); /* wait indefinitely */
/* Increment the accessor count. */
lockFile(fdAccessors, ltWrite, true);
int n = getAccessorCount(fdAccessors) + 1;
setAccessorCount(fdAccessors, n);
debug(format("incremented accessor count to %1%") % n);
lockFile(fdAccessors, ltNone, true);
/* Open the environment normally. */ /* Open the environment normally. */
openEnv(env, path, 0); openEnv(env, path, 0);
}
this->env = env; setAccessorCount(fdAccessors, 1);
/* Downgrade to a read lock. */
debug(format("downgrading to read lock on `%1%'") % lockPath);
lockFile(fdLock, ltRead, true);
} else {
/* There are other accessors. */
debug(format("write lock refused"));
/* Acquire a read lock. */
debug(format("acquiring read lock on `%1%'") % lockPath);
lockFile(fdLock, ltRead, true); /* wait indefinitely */
} catch (DbException e) { rethrow(e); } /* Increment the accessor count. */
lockFile(fdAccessors, ltWrite, true);
int n = getAccessorCount(fdAccessors) + 1;
setAccessorCount(fdAccessors, n);
debug(format("incremented accessor count to %1%") % n);
lockFile(fdAccessors, ltNone, true);
/* Open the environment normally. */
openEnv(env, path, 0);
}
this->env = env;
}
void Database::open(const string & path)
{
try {
open2(path, false);
} catch (DbException e) {
if (e.get_errno() == DB_VERSION_MISMATCH) {
/* Remove the environment while we are holding the global
lock. If things go wrong there, we bail out. !!!
there is some leakage here op DbEnv and lock
handles. */
open2(path, true);
/* Try again. */
open2(path, false);
}
else
rethrow(e);
}
} }

View File

@ -58,6 +58,8 @@ private:
Db * getDb(TableId table); Db * getDb(TableId table);
void open2(const string & path, bool removeOldEnv);
public: public:
Database(); Database();
~Database(); ~Database();