Make restarting state machines explicit

If my memory is correct, @edolstra objected to modifying `wantedOutputs`
upon falling back to doing a build (as we did before), because we should
only modify it in response to new requests --- *actual* wants --- and
not because we are "incidentally" building all the outptus beyond what
may have been requested.

That's a fair point, and the alternative is to replace the boolean soup
with proper enums: Instead of modifying `wantedOuputs` som more, we'll
modify `needsRestart` to indicate we are passed the need.
This commit is contained in:
John Ericson 2023-02-14 13:25:55 -05:00
parent 37fca662b0
commit 0f2b5146c7
2 changed files with 83 additions and 18 deletions

View file

@ -145,8 +145,20 @@ void DerivationGoal::work()
void DerivationGoal::addWantedOutputs(const OutputsSpec & outputs) void DerivationGoal::addWantedOutputs(const OutputsSpec & outputs)
{ {
auto newWanted = wantedOutputs.union_(outputs); auto newWanted = wantedOutputs.union_(outputs);
if (!newWanted.isSubsetOf(wantedOutputs)) switch (needRestart) {
needRestart = true; case NeedRestartForMoreOutputs::OutputsUnmodifedDontNeed:
if (!newWanted.isSubsetOf(wantedOutputs))
needRestart = NeedRestartForMoreOutputs::OutputsAddedDoNeed;
break;
case NeedRestartForMoreOutputs::OutputsAddedDoNeed:
/* No need to check whether we added more outputs, because a
restart is already queued up. */
break;
case NeedRestartForMoreOutputs::BuildInProgressWillNotNeed:
/* We are already building all outputs, so it doesn't matter if
we now want more. */
break;
};
wantedOutputs = newWanted; wantedOutputs = newWanted;
} }
@ -297,12 +309,29 @@ void DerivationGoal::outputsSubstitutionTried()
In particular, it may be the case that the hole in the closure is In particular, it may be the case that the hole in the closure is
an output of the current derivation, which causes a loop if retried. an output of the current derivation, which causes a loop if retried.
*/ */
if (nrIncompleteClosure > 0 && nrIncompleteClosure == nrFailed) retrySubstitution = true; {
bool substitutionFailed =
nrIncompleteClosure > 0 &&
nrIncompleteClosure == nrFailed;
switch (retrySubstitution) {
case RetrySubstitution::NoNeed:
if (substitutionFailed)
retrySubstitution = RetrySubstitution::YesNeed;
break;
case RetrySubstitution::YesNeed:
// Should not be able to reach this state from here.
assert(false);
break;
case RetrySubstitution::AlreadyRetried:
debug("substitution failed again, but we already retried once. Not retrying again.");
break;
}
}
nrFailed = nrNoSubstituters = nrIncompleteClosure = 0; nrFailed = nrNoSubstituters = nrIncompleteClosure = 0;
if (needRestart) { if (needRestart == NeedRestartForMoreOutputs::OutputsAddedDoNeed) {
needRestart = false; needRestart = NeedRestartForMoreOutputs::OutputsUnmodifedDontNeed;
haveDerivation(); haveDerivation();
return; return;
} }
@ -330,9 +359,9 @@ void DerivationGoal::outputsSubstitutionTried()
produced using a substitute. So we have to build instead. */ produced using a substitute. So we have to build instead. */
void DerivationGoal::gaveUpOnSubstitution() void DerivationGoal::gaveUpOnSubstitution()
{ {
/* Make sure checkPathValidity() from now on checks all /* At this point we are building all outputs, so if more are wanted there
outputs. */ is no need to restart. */
wantedOutputs = OutputsSpec::All { }; needRestart = NeedRestartForMoreOutputs::BuildInProgressWillNotNeed;
/* The inputs must be built before we can build this goal. */ /* The inputs must be built before we can build this goal. */
inputDrvOutputs.clear(); inputDrvOutputs.clear();
@ -455,8 +484,8 @@ void DerivationGoal::inputsRealised()
return; return;
} }
if (retrySubstitution && !retriedSubstitution) { if (retrySubstitution == RetrySubstitution::YesNeed) {
retriedSubstitution = true; retrySubstitution = RetrySubstitution::AlreadyRetried;
haveDerivation(); haveDerivation();
return; return;
} }

View file

@ -78,22 +78,58 @@ struct DerivationGoal : public Goal
*/ */
std::map<std::pair<StorePath, std::string>, StorePath> inputDrvOutputs; std::map<std::pair<StorePath, std::string>, StorePath> inputDrvOutputs;
/**
* See `needRestart`; just for that field.
*/
enum struct NeedRestartForMoreOutputs {
/**
* The goal state machine is progressing based on the current value of
* `wantedOutputs. No actions are needed.
*/
OutputsUnmodifedDontNeed,
/**
* `wantedOutputs` has been extended, but the state machine is
* proceeding according to its old value, so we need to restart.
*/
OutputsAddedDoNeed,
/**
* The goal state machine has progressed to the point of doing a build,
* in which case all outputs will be produced, so extensions to
* `wantedOutputs` no longer require a restart.
*/
BuildInProgressWillNotNeed,
};
/** /**
* Whether additional wanted outputs have been added. * Whether additional wanted outputs have been added.
*/ */
bool needRestart = false; NeedRestartForMoreOutputs needRestart = NeedRestartForMoreOutputs::OutputsUnmodifedDontNeed;
/**
* See `retrySubstitution`; just for that field.
*/
enum RetrySubstitution {
/**
* No issues have yet arose, no need to restart.
*/
NoNeed,
/**
* Something failed and there is an incomplete closure. Let's retry
* substituting.
*/
YesNeed,
/**
* We are current or have already retried substitution, and whether or
* not something goes wrong we will not retry again.
*/
AlreadyRetried,
};
/** /**
* Whether to retry substituting the outputs after building the * Whether to retry substituting the outputs after building the
* inputs. This is done in case of an incomplete closure. * inputs. This is done in case of an incomplete closure.
*/ */
bool retrySubstitution = false; RetrySubstitution retrySubstitution = RetrySubstitution::NoNeed;
/**
* Whether we've retried substitution, in which case we won't try
* again.
*/
bool retriedSubstitution = false;
/** /**
* The derivation stored at drvPath. * The derivation stored at drvPath.