From 4b13858fc1fa81edde8c4614479c0281006696d7 Mon Sep 17 00:00:00 2001 From: Guillaume Maudoux Date: Sat, 20 Apr 2024 00:12:33 +0200 Subject: [PATCH] Improve eval cache support for parallelism --- src/libexpr/eval-cache.cc | 383 ++++++++++++++++---------------------- src/libstore/sqlite.cc | 4 +- src/libutil/util.cc | 2 +- src/libutil/util.hh | 2 +- 4 files changed, 168 insertions(+), 223 deletions(-) diff --git a/src/libexpr/eval-cache.cc b/src/libexpr/eval-cache.cc index d60967a14a70..af6897d8842b 100644 --- a/src/libexpr/eval-cache.cc +++ b/src/libexpr/eval-cache.cc @@ -20,106 +20,172 @@ create table if not exists Attributes ( struct AttrDb { - std::atomic_bool failed{false}; - const StoreDirConfig & cfg; - struct State + class State { + public: SQLite db; - SQLiteStmt insertAttribute; - SQLiteStmt insertAttributeWithContext; - SQLiteStmt queryAttribute; - SQLiteStmt queryAttributes; - std::unique_ptr txn; + + private: + SQLiteStmt _upsertAttribute; + SQLiteStmt _insertAttribute; + SQLiteStmt _queryAttribute; + SQLiteStmt _queryAttributes; + SymbolTable & symbols; + + + public: + State(SymbolTable & symbols): symbols(symbols) { }; + State(State && state): symbols(state.symbols) { }; + + void initDb(nix::Path & dbPath) { + db = SQLite(dbPath); + db.isCache(); + db.exec(schema); + + _upsertAttribute.create(db, R"( + insert into Attributes(parent, name, type, value, context) values (?1, ?2, ?3, ?4, ?5) + on conflict do update set type = excluded.type, value = excluded.value, context = excluded.context + returning rowid )"); + + _insertAttribute.create(db, + "insert or ignore into Attributes(parent, name, type, value, context) values (?, ?, ?, ?, ?)"); + + _queryAttribute.create(db, + "select rowid, type, value, context from Attributes where parent = ? and name = ?"); + + _queryAttributes.create(db, + "select name from Attributes where parent = ?"); + } + + template + void ensureAttribute(AttrKey key, AttrType type, T value, bool not_null = true) { + AttrId parentId(key.first); + std::string_view attrName(symbols[key.second]); + + _insertAttribute.use()(parentId)(attrName)(type)(value, not_null)(0, false).exec(); + } + + template + AttrId upsertAttribute( + AttrKey key, + AttrType type, + T value, + bool not_null = true, + std::string_view context = "", + bool hasContext = false) + { + AttrId parentId(key.first); + std::string_view attrName(symbols[key.second]); + + auto query(_upsertAttribute.use()(parentId)(attrName)(type)(value, not_null)(context, hasContext)); + query.next(); + return query.getInt(0); + } + + std::vector queryAttributes(AttrId rowId) { + std::vector attrs; + auto queryAttributes(_queryAttributes.use()(rowId)); + while (queryAttributes.next()) + attrs.emplace_back(symbols.create(queryAttributes.getStr(0))); + return attrs; + } + + std::optional> queryAttribute(AttrKey key) + { + auto queryAttribute(_queryAttribute.use()(key.first)(symbols[key.second])); + if (!queryAttribute.next()) return {}; + + auto rowId = (AttrId) queryAttribute.getInt(0); + auto type = (AttrType) queryAttribute.getInt(1); + + switch (type) { + case AttrType::Placeholder: + return {{rowId, placeholder_t()}}; + case AttrType::FullAttrs: { + // FIXME: expensive, should separate this out. + return {{rowId, queryAttributes(rowId)}}; + } + case AttrType::String: { + NixStringContext context; + if (!queryAttribute.isNull(3)) + for (auto & s : tokenizeString>(queryAttribute.getStr(3), ";")) + context.insert(NixStringContextElem::parse(s)); + return {{rowId, string_t{queryAttribute.getStr(2), context}}}; + } + case AttrType::Bool: + return {{rowId, queryAttribute.getInt(2) != 0}}; + case AttrType::Int: + return {{rowId, int_t{queryAttribute.getInt(2)}}}; + case AttrType::ListOfStrings: + return {{rowId, tokenizeString>(queryAttribute.getStr(2), "\t")}}; + case AttrType::Missing: + return {{rowId, missing_t()}}; + case AttrType::Misc: + return {{rowId, misc_t()}}; + case AttrType::Failed: + return {{rowId, failed_t()}}; + default: + throw Error("unexpected type in evaluation cache"); + } + } }; - std::unique_ptr> _state; + typedef Sync::Lock StateLock; - SymbolTable & symbols; + std::unique_ptr> _state; AttrDb( const StoreDirConfig & cfg, const Hash & fingerprint, SymbolTable & symbols) : cfg(cfg) - , _state(std::make_unique>()) - , symbols(symbols) + , _state(std::make_unique>(std::move(State(symbols)))) { - auto state(_state->lock()); - - Path cacheDir = getCacheDir() + "/nix/eval-cache-v5"; + // v1: ??? + // v2: ??? + // v3: ??? + // v4: ??? + // v5: ??? + // v6: changed db.isCache() to use WAL journaling. + Path cacheDir = getCacheDir() + "/nix/eval-cache-v6"; createDirs(cacheDir); Path dbPath = cacheDir + "/" + fingerprint.to_string(HashFormat::Base16, false) + ".sqlite"; - state->db = SQLite(dbPath); - state->db.isCache(); - state->db.exec(schema); - - state->insertAttribute.create(state->db, - "insert or replace into Attributes(parent, name, type, value) values (?, ?, ?, ?)"); - - state->insertAttributeWithContext.create(state->db, - "insert or replace into Attributes(parent, name, type, value, context) values (?, ?, ?, ?, ?)"); - - state->queryAttribute.create(state->db, - "select rowid, type, value, context from Attributes where parent = ? and name = ?"); - - state->queryAttributes.create(state->db, - "select name from Attributes where parent = ?"); - - state->txn = std::make_unique(state->db); - } - - ~AttrDb() - { - try { - auto state(_state->lock()); - if (!failed) - state->txn->commit(); - state->txn.reset(); - } catch (...) { - ignoreException(); - } + _state->lock()->initDb(dbPath); } template AttrId doSQLite(F && fun) { - if (failed) return 0; + StateLock state(_state->lock()); try { - return fun(); + return retrySQLite([&]() { + SQLiteTxn transaction(state->db); + AttrId res = fun(state); + transaction.commit(); + return res; + }); } catch (SQLiteError &) { - ignoreException(); - failed = true; + ignoreException(lvlDebug); return 0; } } - AttrId setAttrs( - AttrKey key, - const std::vector & attrs) + AttrId setAttrs(AttrKey key, const std::vector & attrs) { - return doSQLite([&]() + return doSQLite([&](StateLock & state) { - auto state(_state->lock()); - - state->insertAttribute.use() - (key.first) - (symbols[key.second]) - (AttrType::FullAttrs) - (0, false).exec(); - - AttrId rowId = state->db.getLastInsertedRowId(); + AttrId rowId = state->upsertAttribute(key, AttrType::FullAttrs, 0, false); assert(rowId); - for (auto & attr : attrs) - state->insertAttribute.use() - (rowId) - (symbols[attr]) - (AttrType::Placeholder) - (0, false).exec(); + for (auto & attr : attrs) { + // Rationale: ensure that it exists, but do not overwrite + // computed values with placeholders + state->ensureAttribute({rowId, attr}, AttrType::Placeholder, 0, false); + } return rowId; }); @@ -130,196 +196,75 @@ struct AttrDb std::string_view s, const char * * context = nullptr) { - return doSQLite([&]() + return doSQLite([&](StateLock & state) { - auto state(_state->lock()); - if (context) { std::string ctx; for (const char * * p = context; *p; ++p) { if (p != context) ctx.push_back(' '); ctx.append(*p); } - state->insertAttributeWithContext.use() - (key.first) - (symbols[key.second]) - (AttrType::String) - (s) - (ctx).exec(); + return state->upsertAttribute(key, AttrType::String, s, true, ctx, true); } else { - state->insertAttribute.use() - (key.first) - (symbols[key.second]) - (AttrType::String) - (s).exec(); + return state->upsertAttribute(key, AttrType::String, s); } - - return state->db.getLastInsertedRowId(); }); } - AttrId setBool( - AttrKey key, - bool b) - { - return doSQLite([&]() - { - auto state(_state->lock()); - - state->insertAttribute.use() - (key.first) - (symbols[key.second]) - (AttrType::Bool) - (b ? 1 : 0).exec(); - - return state->db.getLastInsertedRowId(); + AttrId setBool(AttrKey key, bool b) { + return doSQLite([&](StateLock & state) { + return state->upsertAttribute(key, AttrType::Bool, b ? 1 : 0); }); } - AttrId setInt( - AttrKey key, - int n) - { - return doSQLite([&]() - { - auto state(_state->lock()); - - state->insertAttribute.use() - (key.first) - (symbols[key.second]) - (AttrType::Int) - (n).exec(); - - return state->db.getLastInsertedRowId(); + AttrId setInt(AttrKey key, int n) { + return doSQLite([&](StateLock & state) { + return state->upsertAttribute(key, AttrType::Int, n); }); } - AttrId setListOfStrings( - AttrKey key, - const std::vector & l) - { - return doSQLite([&]() + AttrId setListOfStrings(AttrKey key, const std::vector & l) { + return doSQLite([&](StateLock & state) { - auto state(_state->lock()); - - state->insertAttribute.use() - (key.first) - (symbols[key.second]) - (AttrType::ListOfStrings) - (concatStringsSep("\t", l)).exec(); - - return state->db.getLastInsertedRowId(); + return state->upsertAttribute( + key, + AttrType::ListOfStrings, + concatStringsSep("\t", l)); }); } - AttrId setPlaceholder(AttrKey key) - { - return doSQLite([&]() - { - auto state(_state->lock()); - - state->insertAttribute.use() - (key.first) - (symbols[key.second]) - (AttrType::Placeholder) - (0, false).exec(); - - return state->db.getLastInsertedRowId(); + AttrId setPlaceholder(AttrKey key) { + return doSQLite([&](StateLock & state) { + return state->upsertAttribute(key, AttrType::Placeholder, 0, false); }); } - AttrId setMissing(AttrKey key) - { - return doSQLite([&]() - { - auto state(_state->lock()); - - state->insertAttribute.use() - (key.first) - (symbols[key.second]) - (AttrType::Missing) - (0, false).exec(); - - return state->db.getLastInsertedRowId(); + AttrId setMissing(AttrKey key) { + return doSQLite([&](StateLock & state) { + return state->upsertAttribute(key, AttrType::Missing, 0, false); }); } - AttrId setMisc(AttrKey key) - { - return doSQLite([&]() - { - auto state(_state->lock()); - - state->insertAttribute.use() - (key.first) - (symbols[key.second]) - (AttrType::Misc) - (0, false).exec(); - - return state->db.getLastInsertedRowId(); + AttrId setMisc(AttrKey key) { + return doSQLite([&](StateLock & state) { + return state->upsertAttribute(key, AttrType::Misc, 0, false); }); } - AttrId setFailed(AttrKey key) - { - return doSQLite([&]() - { - auto state(_state->lock()); - - state->insertAttribute.use() - (key.first) - (symbols[key.second]) - (AttrType::Failed) - (0, false).exec(); - - return state->db.getLastInsertedRowId(); + AttrId setFailed(AttrKey key) { + return doSQLite([&](StateLock & state) { + return state->upsertAttribute(key, AttrType::Failed, 0, false); }); } std::optional> getAttr(AttrKey key) { + // Not using doSqlite because 1. it only returns AttrId; and 2. this is + // only a single query, automatically wrapped in a transaction anyway. auto state(_state->lock()); - - auto queryAttribute(state->queryAttribute.use()(key.first)(symbols[key.second])); - if (!queryAttribute.next()) return {}; - - auto rowId = (AttrId) queryAttribute.getInt(0); - auto type = (AttrType) queryAttribute.getInt(1); - - switch (type) { - case AttrType::Placeholder: - return {{rowId, placeholder_t()}}; - case AttrType::FullAttrs: { - // FIXME: expensive, should separate this out. - std::vector attrs; - auto queryAttributes(state->queryAttributes.use()(rowId)); - while (queryAttributes.next()) - attrs.emplace_back(symbols.create(queryAttributes.getStr(0))); - return {{rowId, attrs}}; - } - case AttrType::String: { - NixStringContext context; - if (!queryAttribute.isNull(3)) - for (auto & s : tokenizeString>(queryAttribute.getStr(3), ";")) - context.insert(NixStringContextElem::parse(s)); - return {{rowId, string_t{queryAttribute.getStr(2), context}}}; - } - case AttrType::Bool: - return {{rowId, queryAttribute.getInt(2) != 0}}; - case AttrType::Int: - return {{rowId, int_t{queryAttribute.getInt(2)}}}; - case AttrType::ListOfStrings: - return {{rowId, tokenizeString>(queryAttribute.getStr(2), "\t")}}; - case AttrType::Missing: - return {{rowId, missing_t()}}; - case AttrType::Misc: - return {{rowId, misc_t()}}; - case AttrType::Failed: - return {{rowId, failed_t()}}; - default: - throw Error("unexpected type in evaluation cache"); - } + return state->queryAttribute(key); } + }; static std::shared_ptr makeAttrDb( diff --git a/src/libstore/sqlite.cc b/src/libstore/sqlite.cc index 3175c197870a..bc02c4256394 100644 --- a/src/libstore/sqlite.cc +++ b/src/libstore/sqlite.cc @@ -92,8 +92,8 @@ SQLite::~SQLite() void SQLite::isCache() { - exec("pragma synchronous = off"); - exec("pragma main.journal_mode = truncate"); + exec("pragma synchronous = normal"); + exec("pragma main.journal_mode = wal"); } void SQLite::exec(const std::string & stmt) diff --git a/src/libutil/util.cc b/src/libutil/util.cc index 103ce4232b35..d6e543367efc 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -153,7 +153,7 @@ void ignoreException(Verbosity lvl) try { throw; } catch (std::exception & e) { - printMsg(lvl, "error (ignored): %1%", e.what()); + printMsg(lvl, "exception thrown but ignored: %1%", e.what()); } } catch (...) { } } diff --git a/src/libutil/util.hh b/src/libutil/util.hh index 11a0431da891..39f1b245073c 100644 --- a/src/libutil/util.hh +++ b/src/libutil/util.hh @@ -191,7 +191,7 @@ std::string shellEscape(const std::string_view s); /* Exception handling in destructors: print an error message, then ignore the exception. */ -void ignoreException(Verbosity lvl = lvlError); +void ignoreException(Verbosity lvl = lvlWarning);