|
38 | 38 |
|
39 | 39 | #include <nlohmann/json.hpp> |
40 | 40 | #include <boost/container/small_vector.hpp> |
| 41 | +#include <boost/unordered/concurrent_flat_map.hpp> |
41 | 42 |
|
42 | 43 | #include "nix/util/strings-inline.hh" |
43 | 44 |
|
@@ -264,6 +265,9 @@ EvalState::EvalState( |
264 | 265 | , debugRepl(nullptr) |
265 | 266 | , debugStop(false) |
266 | 267 | , trylevel(0) |
| 268 | + , srcToStore(make_ref<decltype(srcToStore)::element_type>()) |
| 269 | + , importResolutionCache(make_ref<decltype(importResolutionCache)::element_type>()) |
| 270 | + , fileEvalCache(make_ref<decltype(fileEvalCache)::element_type>()) |
267 | 271 | , regexCache(makeRegexCache()) |
268 | 272 | #if NIX_USE_BOEHMGC |
269 | 273 | , valueAllocCache(std::allocate_shared<void *>(traceable_allocator<void *>(), nullptr)) |
@@ -1022,63 +1026,90 @@ Value * ExprPath::maybeThunk(EvalState & state, Env & env) |
1022 | 1026 | return &v; |
1023 | 1027 | } |
1024 | 1028 |
|
1025 | | -void EvalState::evalFile(const SourcePath & path, Value & v, bool mustBeTrivial) |
| 1029 | +/** |
| 1030 | + * A helper `Expr` class to lets us parse and evaluate Nix expressions |
| 1031 | + * from a thunk, ensuring that every file is parsed/evaluated only |
| 1032 | + * once (via the thunk stored in `EvalState::fileEvalCache`). |
| 1033 | + */ |
| 1034 | +struct ExprParseFile : Expr, gc |
1026 | 1035 | { |
1027 | | - FileEvalCache::iterator i; |
1028 | | - if ((i = fileEvalCache.find(path)) != fileEvalCache.end()) { |
1029 | | - v = i->second; |
1030 | | - return; |
1031 | | - } |
| 1036 | + // FIXME: make this a reference (see below). |
| 1037 | + SourcePath path; |
| 1038 | + bool mustBeTrivial; |
1032 | 1039 |
|
1033 | | - auto resolvedPath = resolveExprPath(path); |
1034 | | - if ((i = fileEvalCache.find(resolvedPath)) != fileEvalCache.end()) { |
1035 | | - v = i->second; |
1036 | | - return; |
| 1040 | + ExprParseFile(SourcePath & path, bool mustBeTrivial) |
| 1041 | + : path(path) |
| 1042 | + , mustBeTrivial(mustBeTrivial) |
| 1043 | + { |
1037 | 1044 | } |
1038 | 1045 |
|
1039 | | - printTalkative("evaluating file '%1%'", resolvedPath); |
1040 | | - Expr * e = nullptr; |
| 1046 | + void eval(EvalState & state, Env & env, Value & v) override |
| 1047 | + { |
| 1048 | + printTalkative("evaluating file '%s'", path); |
| 1049 | + |
| 1050 | + auto e = state.parseExprFromFile(path); |
1041 | 1051 |
|
1042 | | - auto j = fileParseCache.find(resolvedPath); |
1043 | | - if (j != fileParseCache.end()) |
1044 | | - e = j->second; |
| 1052 | + try { |
| 1053 | + auto dts = |
| 1054 | + state.debugRepl |
| 1055 | + ? makeDebugTraceStacker( |
| 1056 | + state, *e, state.baseEnv, e->getPos(), "while evaluating the file '%s':", path.to_string()) |
| 1057 | + : nullptr; |
| 1058 | + |
| 1059 | + // Enforce that 'flake.nix' is a direct attrset, not a |
| 1060 | + // computation. |
| 1061 | + if (mustBeTrivial && !(dynamic_cast<ExprAttrs *>(e))) |
| 1062 | + state.error<EvalError>("file '%s' must be an attribute set", path).debugThrow(); |
| 1063 | + |
| 1064 | + state.eval(e, v); |
| 1065 | + } catch (Error & e) { |
| 1066 | + state.addErrorTrace(e, "while evaluating the file '%s':", path.to_string()); |
| 1067 | + throw; |
| 1068 | + } |
| 1069 | + } |
| 1070 | +}; |
1045 | 1071 |
|
1046 | | - if (!e) |
1047 | | - e = parseExprFromFile(resolvedPath); |
| 1072 | +void EvalState::evalFile(const SourcePath & path, Value & v, bool mustBeTrivial) |
| 1073 | +{ |
| 1074 | + auto resolvedPath = getConcurrent(*importResolutionCache, path); |
1048 | 1075 |
|
1049 | | - fileParseCache.emplace(resolvedPath, e); |
| 1076 | + if (!resolvedPath) { |
| 1077 | + resolvedPath = resolveExprPath(path); |
| 1078 | + importResolutionCache->emplace(path, *resolvedPath); |
| 1079 | + } |
1050 | 1080 |
|
1051 | | - try { |
1052 | | - auto dts = debugRepl ? makeDebugTraceStacker( |
1053 | | - *this, |
1054 | | - *e, |
1055 | | - this->baseEnv, |
1056 | | - e->getPos(), |
1057 | | - "while evaluating the file '%1%':", |
1058 | | - resolvedPath.to_string()) |
1059 | | - : nullptr; |
1060 | | - |
1061 | | - // Enforce that 'flake.nix' is a direct attrset, not a |
1062 | | - // computation. |
1063 | | - if (mustBeTrivial && !(dynamic_cast<ExprAttrs *>(e))) |
1064 | | - error<EvalError>("file '%s' must be an attribute set", path).debugThrow(); |
1065 | | - eval(e, v); |
1066 | | - } catch (Error & e) { |
1067 | | - addErrorTrace(e, "while evaluating the file '%1%':", resolvedPath.to_string()); |
1068 | | - throw; |
| 1081 | + if (auto v2 = getConcurrent(*fileEvalCache, *resolvedPath)) { |
| 1082 | + forceValue(**v2, noPos); |
| 1083 | + v = **v2; |
| 1084 | + return; |
1069 | 1085 | } |
1070 | 1086 |
|
1071 | | - fileEvalCache.emplace(resolvedPath, v); |
1072 | | - if (path != resolvedPath) |
1073 | | - fileEvalCache.emplace(path, v); |
| 1087 | + Value * vExpr; |
| 1088 | + // FIXME: put ExprParseFile on the stack instead of the heap once |
| 1089 | + // https://github.com/NixOS/nix/pull/13930 is merged. That will ensure |
| 1090 | + // the post-condition that `expr` is unreachable after |
| 1091 | + // `forceValue()` returns. |
| 1092 | + auto expr = new ExprParseFile{*resolvedPath, mustBeTrivial}; |
| 1093 | + |
| 1094 | + fileEvalCache->try_emplace_and_cvisit( |
| 1095 | + *resolvedPath, |
| 1096 | + nullptr, |
| 1097 | + [&](auto & i) { |
| 1098 | + vExpr = allocValue(); |
| 1099 | + vExpr->mkThunk(&baseEnv, expr); |
| 1100 | + i.second = vExpr; |
| 1101 | + }, |
| 1102 | + [&](auto & i) { vExpr = i.second; }); |
| 1103 | + |
| 1104 | + forceValue(*vExpr, noPos); |
| 1105 | + |
| 1106 | + v = *vExpr; |
1074 | 1107 | } |
1075 | 1108 |
|
1076 | 1109 | void EvalState::resetFileCache() |
1077 | 1110 | { |
1078 | | - fileEvalCache.clear(); |
1079 | | - fileEvalCache.rehash(0); |
1080 | | - fileParseCache.clear(); |
1081 | | - fileParseCache.rehash(0); |
| 1111 | + importResolutionCache->clear(); |
| 1112 | + fileEvalCache->clear(); |
1082 | 1113 | inputCache->clear(); |
1083 | 1114 | } |
1084 | 1115 |
|
@@ -2397,24 +2428,26 @@ StorePath EvalState::copyPathToStore(NixStringContext & context, const SourcePat |
2397 | 2428 | if (nix::isDerivation(path.path.abs())) |
2398 | 2429 | error<EvalError>("file names are not allowed to end in '%1%'", drvExtension).debugThrow(); |
2399 | 2430 |
|
2400 | | - std::optional<StorePath> dstPath; |
2401 | | - if (!srcToStore.cvisit(path, [&dstPath](const auto & kv) { dstPath.emplace(kv.second); })) { |
2402 | | - dstPath.emplace(fetchToStore( |
| 2431 | + auto dstPathCached = getConcurrent(*srcToStore, path); |
| 2432 | + |
| 2433 | + auto dstPath = dstPathCached ? *dstPathCached : [&]() { |
| 2434 | + auto dstPath = fetchToStore( |
2403 | 2435 | fetchSettings, |
2404 | 2436 | *store, |
2405 | 2437 | path.resolveSymlinks(SymlinkResolution::Ancestors), |
2406 | 2438 | settings.readOnlyMode ? FetchMode::DryRun : FetchMode::Copy, |
2407 | 2439 | path.baseName(), |
2408 | 2440 | ContentAddressMethod::Raw::NixArchive, |
2409 | 2441 | nullptr, |
2410 | | - repair)); |
2411 | | - allowPath(*dstPath); |
2412 | | - srcToStore.try_emplace(path, *dstPath); |
2413 | | - printMsg(lvlChatty, "copied source '%1%' -> '%2%'", path, store->printStorePath(*dstPath)); |
2414 | | - } |
| 2442 | + repair); |
| 2443 | + allowPath(dstPath); |
| 2444 | + srcToStore->try_emplace(path, dstPath); |
| 2445 | + printMsg(lvlChatty, "copied source '%1%' -> '%2%'", path, store->printStorePath(dstPath)); |
| 2446 | + return dstPath; |
| 2447 | + }(); |
2415 | 2448 |
|
2416 | | - context.insert(NixStringContextElem::Opaque{.path = *dstPath}); |
2417 | | - return *dstPath; |
| 2449 | + context.insert(NixStringContextElem::Opaque{.path = dstPath}); |
| 2450 | + return dstPath; |
2418 | 2451 | } |
2419 | 2452 |
|
2420 | 2453 | SourcePath EvalState::coerceToPath(const PosIdx pos, Value & v, NixStringContext & context, std::string_view errorCtx) |
|
0 commit comments