Skip to content

Commit bab1cda

Browse files
committed
Use the hash modulo in the derivation outputs
Rather than storing the derivation outputs as `drvPath!outputName` internally, store them as `drvHashModulo!outputName` (or `outputHash!outputName` for fixed-output derivations). This makes the storage slightly more opaque, but enables an earlier cutoff in cases where a fixed-output dependency changes (but keeps the same output hash) − same as what we already do for input-addressed derivations.
1 parent 8914e01 commit bab1cda

File tree

7 files changed

+93
-89
lines changed

7 files changed

+93
-89
lines changed

src/libexpr/primops.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1107,7 +1107,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
11071107
// Shouldn't happen as the toplevel derivation is not CA.
11081108
assert(false);
11091109
},
1110-
[&](UnknownHashes) {
1110+
[&](DeferredHash _) {
11111111
for (auto & i : outputs) {
11121112
drv.outputs.insert_or_assign(i,
11131113
DerivationOutput {

src/libstore/build/derivation-goal.cc

Lines changed: 8 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -504,9 +504,6 @@ void DerivationGoal::inputsRealised()
504504
Derivation drvResolved { *std::move(attempt) };
505505

506506
auto pathResolved = writeDerivation(worker.store, drvResolved);
507-
/* Add to memotable to speed up downstream goal's queries with the
508-
original derivation. */
509-
drvPathResolutions.lock()->insert_or_assign(drvPath, pathResolved);
510507

511508
auto msg = fmt("Resolved derivation: '%s' -> '%s'",
512509
worker.store.printStorePath(drvPath),
@@ -2097,15 +2094,15 @@ struct RestrictedStore : public LocalFSStore, public virtual RestrictedStoreConf
20972094

20982095
void registerDrvOutput(const Realisation & info) override
20992096
{
2100-
if (!goal.isAllowed(info.id.drvPath))
2101-
throw InvalidPath("cannot register unknown drv output '%s' in recursive Nix", printStorePath(info.id.drvPath));
2097+
// XXX: Should we check for something here? Probably, but I'm not sure
2098+
// how
21022099
next->registerDrvOutput(info);
21032100
}
21042101

21052102
std::optional<const Realisation> queryRealisation(const DrvOutput & id) override
21062103
{
2107-
if (!goal.isAllowed(id.drvPath))
2108-
throw InvalidPath("cannot query the output info for unknown derivation '%s' in recursive Nix", printStorePath(id.drvPath));
2104+
// XXX: Should we check for something here? Probably, but I'm not sure
2105+
// how
21092106
return next->queryRealisation(id);
21102107
}
21112108

@@ -3394,23 +3391,14 @@ void DerivationGoal::registerOutputs()
33943391
means it's safe to link the derivation to the output hash. We must do
33953392
that for floating CA derivations, which otherwise couldn't be cached,
33963393
but it's fine to do in all cases. */
3397-
bool isCaFloating = drv->type() == DerivationType::CAFloating;
33983394

3399-
auto drvPathResolved = drvPath;
3400-
if (!useDerivation && isCaFloating) {
3401-
/* Once a floating CA derivations reaches this point, it
3402-
must already be resolved, so we don't bother trying to
3403-
downcast drv to get would would just be an empty
3404-
inputDrvs field. */
3405-
Derivation drv2 { *drv };
3406-
drvPathResolved = writeDerivation(worker.store, drv2);
3407-
}
3408-
3409-
if (settings.isExperimentalFeatureEnabled("ca-derivations"))
3395+
if (settings.isExperimentalFeatureEnabled("ca-derivations")) {
3396+
auto outputHashes = staticOutputHashes(worker.store, *drv);
34103397
for (auto& [outputName, newInfo] : infos)
34113398
worker.store.registerDrvOutput(Realisation{
3412-
.id = DrvOutput{drvPathResolved, outputName},
3399+
.id = DrvOutput{outputHashes.at(outputName), outputName},
34133400
.outPath = newInfo.path});
3401+
}
34143402
}
34153403

34163404

src/libstore/derivations.cc

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -496,10 +496,9 @@ static const DrvHashModulo pathDerivationModulo(Store & store, const StorePath &
496496
*/
497497
DrvHashModulo hashDerivationModulo(Store & store, const Derivation & drv, bool maskOutputs)
498498
{
499+
bool isDeferred = false;
499500
/* Return a fixed hash for fixed-output derivations. */
500501
switch (drv.type()) {
501-
case DerivationType::CAFloating:
502-
return UnknownHashes {};
503502
case DerivationType::CAFixed: {
504503
std::map<std::string, Hash> outputHashes;
505504
for (const auto & i : drv.outputs) {
@@ -512,6 +511,9 @@ DrvHashModulo hashDerivationModulo(Store & store, const Derivation & drv, bool m
512511
}
513512
return outputHashes;
514513
}
514+
case DerivationType::CAFloating:
515+
isDeferred = true;
516+
break;
515517
case DerivationType::InputAddressed:
516518
break;
517519
case DerivationType::DeferredInputAddressed:
@@ -522,13 +524,16 @@ DrvHashModulo hashDerivationModulo(Store & store, const Derivation & drv, bool m
522524
calls to this function. */
523525
std::map<std::string, StringSet> inputs2;
524526
for (auto & i : drv.inputDrvs) {
525-
bool hasUnknownHash = false;
526527
const auto & res = pathDerivationModulo(store, i.first);
527528
std::visit(overloaded {
528529
// Regular non-CA derivation, replace derivation
529530
[&](Hash drvHash) {
530531
inputs2.insert_or_assign(drvHash.to_string(Base16, false), i.second);
531532
},
533+
[&](DeferredHash deferredHash) {
534+
isDeferred = true;
535+
inputs2.insert_or_assign(deferredHash.hash.to_string(Base16, false), i.second);
536+
},
532537
// CA derivation's output hashes
533538
[&](CaOutputHashes outputHashes) {
534539
std::set<std::string> justOut = { "out" };
@@ -540,16 +545,37 @@ DrvHashModulo hashDerivationModulo(Store & store, const Derivation & drv, bool m
540545
justOut);
541546
}
542547
},
543-
[&](UnknownHashes) {
544-
hasUnknownHash = true;
545-
},
546548
}, res);
547-
if (hasUnknownHash) {
548-
return UnknownHashes {};
549-
}
550549
}
551550

552-
return hashString(htSHA256, drv.unparse(store, maskOutputs, &inputs2));
551+
auto hash = hashString(htSHA256, drv.unparse(store, maskOutputs, &inputs2));
552+
553+
if (isDeferred)
554+
return DeferredHash { hash };
555+
else
556+
return hash;
557+
}
558+
559+
560+
std::map<std::string, Hash> staticOutputHashes(Store& store, const Derivation& drv)
561+
{
562+
std::map<std::string, Hash> res;
563+
std::visit(overloaded {
564+
[&](Hash drvHash) {
565+
for (auto & outputName : drv.outputNames()) {
566+
res.insert({outputName, drvHash});
567+
}
568+
},
569+
[&](DeferredHash deferredHash) {
570+
for (auto & outputName : drv.outputNames()) {
571+
res.insert({outputName, deferredHash.hash});
572+
}
573+
},
574+
[&](CaOutputHashes outputHashes) {
575+
res = outputHashes;
576+
},
577+
}, hashDerivationModulo(store, drv, true));
578+
return res;
553579
}
554580

555581

@@ -719,9 +745,6 @@ static void rewriteDerivation(Store & store, BasicDerivation & drv, const String
719745

720746
}
721747

722-
723-
Sync<DrvPathResolutions> drvPathResolutions;
724-
725748
std::optional<BasicDerivation> Derivation::tryResolve(Store & store) {
726749
BasicDerivation resolved { *this };
727750

src/libstore/derivations.hh

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@ namespace nix {
1818
/* The traditional non-fixed-output derivation type. */
1919
struct DerivationOutputInputAddressed
2020
{
21-
/* Will need to become `std::optional<StorePath>` once input-addressed
22-
derivations are allowed to depend on cont-addressed derivations */
2321
StorePath path;
2422
};
2523

@@ -174,12 +172,12 @@ std::string outputPathName(std::string_view drvName, std::string_view outputName
174172
// whose output hashes are always known since they are fixed up-front.
175173
typedef std::map<std::string, Hash> CaOutputHashes;
176174

177-
struct UnknownHashes {};
175+
struct DeferredHash { Hash hash; };
178176

179177
typedef std::variant<
180178
Hash, // regular DRV normalized hash
181179
CaOutputHashes, // Fixed-output derivation hashes
182-
UnknownHashes // Deferred hashes for floating outputs drvs and their dependencies
180+
DeferredHash // Deferred hashes for floating outputs drvs and their dependencies
183181
> DrvHashModulo;
184182

185183
/* Returns hashes with the details of fixed-output subderivations
@@ -207,22 +205,18 @@ typedef std::variant<
207205
*/
208206
DrvHashModulo hashDerivationModulo(Store & store, const Derivation & drv, bool maskOutputs);
209207

208+
/*
209+
Return a map associating each output to a hash that uniquely identifies its
210+
derivation (modulo the self-references).
211+
*/
212+
std::map<std::string, Hash> staticOutputHashes(Store& store, const Derivation& drv);
213+
210214
/* Memoisation of hashDerivationModulo(). */
211215
typedef std::map<StorePath, DrvHashModulo> DrvHashes;
212216

213217
// FIXME: global, though at least thread-safe.
214218
extern Sync<DrvHashes> drvHashes;
215219

216-
/* Memoisation of `readDerivation(..).resove()`. */
217-
typedef std::map<
218-
StorePath,
219-
std::optional<StorePath>
220-
> DrvPathResolutions;
221-
222-
// FIXME: global, though at least thread-safe.
223-
// FIXME: arguably overlaps with hashDerivationModulo memo table.
224-
extern Sync<DrvPathResolutions> drvPathResolutions;
225-
226220
bool wantOutput(const string & output, const std::set<string> & wanted);
227221

228222
struct Source;

src/libstore/local-store.cc

Lines changed: 28 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -659,7 +659,7 @@ void LocalStore::registerDrvOutput(const Realisation & info)
659659
auto state(_state.lock());
660660
retrySQLite<void>([&]() {
661661
state->stmts->RegisterRealisedOutput.use()
662-
(info.id.drvPath.to_string())
662+
(info.id.strHash())
663663
(info.id.outputName)
664664
(printStorePath(info.outPath))
665665
.exec();
@@ -879,17 +879,18 @@ StorePathSet LocalStore::queryValidDerivers(const StorePath & path)
879879

880880
// Try to resolve the derivation at path `original`, with a caching layer
881881
// to make it more efficient
882-
std::optional<StorePath> cachedResolve(
883-
LocalStore & store,
884-
const StorePath & original)
882+
std::optional<Derivation> cachedResolve(
883+
LocalStore& store,
884+
const StorePath& original)
885885
{
886+
// This is quite dirty and leaky, but will disappear once #4340 is merged
887+
static Sync<std::map<StorePath, std::optional<Derivation>>> resolutionsCache;
886888
{
887-
auto resolutions = drvPathResolutions.lock();
888-
auto resolvedPathOptIter = resolutions->find(original);
889-
if (resolvedPathOptIter != resolutions->end()) {
890-
auto & [_, resolvedPathOpt] = *resolvedPathOptIter;
891-
if (resolvedPathOpt)
892-
return resolvedPathOpt;
889+
auto resolutions = resolutionsCache.lock();
890+
auto resolvedDrvIter = resolutions->find(original);
891+
if (resolvedDrvIter != resolutions->end()) {
892+
auto & [_, resolvedDrv] = *resolvedDrvIter;
893+
return *resolvedDrv;
893894
}
894895
}
895896

@@ -898,12 +899,9 @@ std::optional<StorePath> cachedResolve(
898899
auto attempt = drv.tryResolve(store);
899900
if (!attempt)
900901
return std::nullopt;
901-
/* Just compute store path */
902-
auto pathResolved =
903-
writeDerivation(store, *std::move(attempt), NoRepair, true);
904902
/* Store in memo table. */
905-
drvPathResolutions.lock()->insert_or_assign(original, pathResolved);
906-
return pathResolved;
903+
resolutionsCache.lock()->insert_or_assign(original, *attempt);
904+
return *attempt;
907905
}
908906

909907
std::map<std::string, std::optional<StorePath>>
@@ -933,26 +931,24 @@ LocalStore::queryPartialDerivationOutputMap(const StorePath& path_)
933931

934932
auto drv = readDerivation(path);
935933

936-
for (auto & output : drv.outputsAndOptPaths(*this)) {
937-
outputs.emplace(output.first, std::nullopt);
938-
}
939-
940934
auto resolvedDrv = cachedResolve(*this, path);
941935

942-
if (!resolvedDrv)
936+
if (!resolvedDrv) {
937+
for (auto& [outputName, _] : drv.outputsAndOptPaths(*this)) {
938+
if (!outputs.count(outputName))
939+
outputs.emplace(outputName, std::nullopt);
940+
}
943941
return outputs;
942+
}
944943

945-
retrySQLite<void>([&]() {
946-
auto state(_state.lock());
947-
path = *resolvedDrv;
948-
auto useQueryDerivationOutputs{
949-
state->stmts->QueryAllRealisedOutputs.use()(path.to_string())};
950-
951-
while (useQueryDerivationOutputs.next())
952-
outputs.insert_or_assign(
953-
useQueryDerivationOutputs.getStr(0),
954-
parseStorePath(useQueryDerivationOutputs.getStr(1)));
955-
});
944+
auto resolvedDrvHashes = staticOutputHashes(*this, *resolvedDrv);
945+
for (auto& [outputName, hash] : resolvedDrvHashes) {
946+
auto realisation = queryRealisation(DrvOutput{hash, outputName});
947+
if (realisation)
948+
outputs.insert_or_assign(outputName, realisation->outPath);
949+
else
950+
outputs.insert_or_assign(outputName, std::nullopt);
951+
}
956952

957953
return outputs;
958954
}
@@ -1695,12 +1691,11 @@ std::optional<const Realisation> LocalStore::queryRealisation(
16951691
typedef std::optional<const Realisation> Ret;
16961692
return retrySQLite<Ret>([&]() -> Ret {
16971693
auto state(_state.lock());
1698-
auto use(state->stmts->QueryRealisedOutput.use()(id.drvPath.to_string())(
1694+
auto use(state->stmts->QueryRealisedOutput.use()(id.strHash())(
16991695
id.outputName));
17001696
if (!use.next())
17011697
return std::nullopt;
17021698
auto outputPath = parseStorePath(use.getStr(0));
1703-
auto resolvedDrv = StorePath(use.getStr(1));
17041699
return Ret{
17051700
Realisation{.id = id, .outPath = outputPath}};
17061701
});

src/libstore/realisation.cc

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,18 @@ namespace nix {
77
MakeError(InvalidDerivationOutputId, Error);
88

99
DrvOutput DrvOutput::parse(const std::string &strRep) {
10-
const auto &[rawPath, outputs] = parsePathWithOutputs(strRep);
11-
if (outputs.size() != 1)
10+
size_t n = strRep.find("!");
11+
if (n == strRep.npos)
1212
throw InvalidDerivationOutputId("Invalid derivation output id %s", strRep);
1313

1414
return DrvOutput{
15-
.drvPath = StorePath(rawPath),
16-
.outputName = *outputs.begin(),
15+
.drvHash = Hash::parseAnyPrefixed(strRep.substr(0, n)),
16+
.outputName = strRep.substr(n+1),
1717
};
1818
}
1919

2020
std::string DrvOutput::to_string() const {
21-
return std::string(drvPath.to_string()) + "!" + outputName;
21+
return strHash() + "!" + outputName;
2222
}
2323

2424
nlohmann::json Realisation::toJSON() const {

src/libstore/realisation.hh

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,24 @@
66
namespace nix {
77

88
struct DrvOutput {
9-
StorePath drvPath;
9+
// The hash modulo of the derivation
10+
Hash drvHash;
1011
std::string outputName;
1112

1213
std::string to_string() const;
1314

15+
std::string strHash() const
16+
{ return drvHash.to_string(Base16, true); }
17+
1418
static DrvOutput parse(const std::string &);
1519

1620
bool operator<(const DrvOutput& other) const { return to_pair() < other.to_pair(); }
1721
bool operator==(const DrvOutput& other) const { return to_pair() == other.to_pair(); }
1822

1923
private:
2024
// Just to make comparison operators easier to write
21-
std::pair<StorePath, std::string> to_pair() const
22-
{ return std::make_pair(drvPath, outputName); }
25+
std::pair<Hash, std::string> to_pair() const
26+
{ return std::make_pair(drvHash, outputName); }
2327
};
2428

2529
struct Realisation {

0 commit comments

Comments
 (0)