From 349d2c58e579ddd2e423a8a111d4e9cb102e7a1b Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 15 Jul 2025 18:21:29 +0200 Subject: [PATCH 1/3] Use WAL mode for SQLite cache databases With "truncate" mode, if we try to write to the database while another process has an active write transaction, we'll block until the other transaction finishes. This is a problem for the evaluation cache in particular, since it uses long-running transactions. WAL mode does not have this issue: it just returns "busy" right away, so Nix will print error (ignored): SQLite database '/home/eelco/.cache/nix/eval-cache-v5/...' is busy and stop trying to write to the evaluation cache. (This was the intended/original behaviour, see AttrDb::doSQLite().) --- src/libstore/sqlite.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libstore/sqlite.cc b/src/libstore/sqlite.cc index dd9f10422..41c9f9e7d 100644 --- a/src/libstore/sqlite.cc +++ b/src/libstore/sqlite.cc @@ -99,7 +99,7 @@ SQLite::~SQLite() void SQLite::isCache() { exec("pragma synchronous = off"); - exec("pragma main.journal_mode = truncate"); + exec("pragma main.journal_mode = wal"); } void SQLite::exec(const std::string & stmt) From 4ab8ff5b4ce538bde5c51d2225c3747316fcf304 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 22 Jul 2025 11:47:52 +0200 Subject: [PATCH 2/3] SQLite: Use std::filesystem::path --- src/libexpr/eval-cache.cc | 4 ++-- src/libstore/include/nix/store/sqlite.hh | 3 ++- src/libstore/local-store.cc | 3 +-- src/libstore/sqlite.cc | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/libexpr/eval-cache.cc b/src/libexpr/eval-cache.cc index dfb1b1a7e..4115196c5 100644 --- a/src/libexpr/eval-cache.cc +++ b/src/libexpr/eval-cache.cc @@ -69,10 +69,10 @@ struct AttrDb { auto state(_state->lock()); - Path cacheDir = getCacheDir() + "/eval-cache-v5"; + auto cacheDir = std::filesystem::path(getCacheDir()) / "eval-cache-v5"; createDirs(cacheDir); - Path dbPath = cacheDir + "/" + fingerprint.to_string(HashFormat::Base16, false) + ".sqlite"; + auto dbPath = cacheDir / (fingerprint.to_string(HashFormat::Base16, false) + ".sqlite"); state->db = SQLite(dbPath); state->db.isCache(); diff --git a/src/libstore/include/nix/store/sqlite.hh b/src/libstore/include/nix/store/sqlite.hh index e6d8a818a..3495c0bd1 100644 --- a/src/libstore/include/nix/store/sqlite.hh +++ b/src/libstore/include/nix/store/sqlite.hh @@ -1,6 +1,7 @@ #pragma once ///@file +#include #include #include @@ -41,7 +42,7 @@ struct SQLite SQLite() {} - SQLite(const Path & path, SQLiteOpenMode mode = SQLiteOpenMode::Normal); + SQLite(const std::filesystem::path & path, SQLiteOpenMode mode = SQLiteOpenMode::Normal); SQLite(const SQLite & from) = delete; SQLite & operator=(const SQLite & from) = delete; diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc index 81768e4eb..d8e103093 100644 --- a/src/libstore/local-store.cc +++ b/src/libstore/local-store.cc @@ -483,12 +483,11 @@ void LocalStore::openDB(State & state, bool create) throw SysError("Nix database directory '%1%' is not writable", dbDir); /* Open the Nix database. */ - std::string dbPath = dbDir + "/db.sqlite"; auto & db(state.db); auto openMode = config->readOnly ? SQLiteOpenMode::Immutable : create ? SQLiteOpenMode::Normal : SQLiteOpenMode::NoCreate; - state.db = SQLite(dbPath, openMode); + state.db = SQLite(std::filesystem::path(dbDir) / "db.sqlite", openMode); #ifdef __CYGWIN__ /* The cygwin version of sqlite3 has a patch which calls diff --git a/src/libstore/sqlite.cc b/src/libstore/sqlite.cc index 41c9f9e7d..56a69470a 100644 --- a/src/libstore/sqlite.cc +++ b/src/libstore/sqlite.cc @@ -58,7 +58,7 @@ static void traceSQL(void * x, const char * sql) notice("SQL<[%1%]>", sql); }; -SQLite::SQLite(const Path & path, SQLiteOpenMode mode) +SQLite::SQLite(const std::filesystem::path & path, SQLiteOpenMode mode) { // useSQLiteWAL also indicates what virtual file system we need. Using // `unix-dotfile` is needed on NFS file systems and on Windows' Subsystem @@ -68,7 +68,7 @@ SQLite::SQLite(const Path & path, SQLiteOpenMode mode) int flags = immutable ? SQLITE_OPEN_READONLY : SQLITE_OPEN_READWRITE; if (mode == SQLiteOpenMode::Normal) flags |= SQLITE_OPEN_CREATE; - auto uri = "file:" + percentEncode(path) + "?immutable=" + (immutable ? "1" : "0"); + auto uri = "file:" + percentEncode(path.string()) + "?immutable=" + (immutable ? "1" : "0"); int ret = sqlite3_open_v2(uri.c_str(), &db, SQLITE_OPEN_URI | flags, vfs); if (ret != SQLITE_OK) { const char * err = sqlite3_errstr(ret); From 0df147b145f787377c4a883a9dbb1ad28c7405e3 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 1 Aug 2025 16:40:37 +0200 Subject: [PATCH 3/3] Bump the version of the SQLite caches This avoids problems with older versions of Nix that don't put the caches in WAL mode. That's generally not a problem, until you do something like nix build --print-out-paths ... | cachix which deadlocks because cachix tries to switch the caches to truncate mode, which requires exclusive access. But the first process cannot make progress because the cachix process isn't reading from the pipe. --- src/libexpr/eval-cache.cc | 2 +- src/libfetchers/cache.cc | 2 +- src/libstore/nar-info-disk-cache.cc | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libexpr/eval-cache.cc b/src/libexpr/eval-cache.cc index 4115196c5..3ec344c41 100644 --- a/src/libexpr/eval-cache.cc +++ b/src/libexpr/eval-cache.cc @@ -69,7 +69,7 @@ struct AttrDb { auto state(_state->lock()); - auto cacheDir = std::filesystem::path(getCacheDir()) / "eval-cache-v5"; + auto cacheDir = std::filesystem::path(getCacheDir()) / "eval-cache-v6"; createDirs(cacheDir); auto dbPath = cacheDir / (fingerprint.to_string(HashFormat::Base16, false) + ".sqlite"); diff --git a/src/libfetchers/cache.cc b/src/libfetchers/cache.cc index 85fd94590..85a33e472 100644 --- a/src/libfetchers/cache.cc +++ b/src/libfetchers/cache.cc @@ -37,7 +37,7 @@ struct CacheImpl : Cache { auto state(_state.lock()); - auto dbPath = getCacheDir() + "/fetcher-cache-v3.sqlite"; + auto dbPath = getCacheDir() + "/fetcher-cache-v4.sqlite"; createDirs(dirOf(dbPath)); state->db = SQLite(dbPath); diff --git a/src/libstore/nar-info-disk-cache.cc b/src/libstore/nar-info-disk-cache.cc index 0350c874a..69d8d2e14 100644 --- a/src/libstore/nar-info-disk-cache.cc +++ b/src/libstore/nar-info-disk-cache.cc @@ -86,7 +86,7 @@ public: Sync _state; - NarInfoDiskCacheImpl(Path dbPath = getCacheDir() + "/binary-cache-v6.sqlite") + NarInfoDiskCacheImpl(Path dbPath = getCacheDir() + "/binary-cache-v7.sqlite") { auto state(_state.lock());