* Use optimistic profile locking for nix-env operations like `-i' and

`-u'.  Instead of acquiring an exclusive lock on the profile for the
  entire duration of the operation, we just perform the operation
  optimistically (without an exclusive lock), and check at the end
  whether the profile changed while we were busy (i.e., the symlink
  target changed).  If so, the operation is restarted.  Restarting is
  generally cheap, since the build results are still in the Nix store.
  Most of the time, only the user environment has to be rebuilt.
This commit is contained in:
Eelco Dolstra 2008-08-04 16:21:45 +00:00
parent a87b5256e2
commit 339c142009
1 changed files with 167 additions and 142 deletions

View File

@ -215,8 +215,34 @@ static DrvInfos queryInstalled(EvalState & state, const Path & userEnv)
} }
static void createUserEnv(EvalState & state, const DrvInfos & elems, /* Ensure exclusive access to a profile. Any command that modifies
const Path & profile, bool keepDerivations) the profile first acquires this lock. */
static void lockProfile(PathLocks & lock, const Path & profile)
{
lock.lockPaths(singleton<PathSet>(profile),
(format("waiting for lock on profile `%1%'") % profile).str());
lock.setDeletion(true);
}
/* Optimistic locking is used by long-running operations like `nix-env
-i'. Instead of acquiring the exclusive lock for the entire
duration of the operation, we just perform the operation
optimistically (without an exclusive lock), and check at the end
whether the profile changed while we were busy (i.e., the symlink
target changed). If so, the operation is restarted. Restarting is
generally cheap, since the build results are still in the Nix
store. Most of the time, only the user environment has to be
rebuilt. */
static string optimisticLockProfile(const Path & profile)
{
return pathExists(profile) ? readLink(profile) : "";
}
static bool createUserEnv(EvalState & state, const DrvInfos & elems,
const Path & profile, bool keepDerivations,
const string & lockToken)
{ {
/* Build the components in the user environment, if they don't /* Build the components in the user environment, if they don't
exist already. */ exist already. */
@ -305,9 +331,20 @@ static void createUserEnv(EvalState & state, const DrvInfos & elems,
store->buildDerivations(singleton<PathSet>(topLevelDrv.queryDrvPath(state))); store->buildDerivations(singleton<PathSet>(topLevelDrv.queryDrvPath(state)));
/* Switch the current user environment to the output path. */ /* Switch the current user environment to the output path. */
PathLocks lock;
lockProfile(lock, profile);
Path lockTokenCur = optimisticLockProfile(profile);
if (lockToken != lockTokenCur) {
printMsg(lvlError, format("profile `%1%' changed while we were busy; restarting") % profile);
return false;
}
debug(format("switching to new user environment")); debug(format("switching to new user environment"));
Path generation = createGeneration(profile, topLevelDrv.queryOutPath(state)); Path generation = createGeneration(profile, topLevelDrv.queryOutPath(state));
switchLink(profile, generation); switchLink(profile, generation);
return true;
} }
@ -544,14 +581,6 @@ static void printMissing(EvalState & state, const DrvInfos & elems)
} }
static void lockProfile(PathLocks & lock, const Path & profile)
{
lock.lockPaths(singleton<PathSet>(profile),
(format("waiting for lock on profile `%1%'") % profile).str());
lock.setDeletion(true);
}
static void installDerivations(Globals & globals, static void installDerivations(Globals & globals,
const Strings & args, const Path & profile) const Strings & args, const Path & profile)
{ {
@ -574,35 +603,35 @@ static void installDerivations(Globals & globals,
/* Add in the already installed derivations, unless they have the /* Add in the already installed derivations, unless they have the
same name as a to-be-installed element. */ same name as a to-be-installed element. */
PathLocks lock;
lockProfile(lock, profile);
DrvInfos installedElems = queryInstalled(globals.state, profile);
DrvInfos allElems(newElems); while (true) {
for (DrvInfos::iterator i = installedElems.begin(); string lockToken = optimisticLockProfile(profile);
i != installedElems.end(); ++i)
{ DrvInfos installedElems = queryInstalled(globals.state, profile);
DrvName drvName(i->name);
MetaInfo meta = i->queryMetaInfo(globals.state); DrvInfos allElems(newElems);
if (!globals.preserveInstalled && foreach (DrvInfos::iterator, i, installedElems) {
newNames.find(drvName.name) != newNames.end() && DrvName drvName(i->name);
meta["keep"] != "true") MetaInfo meta = i->queryMetaInfo(globals.state);
printMsg(lvlInfo, if (!globals.preserveInstalled &&
format("replacing old `%1%'") % i->name); newNames.find(drvName.name) != newNames.end() &&
else meta["keep"] != "true")
allElems.push_back(*i); printMsg(lvlInfo,
format("replacing old `%1%'") % i->name);
else
allElems.push_back(*i);
}
foreach (DrvInfos::iterator, i, newElems)
printMsg(lvlInfo, format("installing `%1%'") % i->name);
printMissing(globals.state, newElems);
if (globals.dryRun) return;
if (createUserEnv(globals.state, allElems,
profile, globals.keepDerivations, lockToken)) break;
} }
for (DrvInfos::iterator i = newElems.begin(); i != newElems.end(); ++i)
printMsg(lvlInfo,
format("installing `%1%'") % i->name);
printMissing(globals.state, newElems);
if (globals.dryRun) return;
createUserEnv(globals.state, allElems,
profile, globals.keepDerivations);
} }
@ -634,77 +663,75 @@ static void upgradeDerivations(Globals & globals,
for a derivation in the input Nix expression that has the same for a derivation in the input Nix expression that has the same
name and a higher version number. */ name and a higher version number. */
/* Load the currently installed derivations. */ while (true) {
PathLocks lock; string lockToken = optimisticLockProfile(globals.profile);
lockProfile(lock, globals.profile);
DrvInfos installedElems = queryInstalled(globals.state, globals.profile); DrvInfos installedElems = queryInstalled(globals.state, globals.profile);
/* Fetch all derivations from the input file. */ /* Fetch all derivations from the input file. */
DrvInfos availElems; DrvInfos availElems;
queryInstSources(globals.state, globals.instSource, args, availElems, false); queryInstSources(globals.state, globals.instSource, args, availElems, false);
/* Go through all installed derivations. */ /* Go through all installed derivations. */
DrvInfos newElems; DrvInfos newElems;
for (DrvInfos::iterator i = installedElems.begin(); foreach (DrvInfos::iterator, i, installedElems) {
i != installedElems.end(); ++i) DrvName drvName(i->name);
{
DrvName drvName(i->name);
MetaInfo meta = i->queryMetaInfo(globals.state); MetaInfo meta = i->queryMetaInfo(globals.state);
if (meta["keep"] == "true") { if (meta["keep"] == "true") {
newElems.push_back(*i); newElems.push_back(*i);
continue; continue;
} }
/* Find the derivation in the input Nix expression with the /* Find the derivation in the input Nix expression with
same name that satisfies the version constraints specified the same name that satisfies the version constraints
by upgradeType. If there are multiple matches, take the specified by upgradeType. If there are multiple
one with the highest priority. If there are still multiple matches, take the one with the highest priority. If
matches, take the one with the highest version. */ there are still multiple matches, take the one with the
DrvInfos::iterator bestElem = availElems.end(); highest version. */
DrvName bestName; DrvInfos::iterator bestElem = availElems.end();
for (DrvInfos::iterator j = availElems.begin(); DrvName bestName;
j != availElems.end(); ++j) foreach (DrvInfos::iterator, j, availElems) {
{ DrvName newName(j->name);
DrvName newName(j->name); if (newName.name == drvName.name) {
if (newName.name == drvName.name) { int d = comparePriorities(globals.state, *i, *j);
int d = comparePriorities(globals.state, *i, *j); if (d == 0) d = compareVersions(drvName.version, newName.version);
if (d == 0) d = compareVersions(drvName.version, newName.version); if (upgradeType == utLt && d < 0 ||
if (upgradeType == utLt && d < 0 || upgradeType == utLeq && d <= 0 ||
upgradeType == utLeq && d <= 0 || upgradeType == utEq && d == 0 ||
upgradeType == utEq && d == 0 || upgradeType == utAlways)
upgradeType == utAlways) {
{ int d2 = -1;
int d2 = -1; if (bestElem != availElems.end()) {
if (bestElem != availElems.end()) { d2 = comparePriorities(globals.state, *bestElem, *j);
d2 = comparePriorities(globals.state, *bestElem, *j); if (d2 == 0) d2 = compareVersions(bestName.version, newName.version);
if (d2 == 0) d2 = compareVersions(bestName.version, newName.version); }
} if (d2 < 0) {
if (d2 < 0) { bestElem = j;
bestElem = j; bestName = newName;
bestName = newName; }
} }
} }
} }
}
if (bestElem != availElems.end() && if (bestElem != availElems.end() &&
i->queryOutPath(globals.state) != i->queryOutPath(globals.state) !=
bestElem->queryOutPath(globals.state)) bestElem->queryOutPath(globals.state))
{ {
printMsg(lvlInfo, printMsg(lvlInfo,
format("upgrading `%1%' to `%2%'") format("upgrading `%1%' to `%2%'")
% i->name % bestElem->name); % i->name % bestElem->name);
newElems.push_back(*bestElem); newElems.push_back(*bestElem);
} else newElems.push_back(*i); } else newElems.push_back(*i);
} }
printMissing(globals.state, newElems); printMissing(globals.state, newElems);
if (globals.dryRun) return; if (globals.dryRun) return;
createUserEnv(globals.state, newElems, if (createUserEnv(globals.state, newElems,
globals.profile, globals.keepDerivations); globals.profile, globals.keepDerivations, lockToken)) break;
}
} }
@ -748,29 +775,27 @@ static void opSetFlag(Globals & globals,
string flagValue = *arg++; string flagValue = *arg++;
DrvNames selectors = drvNamesFromArgs(Strings(arg, opArgs.end())); DrvNames selectors = drvNamesFromArgs(Strings(arg, opArgs.end()));
/* Load the currently installed derivations. */ while (true) {
PathLocks lock; string lockToken = optimisticLockProfile(globals.profile);
lockProfile(lock, globals.profile);
DrvInfos installedElems = queryInstalled(globals.state, globals.profile);
/* Update all matching derivations. */ DrvInfos installedElems = queryInstalled(globals.state, globals.profile);
for (DrvInfos::iterator i = installedElems.begin();
i != installedElems.end(); ++i) /* Update all matching derivations. */
{ foreach (DrvInfos::iterator, i, installedElems) {
DrvName drvName(i->name); DrvName drvName(i->name);
for (DrvNames::iterator j = selectors.begin(); foreach (DrvNames::iterator, j, selectors)
j != selectors.end(); ++j) if (j->matches(drvName)) {
if (j->matches(drvName)) { printMsg(lvlInfo,
printMsg(lvlInfo, format("setting flag on `%1%'") % i->name);
format("setting flag on `%1%'") % i->name); setMetaFlag(globals.state, *i, flagName, flagValue);
setMetaFlag(globals.state, *i, flagName, flagValue); break;
break; }
} }
/* Write the new user environment. */
if (createUserEnv(globals.state, installedElems,
globals.profile, globals.keepDerivations, lockToken)) break;
} }
/* Write the new user environment. */
createUserEnv(globals.state, installedElems,
globals.profile, globals.keepDerivations);
} }
@ -812,33 +837,33 @@ static void opSet(Globals & globals,
static void uninstallDerivations(Globals & globals, Strings & selectors, static void uninstallDerivations(Globals & globals, Strings & selectors,
Path & profile) Path & profile)
{ {
PathLocks lock; while (true) {
lockProfile(lock, profile); string lockToken = optimisticLockProfile(profile);
DrvInfos installedElems = queryInstalled(globals.state, profile);
DrvInfos newElems;
for (DrvInfos::iterator i = installedElems.begin(); DrvInfos installedElems = queryInstalled(globals.state, profile);
i != installedElems.end(); ++i) DrvInfos newElems;
{
DrvName drvName(i->name); foreach (DrvInfos::iterator, i, installedElems) {
bool found = false; DrvName drvName(i->name);
for (Strings::iterator j = selectors.begin(); j != selectors.end(); ++j) bool found = false;
/* !!! the repeated calls to followLinksToStorePath() are foreach (Strings::iterator, j, selectors)
expensive, should pre-compute them. */ /* !!! the repeated calls to followLinksToStorePath()
if ((isPath(*j) && i->queryOutPath(globals.state) == followLinksToStorePath(*j)) are expensive, should pre-compute them. */
|| DrvName(*j).matches(drvName)) if ((isPath(*j) && i->queryOutPath(globals.state) == followLinksToStorePath(*j))
{ || DrvName(*j).matches(drvName))
printMsg(lvlInfo, format("uninstalling `%1%'") % i->name); {
found = true; printMsg(lvlInfo, format("uninstalling `%1%'") % i->name);
break; found = true;
} break;
if (!found) newElems.push_back(*i); }
if (!found) newElems.push_back(*i);
}
if (globals.dryRun) return;
if (createUserEnv(globals.state, newElems,
profile, globals.keepDerivations, lockToken)) break;
} }
if (globals.dryRun) return;
createUserEnv(globals.state, newElems,
profile, globals.keepDerivations);
} }