|
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)) |
@@ -1026,63 +1030,85 @@ Value * ExprPath::maybeThunk(EvalState & state, Env & env) |
1026 | 1030 | return &v; |
1027 | 1031 | } |
1028 | 1032 |
|
1029 | | -void EvalState::evalFile(const SourcePath & path, Value & v, bool mustBeTrivial) |
| 1033 | +/** |
| 1034 | + * A helper `Expr` class to lets us parse and evaluate Nix expressions |
| 1035 | + * from a thunk, ensuring that every file is parsed/evaluated only |
| 1036 | + * once (via the thunk stored in `EvalState::fileEvalCache`). |
| 1037 | + */ |
| 1038 | +struct ExprParseFile : Expr |
1030 | 1039 | { |
1031 | | - FileEvalCache::iterator i; |
1032 | | - if ((i = fileEvalCache.find(path)) != fileEvalCache.end()) { |
1033 | | - v = i->second; |
1034 | | - return; |
1035 | | - } |
| 1040 | + SourcePath & path; |
| 1041 | + bool mustBeTrivial; |
1036 | 1042 |
|
1037 | | - auto resolvedPath = resolveExprPath(path); |
1038 | | - if ((i = fileEvalCache.find(resolvedPath)) != fileEvalCache.end()) { |
1039 | | - v = i->second; |
1040 | | - return; |
| 1043 | + ExprParseFile(SourcePath & path, bool mustBeTrivial) |
| 1044 | + : path(path) |
| 1045 | + , mustBeTrivial(mustBeTrivial) |
| 1046 | + { |
1041 | 1047 | } |
1042 | 1048 |
|
1043 | | - printTalkative("evaluating file '%1%'", resolvedPath); |
1044 | | - Expr * e = nullptr; |
| 1049 | + void eval(EvalState & state, Env & env, Value & v) override |
| 1050 | + { |
| 1051 | + printTalkative("evaluating file '%s'", path); |
1045 | 1052 |
|
1046 | | - auto j = fileParseCache.find(resolvedPath); |
1047 | | - if (j != fileParseCache.end()) |
1048 | | - e = j->second; |
| 1053 | + auto e = state.parseExprFromFile(path); |
1049 | 1054 |
|
1050 | | - if (!e) |
1051 | | - e = parseExprFromFile(resolvedPath); |
| 1055 | + try { |
| 1056 | + auto dts = |
| 1057 | + state.debugRepl |
| 1058 | + ? makeDebugTraceStacker( |
| 1059 | + state, *e, state.baseEnv, e->getPos(), "while evaluating the file '%s':", path.to_string()) |
| 1060 | + : nullptr; |
| 1061 | + |
| 1062 | + // Enforce that 'flake.nix' is a direct attrset, not a |
| 1063 | + // computation. |
| 1064 | + if (mustBeTrivial && !(dynamic_cast<ExprAttrs *>(e))) |
| 1065 | + state.error<EvalError>("file '%s' must be an attribute set", path).debugThrow(); |
| 1066 | + |
| 1067 | + state.eval(e, v); |
| 1068 | + } catch (Error & e) { |
| 1069 | + state.addErrorTrace(e, "while evaluating the file '%s':", path.to_string()); |
| 1070 | + throw; |
| 1071 | + } |
| 1072 | + } |
| 1073 | +}; |
1052 | 1074 |
|
1053 | | - fileParseCache.emplace(resolvedPath, e); |
| 1075 | +void EvalState::evalFile(const SourcePath & path, Value & v, bool mustBeTrivial) |
| 1076 | +{ |
| 1077 | + auto resolvedPath = getConcurrent(*importResolutionCache, path); |
1054 | 1078 |
|
1055 | | - try { |
1056 | | - auto dts = debugRepl ? makeDebugTraceStacker( |
1057 | | - *this, |
1058 | | - *e, |
1059 | | - this->baseEnv, |
1060 | | - e->getPos(), |
1061 | | - "while evaluating the file '%1%':", |
1062 | | - resolvedPath.to_string()) |
1063 | | - : nullptr; |
1064 | | - |
1065 | | - // Enforce that 'flake.nix' is a direct attrset, not a |
1066 | | - // computation. |
1067 | | - if (mustBeTrivial && !(dynamic_cast<ExprAttrs *>(e))) |
1068 | | - error<EvalError>("file '%s' must be an attribute set", path).debugThrow(); |
1069 | | - eval(e, v); |
1070 | | - } catch (Error & e) { |
1071 | | - addErrorTrace(e, "while evaluating the file '%1%':", resolvedPath.to_string()); |
1072 | | - throw; |
| 1079 | + if (!resolvedPath) { |
| 1080 | + resolvedPath = resolveExprPath(path); |
| 1081 | + importResolutionCache->emplace(path, *resolvedPath); |
| 1082 | + } |
| 1083 | + |
| 1084 | + if (auto v2 = getConcurrent(*fileEvalCache, *resolvedPath)) { |
| 1085 | + forceValue(**v2, noPos); |
| 1086 | + v = **v2; |
| 1087 | + return; |
1073 | 1088 | } |
1074 | 1089 |
|
1075 | | - fileEvalCache.emplace(resolvedPath, v); |
1076 | | - if (path != resolvedPath) |
1077 | | - fileEvalCache.emplace(path, v); |
| 1090 | + Value * vExpr; |
| 1091 | + ExprParseFile expr{*resolvedPath, mustBeTrivial}; |
| 1092 | + |
| 1093 | + fileEvalCache->try_emplace_and_cvisit( |
| 1094 | + *resolvedPath, |
| 1095 | + nullptr, |
| 1096 | + [&](auto & i) { |
| 1097 | + vExpr = allocValue(); |
| 1098 | + vExpr->mkThunk(&baseEnv, &expr); |
| 1099 | + i.second = vExpr; |
| 1100 | + }, |
| 1101 | + [&](auto & i) { vExpr = i.second; }); |
| 1102 | + |
| 1103 | + forceValue(*vExpr, noPos); |
| 1104 | + |
| 1105 | + v = *vExpr; |
1078 | 1106 | } |
1079 | 1107 |
|
1080 | 1108 | void EvalState::resetFileCache() |
1081 | 1109 | { |
1082 | | - fileEvalCache.clear(); |
1083 | | - fileEvalCache.rehash(0); |
1084 | | - fileParseCache.clear(); |
1085 | | - fileParseCache.rehash(0); |
| 1110 | + importResolutionCache->clear(); |
| 1111 | + fileEvalCache->clear(); |
1086 | 1112 | inputCache->clear(); |
1087 | 1113 | } |
1088 | 1114 |
|
@@ -2367,24 +2393,26 @@ StorePath EvalState::copyPathToStore(NixStringContext & context, const SourcePat |
2367 | 2393 | if (nix::isDerivation(path.path.abs())) |
2368 | 2394 | error<EvalError>("file names are not allowed to end in '%1%'", drvExtension).debugThrow(); |
2369 | 2395 |
|
2370 | | - std::optional<StorePath> dstPath; |
2371 | | - if (!srcToStore.cvisit(path, [&dstPath](const auto & kv) { dstPath.emplace(kv.second); })) { |
2372 | | - dstPath.emplace(fetchToStore( |
| 2396 | + auto dstPathCached = getConcurrent(*srcToStore, path); |
| 2397 | + |
| 2398 | + auto dstPath = dstPathCached ? *dstPathCached : [&]() { |
| 2399 | + auto dstPath = fetchToStore( |
2373 | 2400 | fetchSettings, |
2374 | 2401 | *store, |
2375 | 2402 | path.resolveSymlinks(SymlinkResolution::Ancestors), |
2376 | 2403 | settings.readOnlyMode ? FetchMode::DryRun : FetchMode::Copy, |
2377 | 2404 | path.baseName(), |
2378 | 2405 | ContentAddressMethod::Raw::NixArchive, |
2379 | 2406 | nullptr, |
2380 | | - repair)); |
2381 | | - allowPath(*dstPath); |
2382 | | - srcToStore.try_emplace(path, *dstPath); |
2383 | | - printMsg(lvlChatty, "copied source '%1%' -> '%2%'", path, store->printStorePath(*dstPath)); |
2384 | | - } |
| 2407 | + repair); |
| 2408 | + allowPath(dstPath); |
| 2409 | + srcToStore->try_emplace(path, dstPath); |
| 2410 | + printMsg(lvlChatty, "copied source '%1%' -> '%2%'", path, store->printStorePath(dstPath)); |
| 2411 | + return dstPath; |
| 2412 | + }(); |
2385 | 2413 |
|
2386 | | - context.insert(NixStringContextElem::Opaque{.path = *dstPath}); |
2387 | | - return *dstPath; |
| 2414 | + context.insert(NixStringContextElem::Opaque{.path = dstPath}); |
| 2415 | + return dstPath; |
2388 | 2416 | } |
2389 | 2417 |
|
2390 | 2418 | SourcePath EvalState::coerceToPath(const PosIdx pos, Value & v, NixStringContext & context, std::string_view errorCtx) |
|
0 commit comments