diff --git a/libraries/net/test/test_util.hpp b/libraries/net/test/test_util.hpp index 1ceb817d3..6495a6942 100644 --- a/libraries/net/test/test_util.hpp +++ b/libraries/net/test/test_util.hpp @@ -100,16 +100,14 @@ struct WasmMemoryCache struct TempDatabase { TempDatabase() - : dir("psibase-test"), - sharedState{std::make_shared( - psibase::SharedDatabase{dir.path.native(), 1ull << 27, 1ull << 27, 1ull << 27, - 1ull << 27}, + : sharedState{std::make_shared( + psibase::SharedDatabase{std::filesystem::temp_directory_path(), + {1ull << 27, 1ull << 27, 1ull << 27, 1ull << 27}, + triedent::open_mode::temporary}, psibase::WasmCache{16})} { - dir.reset(); } - auto getSystemContext() { return sharedState->getSystemContext(); } - TempDir dir; + auto getSystemContext() { return sharedState->getSystemContext(); } std::shared_ptr sharedState; }; diff --git a/libraries/psibase/native/include/psibase/db.hpp b/libraries/psibase/native/include/psibase/db.hpp index 35bbe858e..18e2b1a70 100644 --- a/libraries/psibase/native/include/psibase/db.hpp +++ b/libraries/psibase/native/include/psibase/db.hpp @@ -8,13 +8,15 @@ #include #include #include +#include -#include +#include namespace triedent { class write_session; class root; + struct database_config; } // namespace triedent namespace psibase @@ -55,17 +57,20 @@ namespace psibase std::shared_ptr impl; SharedDatabase() = default; - SharedDatabase(const boost::filesystem::path& dir, - uint64_t hot_addr_bits = 1ull << 32, - uint64_t warm_addr_bits = 1ull << 32, - uint64_t cool_addr_bits = 1ull << 32, - uint64_t cold_addr_bits = 1ull << 32); + SharedDatabase(const std::filesystem::path& dir, + const triedent::database_config& config, + triedent::open_mode mode = triedent::open_mode::create); SharedDatabase(const SharedDatabase&) = default; SharedDatabase(SharedDatabase&&) = default; SharedDatabase& operator=(const SharedDatabase&) = default; SharedDatabase& operator=(SharedDatabase&&) = default; + // Returns a fork of the database. + // Warning: this should only be used for temporary databases as + // the storage is shared. + SharedDatabase clone() const; + ConstRevisionPtr getHead(); ConstRevisionPtr emptyRevision(); WriterPtr createWriter(); @@ -190,8 +195,9 @@ namespace psibase std::optional kvMaxRaw(DbId db, psio::input_stream key); template - auto kvPut(DbId db, const K& key, const V& value) - -> std::enable_if_t(), void> + auto kvPut(DbId db, + const K& key, + const V& value) -> std::enable_if_t(), void> { kvPutRaw(db, psio::convert_to_key(key), psio::convert_to_frac(value)); } diff --git a/libraries/psibase/native/src/useTriedent.cpp b/libraries/psibase/native/src/useTriedent.cpp index 231778e6b..4f0182598 100644 --- a/libraries/psibase/native/src/useTriedent.cpp +++ b/libraries/psibase/native/src/useTriedent.cpp @@ -270,7 +270,8 @@ namespace psibase { std::shared_ptr trie; - std::mutex topMutex; + std::mutex topMutex; + std::shared_ptr topRoot; std::mutex headMutex; std::shared_ptr head; @@ -278,39 +279,33 @@ namespace psibase std::mutex subjectiveMutex; std::shared_ptr subjective; - SharedDatabaseImpl(const std::filesystem::path& dir, - uint64_t hot_bytes, - uint64_t warm_bytes, - uint64_t cool_bytes, - uint64_t cold_bytes) + SharedDatabaseImpl(const std::filesystem::path& dir, + const triedent::database_config& config, + triedent::open_mode mode) { // The largest object is 16 MiB // Each file must be at least double this constexpr std::uint64_t min_size = 32 * 1024 * 1024; - if (hot_bytes < min_size || warm_bytes < min_size || cool_bytes < min_size || - cold_bytes < min_size) + if (config.hot_bytes < min_size || config.warm_bytes < min_size || + config.cool_bytes < min_size || config.cold_bytes < min_size) { throw std::runtime_error("Requested database size is too small"); } - if (!std::filesystem::exists(dir / "db")) - { - // std::cout << "Creating " << dir << "\n"; - triedent::database::create( // - dir, // - triedent::database::config{ - .hot_bytes = hot_bytes, - .warm_bytes = warm_bytes, - .cool_bytes = cool_bytes, - .cold_bytes = cold_bytes, - }); - } - else - { - // std::cout << "Open existing " << dir << "\n"; - } - trie = std::make_shared(dir.c_str(), triedent::database::read_write); - auto s = trie->start_write_session(); - head = loadRevision(*s, s->get_top_root(), revisionHeadKey); + trie = std::make_shared(dir.c_str(), config, mode); + auto s = trie->start_write_session(); + topRoot = s->get_top_root(); + head = loadRevision(*s, topRoot, revisionHeadKey); + } + + SharedDatabaseImpl(const SharedDatabaseImpl& other) + : trie(other.trie), topRoot(other.topRoot), head(other.head), subjective(other.subjective) + { + } + + auto getTopRoot() + { + std::lock_guard lock{topMutex}; + return topRoot; } auto getHead() @@ -323,7 +318,6 @@ namespace psibase { { std::lock_guard lock{topMutex}; - auto topRoot = session.get_top_root(); session.upsert(topRoot, revisionHeadKey, r->roots); session.set_top_root(topRoot); } @@ -337,25 +331,25 @@ namespace psibase const Revision& r) { std::lock_guard lock{topMutex}; - auto topRoot = session.get_top_root(); session.upsert(topRoot, revisionById(blockId), r.roots); session.set_top_root(topRoot); } }; // SharedDatabaseImpl - SharedDatabase::SharedDatabase(const boost::filesystem::path& dir, - uint64_t hot_bytes, - uint64_t warm_bytes, - uint64_t cool_bytes, - uint64_t cold_bytes) - : impl{std::make_shared(dir.c_str(), - hot_bytes, - warm_bytes, - cool_bytes, - cold_bytes)} + SharedDatabase::SharedDatabase(const std::filesystem::path& dir, + const triedent::database_config& config, + triedent::open_mode mode) + : impl{std::make_shared(dir, config, mode)} { } + SharedDatabase SharedDatabase::clone() const + { + SharedDatabase result{*this}; + result.impl = std::make_shared(*impl); + return result; + } + ConstRevisionPtr SharedDatabase::getHead() { return impl->getHead(); @@ -378,7 +372,7 @@ namespace psibase ConstRevisionPtr SharedDatabase::getRevision(Writer& writer, const Checksum256& blockId) { - return loadRevision(writer, writer.get_top_root(), revisionById(blockId), true); + return loadRevision(writer, impl->getTopRoot(), revisionById(blockId), true); } // TODO: move triedent::root destruction to a gc thread @@ -386,17 +380,16 @@ namespace psibase { // TODO: Reduce critical section std::lock_guard lock{impl->topMutex}; - auto topRoot = writer.get_top_root(); std::vector key{revisionByIdPrefix}; // Remove everything with a blockNum <= irreversible's, except irreversible. - while (writer.get_greater_equal(topRoot, key, &key, nullptr, nullptr)) + while (writer.get_greater_equal(impl->topRoot, key, &key, nullptr, nullptr)) { if (key.size() != 1 + irreversible.size() || key[0] != revisionByIdPrefix || memcmp(key.data() + 1, irreversible.data(), sizeof(BlockNum)) > 0) break; if (memcmp(key.data() + 1, irreversible.data(), irreversible.size())) - writer.remove(topRoot, key); + writer.remove(impl->topRoot, key); key.push_back(0); } @@ -405,7 +398,7 @@ namespace psibase std::vector> roots; std::vector statusBytes; auto sk = psio::convert_to_key(statusKey()); - while (writer.get_greater_equal(topRoot, key, &key, nullptr, &roots)) + while (writer.get_greater_equal(impl->topRoot, key, &key, nullptr, &roots)) { if (key.size() != 1 + irreversible.size() || key[0] != revisionByIdPrefix) break; @@ -415,12 +408,13 @@ namespace psibase auto status = psio::from_frac(psio::prevalidated{statusBytes}); if (!status.head) throw std::runtime_error("Status row is missing head information in fork"); - if (!writer.get(topRoot, revisionById(status.head->header.previous), nullptr, nullptr)) - writer.remove(topRoot, key); + if (!writer.get(impl->topRoot, revisionById(status.head->header.previous), nullptr, + nullptr)) + writer.remove(impl->topRoot, key); key.push_back(0); } - writer.set_top_root(topRoot); + writer.set_top_root(impl->topRoot); } // removeRevisions void SharedDatabase::setBlockData(Writer& writer, @@ -434,16 +428,15 @@ namespace psibase fullKey.insert(fullKey.end(), blockId.begin(), blockId.end()); fullKey.insert(fullKey.end(), key.begin(), key.end()); std::lock_guard lock{impl->topMutex}; - auto topRoot = writer.get_top_root(); - writer.upsert(topRoot, fullKey, value); - writer.set_top_root(topRoot); + writer.upsert(impl->topRoot, fullKey, value); + writer.set_top_root(impl->topRoot); } std::optional> SharedDatabase::getBlockData(Writer& reader, const Checksum256& blockId, std::span key) { - auto topRoot = reader.get_top_root(); + auto topRoot = impl->getTopRoot(); std::vector fullKey; fullKey.reserve(1 + blockId.size() + key.size()); fullKey.push_back(blockDataPrefix); @@ -486,9 +479,8 @@ namespace psibase writer.splice(impl->subjective, updated, dbKey(r.lower), dbKey(r.upper)); } std::lock_guard lock{impl->topMutex}; - auto r = writer.get_top_root(); - writer.upsert(r, subjectiveKey, {&impl->subjective, 1}); - writer.set_top_root(std::move(r)); + writer.upsert(impl->topRoot, subjectiveKey, {&impl->subjective, 1}); + writer.set_top_root(impl->topRoot); } return true; } diff --git a/libraries/psibase/tester/include/psibase/DefaultTestChain.hpp b/libraries/psibase/tester/include/psibase/DefaultTestChain.hpp index e4f8bbea3..3a5ead527 100644 --- a/libraries/psibase/tester/include/psibase/DefaultTestChain.hpp +++ b/libraries/psibase/tester/include/psibase/DefaultTestChain.hpp @@ -14,9 +14,11 @@ namespace psibase public: // default excludes Docs and TokenUsers static std::vector defaultPackages(); - DefaultTestChain(const std::vector& packageNames = defaultPackages(), - bool installUI = false, - const DatabaseConfig& dbconfig = {}); + DefaultTestChain(); + DefaultTestChain(const std::vector& packageNames, + bool installUI = false, + const DatabaseConfig& dbconfig = {}, + bool pub = true); AccountNumber addService(const char* acc, const char* filename, bool show = false); AccountNumber addService(AccountNumber acc, const char* filename, bool show = false); diff --git a/libraries/psibase/tester/include/psibase/tester.hpp b/libraries/psibase/tester/include/psibase/tester.hpp index d7afa6c3c..45374bfce 100644 --- a/libraries/psibase/tester/include/psibase/tester.hpp +++ b/libraries/psibase/tester/include/psibase/tester.hpp @@ -9,6 +9,8 @@ #include #include +#include + namespace psibase { using KeyList = std::vector concept HttpRequestBody = requires(const T& t) { - { - t.contentType() - } -> std::convertible_to; - { - t.body() - } -> std::convertible_to>; + { t.contentType() } -> std::convertible_to; + { t.body() } -> std::convertible_to>; }; /** * Manages a chain. - * Only one TestChain can exist at a time. * The test chain uses simulated time. */ class TestChain @@ -155,28 +152,48 @@ namespace psibase std::optional status; bool producing = false; bool isAutoBlockStart = true; + bool isPublicChain; + + explicit TestChain(uint32_t chain_id, bool clone, bool pub = true); public: - explicit TestChain(const DatabaseConfig&); + // Clones the chain + TestChain(const TestChain&, bool pub); + /** + * Creates a new temporary chain. + * + * @param pub If this is the only public chain, it will be automatically + * selected. + */ + explicit TestChain(const DatabaseConfig&, bool pub = true); + /** + * Opens a chain. + * + * @param flags must include at least either O_RDONLY or O_RDWR, and + * can also contain O_CREAT, O_EXCL, and O_TRUNC. + */ + TestChain(std::string_view path, + int flags = O_CREAT | O_RDWR, + const DatabaseConfig& cfg = {}, + bool pub = true); TestChain(uint64_t hot_bytes = 1ull << 27, uint64_t warm_bytes = 1ull << 27, uint64_t cool_bytes = 1ull << 27, uint64_t cold_bytes = 1ull << 27); - TestChain(const TestChain&) = delete; virtual ~TestChain(); TestChain& operator=(const TestChain&) = delete; /** - * Shuts down the chain to allow copying its state file. The chain's temporary path will - * live until this object destructs. + * Boots the chain. */ - void shutdown(); + void boot(const std::vector& names, bool installUI); /** - * Get the temporary path which contains the chain's blocks and states directories + * Shuts down the chain to allow copying its state file. The chain's temporary path will + * live until this object destructs. */ - std::string getPath(); + void shutdown(); /** * By default, the TestChain will automatically advance blocks. @@ -213,7 +230,8 @@ namespace psibase /* * Creates a transaction. */ - Transaction makeTransaction(std::vector&& actions = {}) const; + Transaction makeTransaction(std::vector&& actions = {}, + uint32_t expire_sec = 2) const; /** * Pushes a transaction onto the chain. If no block is currently pending, starts one. @@ -383,6 +401,27 @@ namespace psibase }; auto from(AccountNumber id) { return UserContext{*this, id, {}}; } + + /// Get a key-value pair, if any + std::optional> kvGetRaw(psibase::DbId db, psio::input_stream key); + + /// Get a key-value pair, if any + template + std::optional kvGet(psibase::DbId db, const K& key) + { + auto v = kvGetRaw(db, psio::convert_to_key(key)); + if (!v) + return std::nullopt; + // TODO: validate (allow opt-in or opt-out) + return psio::from_frac(psio::prevalidated{*v}); + } + + /// Get a key-value pair, if any + template + std::optional kvGet(const K& key) + { + return kvGet(psibase::DbId::service, key); + } }; // TestChain } // namespace psibase diff --git a/libraries/psibase/tester/src/DefaultTestChain.cpp b/libraries/psibase/tester/src/DefaultTestChain.cpp index 8725a4686..e582875dc 100644 --- a/libraries/psibase/tester/src/DefaultTestChain.cpp +++ b/libraries/psibase/tester/src/DefaultTestChain.cpp @@ -30,7 +30,7 @@ namespace InitCwd initCwd; #endif - void pushGenesisTransaction(DefaultTestChain& chain, std::span service_packages) + void pushGenesisTransaction(TestChain& chain, std::span service_packages) { std::vector services; for (auto& s : service_packages) @@ -138,29 +138,25 @@ namespace group.push_back(std::move(act)); if (size >= 1024 * 1024) { - result.push_back(SignedTransaction{chain.makeTransaction(std::move(group))}); + result.push_back(SignedTransaction{chain.makeTransaction(std::move(group), 30)}); size = 0; } } if (!group.empty()) { - result.push_back(SignedTransaction{chain.makeTransaction(std::move(group))}); + result.push_back(SignedTransaction{chain.makeTransaction(std::move(group), 30)}); } return result; } -} // namespace -std::vector DefaultTestChain::defaultPackages() -{ - return {"Accounts", "AuthAny", "AuthDelegate", "AuthSig", "CommonApi", "CpuLimit", "Events", - "Explorer", "Fractal", "Invite", "Nft", "Packages", "Producers", "HttpServer", - "Sites", "SetCode", "Symbol", "Tokens", "Transact"}; -} + DefaultTestChain& defaultChainInstance() + { + static DefaultTestChain result{DefaultTestChain::defaultPackages(), false, {}, false}; + return result; + } +} // namespace -DefaultTestChain::DefaultTestChain(const std::vector& names, - bool installUI, - const DatabaseConfig& dbconfig) - : TestChain(dbconfig) +void TestChain::boot(const std::vector& names, bool installUI) { auto packageRoot = std::getenv("PSIBASE_DATADIR"); check(!!packageRoot, "Cannot find package directory: PSIBASE_DATADIR not defined"); @@ -179,13 +175,35 @@ DefaultTestChain::DefaultTestChain(const std::vector& names, {transactor{Transact::service, Transact::service}.startBoot(transactionIds)}), {}); check(psibase::show(false, trace) == "", "Failed to boot"); - startBlock(); - setAutoBlockStart(true); for (const auto& trx : transactions) { + startBlock(); auto trace = pushTransaction(trx); check(psibase::show(false, trace) == "", "Failed to boot"); } + setAutoBlockStart(true); + startBlock(); +} + +std::vector DefaultTestChain::defaultPackages() +{ + return {"Accounts", "AuthAny", "AuthDelegate", "AuthSig", "CommonApi", "CpuLimit", "Events", + "Explorer", "Fractal", "Invite", "Nft", "Packages", "Producers", "HttpServer", + "Sites", "SetCode", "Symbol", "Tokens", "Transact"}; +} + +DefaultTestChain::DefaultTestChain() : TestChain(defaultChainInstance(), true) +{ + startBlock(); +} + +DefaultTestChain::DefaultTestChain(const std::vector& names, + bool installUI, + const DatabaseConfig& dbconfig, + bool pub) + : TestChain(dbconfig, pub) +{ + boot(names, installUI); } AccountNumber DefaultTestChain::addAccount( diff --git a/libraries/psibase/tester/src/tester.cpp b/libraries/psibase/tester/src/tester.cpp index d63ae8b72..250732966 100644 --- a/libraries/psibase/tester/src/tester.cpp +++ b/libraries/psibase/tester/src/tester.cpp @@ -18,6 +18,40 @@ namespace psibase::tester::raw } } // namespace psibase::tester::raw +namespace +{ + std::uint32_t numPublicChains = 0; + struct ScopedSelectChain + { + ScopedSelectChain(std::uint32_t id) : saved(psibase::tester::raw::selectedChain) + { + psibase::tester::raw::selectedChain = id; + } + ~ScopedSelectChain() { psibase::tester::raw::selectedChain = saved; } + std::optional saved; + }; + __wasi_oflags_t get_wasi_oflags(int flags) + { + __wasi_oflags_t result = 0; + if (flags & O_CREAT) + result |= __WASI_OFLAGS_CREAT; + if (flags & O_EXCL) + result |= __WASI_OFLAGS_EXCL; + if (flags & O_TRUNC) + result |= __WASI_OFLAGS_TRUNC; + return result; + } + __wasi_rights_t get_wasi_rights(int flags) + { + __wasi_rights_t result = 0; + if ((flags & O_RDONLY) == O_RDONLY || (flags & O_RDWR) == O_RDWR) + result |= __WASI_RIGHTS_FD_READ; + if ((flags & O_WRONLY) == O_WRONLY || (flags & O_RDWR) == O_RDWR) + result |= __WASI_RIGHTS_FD_WRITE; + return result; + } +} // namespace + using psibase::tester::raw::selectedChain; using namespace SystemService::AuthSig; @@ -26,15 +60,23 @@ extern "C" #define TESTER_NATIVE(name) [[clang::import_module("psibase"), clang::import_name(#name)]] // clang-format off TESTER_NATIVE(createChain) uint32_t testerCreateChain(uint64_t hot_addr_bits, uint64_t warm_addr_bits, uint64_t cool_addr_bits, uint64_t cold_addr_bits); + TESTER_NATIVE(cloneChain) uint32_t testerCloneChain(uint32_t chain); TESTER_NATIVE(destroyChain) void testerDestroyChain(uint32_t chain); TESTER_NATIVE(finishBlock) void testerFinishBlock(uint32_t chain_index); - TESTER_NATIVE(getChainPath) uint32_t testerGetChainPath(uint32_t chain, char* dest, uint32_t dest_size); TESTER_NATIVE(pushTransaction) uint32_t testerPushTransaction(uint32_t chain_index, const char* args_packed, uint32_t args_packed_size); TESTER_NATIVE(httpRequest) uint32_t testerHttpRequest(uint32_t chain_index, const char* args_packed, uint32_t args_packed_size); TESTER_NATIVE(socketRecv) uint32_t testerSocketRecv(int32_t fd, std::size_t* size); TESTER_NATIVE(selectChainForDb) void testerSelectChainForDb(uint32_t chain_index); TESTER_NATIVE(shutdownChain) void testerShutdownChain(uint32_t chain); TESTER_NATIVE(startBlock) void testerStartBlock(uint32_t chain_index, uint32_t time_seconds); + TESTER_NATIVE(kvGet) uint32_t testerKvGet(uint32_t chain, psibase::DbId db, const char* key, uint32_t keyLen); + + TESTER_NATIVE(openChain) uint32_t testerOpenChain(const char* path, + uint32_t pathlen, + uint16_t oflags, + uint64_t fs_rights_base, + const psibase::DatabaseConfig* config); + // clang-format on #undef TESTER_NATIVE @@ -125,16 +167,27 @@ void psibase::expect(TransactionTrace t, const std::string& expected, bool alway } } -psibase::TestChain::TestChain(const DatabaseConfig& dbconfig) - : id{::testerCreateChain(dbconfig.hotBytes, - dbconfig.warmBytes, - dbconfig.coolBytes, - dbconfig.coldBytes)} +psibase::TestChain::TestChain(uint32_t chain_id, bool clone, bool pub) + : id{clone ? ::testerCloneChain(chain_id) : chain_id}, isPublicChain(pub) { - if (id == 0) + if (pub && numPublicChains++ == 0) psibase::tester::raw::selectedChain = id; } +psibase::TestChain::TestChain(const TestChain& other, bool pub) : TestChain{other.id, true, pub} +{ + status = other.status; +} + +psibase::TestChain::TestChain(const DatabaseConfig& dbconfig, bool pub) + : TestChain{::testerCreateChain(dbconfig.hotBytes, + dbconfig.warmBytes, + dbconfig.coolBytes, + dbconfig.coldBytes), + false, pub} +{ +} + psibase::TestChain::TestChain(uint64_t hot_bytes, uint64_t warm_bytes, uint64_t cool_bytes, @@ -143,8 +196,21 @@ psibase::TestChain::TestChain(uint64_t hot_bytes, { } +psibase::TestChain::TestChain(std::string_view path, int flags, const DatabaseConfig& cfg, bool pub) + : TestChain(::testerOpenChain(path.data(), + path.size(), + get_wasi_oflags(flags), + get_wasi_rights(flags), + &cfg), + false, + pub) +{ +} + psibase::TestChain::~TestChain() { + if (isPublicChain) + --numPublicChains; ::testerDestroyChain(id); if (selectedChain && *selectedChain == id) selectedChain.reset(); @@ -155,14 +221,6 @@ void psibase::TestChain::shutdown() ::testerShutdownChain(id); } -std::string psibase::TestChain::getPath() -{ - size_t len = testerGetChainPath(id, nullptr, 0); - std::string result(len, 0); - testerGetChainPath(id, result.data(), len); - return result; -} - void psibase::TestChain::setAutoBlockStart(bool enable) { isAutoBlockStart = enable; @@ -188,7 +246,7 @@ void psibase::TestChain::startBlock(TimePointSec tp) if (status && status->current.time.seconds + 1 < tp.seconds) ::testerStartBlock(id, tp.seconds - 1); ::testerStartBlock(id, tp.seconds); - status = psibase::kvGet(psibase::StatusRow::db, psibase::statusKey()); + status = kvGet(psibase::StatusRow::db, psibase::statusKey()); producing = true; } @@ -200,16 +258,18 @@ void psibase::TestChain::finishBlock() void psibase::TestChain::fillTapos(Transaction& t, uint32_t expire_sec) const { + ScopedSelectChain s{id}; t.tapos.expiration.seconds = (status ? status->current.time.seconds : 0) + expire_sec; auto [index, suffix] = SystemService::headTapos(); t.tapos.refBlockIndex = index; t.tapos.refBlockSuffix = suffix; } -psibase::Transaction psibase::TestChain::makeTransaction(std::vector&& actions) const +psibase::Transaction psibase::TestChain::makeTransaction(std::vector&& actions, + uint32_t expire_sec) const { Transaction t; - fillTapos(t); + fillTapos(t, expire_sec); t.actions = std::move(actions); return t; } @@ -266,3 +326,12 @@ psibase::HttpReply psibase::TestChain::http(const HttpRequest& request) return psio::from_frac(getResult(size)); } + +std::optional> psibase::TestChain::kvGetRaw(psibase::DbId db, + psio::input_stream key) +{ + auto size = ::testerKvGet(id, db, key.pos, key.remaining()); + if (size == -1) + return std::nullopt; + return psibase::getResult(size); +} diff --git a/libraries/triedent/include/triedent/cache_allocator.hpp b/libraries/triedent/include/triedent/cache_allocator.hpp index 88371f38b..c8d851ae0 100644 --- a/libraries/triedent/include/triedent/cache_allocator.hpp +++ b/libraries/triedent/include/triedent/cache_allocator.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -14,6 +15,10 @@ namespace triedent { + std::filesystem::path get_subpath(const std::filesystem::path& dir, + const char* name, + open_mode mode); + // Cache allocator manages all storage for the database. // // It maintains multiple buffers and moves accessed data to the hot @@ -29,22 +34,9 @@ namespace triedent public: using id = object_id; - // cold_bytes can grow - // hot/warm/cool are fixed - // hot/warm/cool/cold MUST be more than twice the - // maximum allocation size. - struct config - { - uint64_t hot_bytes = 1000 * 1000ull; - uint64_t warm_bytes = 1000 * 1000ull; - uint64_t cool_bytes = 1000 * 1000ull; - uint64_t cold_bytes = 1000 * 1000ull; - }; - - cache_allocator(const std::filesystem::path& path, - const config& cfg, - access_mode mode, - bool allow_gc = false); + using config = database_config; + + cache_allocator(const std::filesystem::path& path, const config& cfg, open_mode mode); ~cache_allocator(); auto start_session() { return gc_queue::session{_gc}; } diff --git a/libraries/triedent/include/triedent/database.hpp b/libraries/triedent/include/triedent/database.hpp index dee442be0..1627614ea 100644 --- a/libraries/triedent/include/triedent/database.hpp +++ b/libraries/triedent/include/triedent/database.hpp @@ -414,11 +414,8 @@ namespace triedent using string_view = std::string_view; using id = object_id; - database(const std::filesystem::path& dir, - const config& cfg, - access_mode mode, - bool allow_gc = false); - database(const std::filesystem::path& dir, access_mode mode, bool allow_gc = false); + database(const std::filesystem::path& dir, const config& cfg, open_mode mode); + database(const std::filesystem::path& dir, open_mode mode); ~database(); static void create(std::filesystem::path dir, config); @@ -1768,12 +1765,8 @@ namespace triedent std::unique_lock& l, std::string_view key, database::id child) { - { - t.branches() - } -> std::same_as; - { - t.children() - } -> std::ranges::input_range; + { t.branches() } -> std::same_as; + { t.children() } -> std::ranges::input_range; t.clone_child(session, l, child); t.clone_child_with_prefix(session, l, key, child); }; diff --git a/libraries/triedent/include/triedent/debug.hpp b/libraries/triedent/include/triedent/debug.hpp index f7cdcbe1d..e2ee0037f 100644 --- a/libraries/triedent/include/triedent/debug.hpp +++ b/libraries/triedent/include/triedent/debug.hpp @@ -41,11 +41,12 @@ namespace triedent std::cerr << "\n"; } - inline auto set_current_thread_name( const char* name ) { + inline auto set_current_thread_name(const char* name) + { #ifdef __APPLE__ - return pthread_setname_np(name); + return pthread_setname_np(name); #else - return pthread_setname_np( pthread_self(), name); + return pthread_setname_np(pthread_self(), name); #endif } diff --git a/libraries/triedent/include/triedent/file_fwd.hpp b/libraries/triedent/include/triedent/file_fwd.hpp index d9118f701..f583391f9 100644 --- a/libraries/triedent/include/triedent/file_fwd.hpp +++ b/libraries/triedent/include/triedent/file_fwd.hpp @@ -1,7 +1,7 @@ #pragma once -#include #include +#include namespace triedent { @@ -34,4 +34,41 @@ namespace triedent std::uint64_t available_bytes; std::uint64_t num_objects; }; + + // cold_bytes can grow + // hot/warm/cool are fixed + // hot/warm/cool/cold MUST be more than twice the + // maximum allocation size. + struct database_config + { + std::uint64_t hot_bytes = 1000 * 1000ull; + std::uint64_t warm_bytes = 1000 * 1000ull; + std::uint64_t cool_bytes = 1000 * 1000ull; + std::uint64_t cold_bytes = 1000 * 1000ull; + }; + + enum access_mode + { + read_only = 0, + read_write = 1, + }; + + enum class open_mode + { + // Open an existing database + read_only = 0, + read_write = 1, + // Create a new database if the database does not exist + create = 2, + // Create a new database, overwriting an existing database + trunc = 3, + // Create a new database. It is an error if the database already exists + create_new = 4, + // Create a unique temporary database which will be deleted when it is closed. + // The path should be an existing directory. + temporary = 5, + // Open an existing database for garbage collection + gc = 6, + }; + } // namespace triedent diff --git a/libraries/triedent/include/triedent/mapping.hpp b/libraries/triedent/include/triedent/mapping.hpp index dd4a01484..89bc21f95 100644 --- a/libraries/triedent/include/triedent/mapping.hpp +++ b/libraries/triedent/include/triedent/mapping.hpp @@ -1,5 +1,7 @@ #pragma once +#include + #include #include #include @@ -8,12 +10,6 @@ namespace triedent { - enum access_mode - { - read_only = 0, - read_write = 1 - }; - // Thread safety: // // The file must not be resized by another process @@ -34,7 +30,7 @@ namespace triedent class mapping { public: - mapping(const std::filesystem::path& file, access_mode mode, bool pin = false); + mapping(const std::filesystem::path& file, open_mode mode, bool pin = false); ~mapping(); // Sets the size of the file to new_size. // diff --git a/libraries/triedent/include/triedent/node.hpp b/libraries/triedent/include/triedent/node.hpp index 6e34af64a..101653c80 100644 --- a/libraries/triedent/include/triedent/node.hpp +++ b/libraries/triedent/include/triedent/node.hpp @@ -26,9 +26,7 @@ namespace triedent template concept key_builder = requires(T& t, cache_allocator& a, std::unique_lock& session) { reload_key(a, session, t); - { - t.size() - } -> std::convertible_to; + { t.size() } -> std::convertible_to; requires std::convertible_to; }; diff --git a/libraries/triedent/include/triedent/object_db.hpp b/libraries/triedent/include/triedent/object_db.hpp index 1465df197..62eb46979 100644 --- a/libraries/triedent/include/triedent/object_db.hpp +++ b/libraries/triedent/include/triedent/object_db.hpp @@ -67,10 +67,7 @@ namespace triedent public: using object_id = triedent::object_id; - object_db(gc_queue& gc, - std::filesystem::path idfile, - access_mode mode, - bool allow_gc = false); + object_db(gc_queue& gc, std::filesystem::path idfile, open_mode mode); // Bumps the reference count by 1 if possible bool bump_count(object_id id) @@ -259,10 +256,7 @@ namespace triedent } }; - inline object_db::object_db(gc_queue& gc, - std::filesystem::path idfile, - access_mode mode, - bool allow_gc) + inline object_db::object_db(gc_queue& gc, std::filesystem::path idfile, open_mode mode) : _gc(gc), _region(idfile, mode, true) { if (_region.size() == 0) @@ -289,7 +283,8 @@ namespace triedent if ((_header->flags & file_type_mask) != file_type_index) throw std::runtime_error("Not a triedent obj_ids file: " + idfile.native()); - if (!allow_gc && mode == access_mode::read_write && (_header->flags.load() & running_gc_flag)) + if (mode != open_mode::gc && mode != open_mode::read_only && + (_header->flags.load() & running_gc_flag)) throw std::runtime_error("garbage collection in progress"); if (_header->max_unallocated.id > (existing_size - sizeof(object_db_header)) / 8 - 1) diff --git a/libraries/triedent/include/triedent/region_allocator.hpp b/libraries/triedent/include/triedent/region_allocator.hpp index f17cc4d26..5dfa4e525 100644 --- a/libraries/triedent/include/triedent/region_allocator.hpp +++ b/libraries/triedent/include/triedent/region_allocator.hpp @@ -25,7 +25,7 @@ namespace triedent region_allocator(gc_queue& gc, object_db& obj_ids, const std::filesystem::path& path, - access_mode mode, + open_mode mode, std::uint64_t initial_size = 64 * 1024 * 1024); ~region_allocator(); void* try_allocate(std::unique_lock& session, diff --git a/libraries/triedent/include/triedent/ring_allocator.hpp b/libraries/triedent/include/triedent/ring_allocator.hpp index 619da8dc9..8f7614b4c 100644 --- a/libraries/triedent/include/triedent/ring_allocator.hpp +++ b/libraries/triedent/include/triedent/ring_allocator.hpp @@ -70,7 +70,7 @@ namespace triedent ring_allocator(const std::filesystem::path& path, std::uint64_t size, std::uint8_t level, - access_mode mode, + open_mode mode, bool pin); struct header { diff --git a/libraries/triedent/src/cache_allocator.cpp b/libraries/triedent/src/cache_allocator.cpp index 1c7348725..d3ed72fe7 100644 --- a/libraries/triedent/src/cache_allocator.cpp +++ b/libraries/triedent/src/cache_allocator.cpp @@ -4,18 +4,29 @@ namespace triedent { + std::filesystem::path get_subpath(const std::filesystem::path& dir, + const char* name, + open_mode mode) + { + if (mode == open_mode::temporary) + return dir; + else + return dir / name; + } + cache_allocator::cache_allocator(const std::filesystem::path& dir, const config& cfg, - access_mode mode, - bool allow_gc) + open_mode mode) : _gc{256}, - _obj_ids(_gc, dir / "obj_ids", mode, allow_gc), - _levels{ring_allocator{dir / "hot", cfg.hot_bytes, hot_cache, mode, true}, - ring_allocator{dir / "warm", cfg.warm_bytes, warm_cache, mode, true}, - ring_allocator{dir / "cool", cfg.cool_bytes, cool_cache, mode, true}}, - _cold{_gc, _obj_ids, dir / "cold", mode, cfg.cold_bytes} + _obj_ids(_gc, get_subpath(dir, "obj_ids", mode), mode), + _levels{ + ring_allocator{get_subpath(dir, "hot", mode), cfg.hot_bytes, hot_cache, mode, true}, + ring_allocator{get_subpath(dir, "warm", mode), cfg.warm_bytes, warm_cache, mode, true}, + ring_allocator{get_subpath(dir, "cool", mode), cfg.cool_bytes, cool_cache, mode, + true}}, + _cold{_gc, _obj_ids, get_subpath(dir, "cold", mode), mode, cfg.cold_bytes} { - if (mode == access_mode::read_write) + if (mode != open_mode::read_only) { _swap_thread = std::thread( [this]() @@ -23,7 +34,7 @@ namespace triedent thread_name("swap"); #ifndef __APPLE__ pthread_setname_np(pthread_self(), "swap"); -#else // if __APPLE__ +#else // if __APPLE__ pthread_setname_np("swap"); #endif swap_loop(); @@ -31,9 +42,9 @@ namespace triedent _gc_thread = std::thread{[this] { #ifndef __APPLE__ - pthread_setname_np(pthread_self(), "swap"); -#else // if __APPLE__ - pthread_setname_np("swap"); + pthread_setname_np(pthread_self(), "swap"); +#else // if __APPLE__ + pthread_setname_np("swap"); #endif _gc.run(&_done); }}; diff --git a/libraries/triedent/src/database.cpp b/libraries/triedent/src/database.cpp index 8f081c49c..2c1e13f9f 100644 --- a/libraries/triedent/src/database.cpp +++ b/libraries/triedent/src/database.cpp @@ -3,12 +3,20 @@ namespace triedent { - database::database(const std::filesystem::path& dir, - const config& cfg, - access_mode mode, - bool allow_gc) - : _ring{dir / "data", cfg, mode, allow_gc}, - _file{dir / "db", mode}, + namespace + { + void create_db_dirs(const std::filesystem::path& dir, open_mode mode) + { + if (mode == open_mode::create || mode == open_mode::trunc || mode == open_mode::create_new) + { + std::filesystem::create_directories(dir / "data"); + } + } + } // namespace + + database::database(const std::filesystem::path& dir, const config& cfg, open_mode mode) + : _ring{(create_db_dirs(dir, mode), get_subpath(dir, "data", mode)), cfg, mode}, + _file{get_subpath(dir, "db", mode), mode}, _root_release_session{_ring} { if (_file.size() == 0) @@ -29,8 +37,8 @@ namespace triedent throw std::runtime_error("Not a triedent db file: " + (dir / "db").native()); } - database::database(const std::filesystem::path& dir, access_mode mode, bool allow_gc) - : database(dir, config{}, mode, allow_gc) + database::database(const std::filesystem::path& dir, open_mode mode) + : database(dir, config{}, mode) { } @@ -44,7 +52,7 @@ namespace triedent std::filesystem::create_directories(dir / "data"); - (void)database{dir, cfg, access_mode::read_write}; + (void)database{dir, cfg, open_mode::create_new}; } void database::print_stats(std::ostream& os, bool detail) diff --git a/libraries/triedent/src/gc_queue.cpp b/libraries/triedent/src/gc_queue.cpp index 34587cab5..a1ff063df 100644 --- a/libraries/triedent/src/gc_queue.cpp +++ b/libraries/triedent/src/gc_queue.cpp @@ -133,7 +133,7 @@ namespace triedent { if constexpr (debug_gc) { - // std::osyncstream(std::cout) << "run gc: " << start << std::endl; + // std::osyncstream(std::cout) << "run gc: " << start << std::endl; } out.push_back(std::move(_queue[start])); --_size; diff --git a/libraries/triedent/src/mapping.cpp b/libraries/triedent/src/mapping.cpp index bdab61888..9b607ca29 100644 --- a/libraries/triedent/src/mapping.cpp +++ b/libraries/triedent/src/mapping.cpp @@ -33,25 +33,53 @@ namespace triedent *pinned = ::mlock(base, size) == 0; } } - } // namespace - mapping::mapping(const std::filesystem::path& file, access_mode mode, bool pin) - : _mode(mode), _pinned(pin) - { - int flags = O_CLOEXEC; - int flock_operation; - if (mode == access_mode::read_write) + int get_flags(open_mode mode) { - flags |= O_RDWR; - flags |= O_CREAT; - flock_operation = LOCK_EX; + switch (mode) + { + case open_mode::read_only: + return O_RDONLY; + case open_mode::read_write: + case open_mode::gc: + return O_RDWR; + case open_mode::create: + return O_RDWR | O_CREAT; + case open_mode::trunc: + return O_RDWR | O_CREAT | O_TRUNC; + case open_mode::create_new: + return O_RDWR | O_CREAT | O_EXCL; + case open_mode::temporary: +#ifdef O_TMPFILE + return O_RDWR | O_TMPFILE; +#else + return O_RDWR | O_CREAT | O_EXCL; +#endif + } + __builtin_unreachable(); } + } // namespace + + mapping::mapping(const std::filesystem::path& file, open_mode mode, bool pin) + : _mode(mode == open_mode::read_only ? access_mode::read_only : access_mode::read_write), + _pinned(pin) + { + int flags = O_CLOEXEC | get_flags(mode); + int flock_operation = mode == open_mode::read_only ? LOCK_SH : LOCK_EX; +#ifdef O_TMPFILE + _fd = ::open(file.native().c_str(), flags, 0644); + if (_fd == -1 && mode == open_mode::temporary && errno == EOPNOTSUPP) +#else + if (mode != open_mode::temporary) + _fd = ::open(file.native().c_str(), flags, 0644); else +#endif { - flags |= O_RDONLY; - flock_operation = LOCK_SH; + std::string filename = (file / "triedent-XXXXXX").native(); + _fd = ::mkstemp(filename.data()); + if (_fd != -1) + ::unlink(filename.data()); } - _fd = ::open(file.native().c_str(), flags, 0644); if (_fd == -1) { throw std::system_error{errno, std::generic_category()}; @@ -74,7 +102,7 @@ namespace triedent } else { - if (auto addr = ::mmap(nullptr, _size, get_prot(mode), MAP_SHARED, _fd, 0); + if (auto addr = ::mmap(nullptr, _size, get_prot(_mode), MAP_SHARED, _fd, 0); addr != MAP_FAILED) { _data = addr; diff --git a/libraries/triedent/src/mermaid.cpp b/libraries/triedent/src/mermaid.cpp index 139f1cb58..d2ce84000 100644 --- a/libraries/triedent/src/mermaid.cpp +++ b/libraries/triedent/src/mermaid.cpp @@ -54,7 +54,7 @@ int main(int argc, char** argv) } if (vm.count("status")) { - auto db = std::make_shared(db_dir.c_str(), triedent::database::read_only); + auto db = std::make_shared(db_dir.c_str(), triedent::open_mode::read_only); db->print_stats(std::cerr, true); } if (vm.count("create")) @@ -67,7 +67,7 @@ int main(int argc, char** argv) if (vm.count("validate")) { - auto db = std::make_shared(db_dir.c_str(), triedent::database::read_only); + auto db = std::make_shared(db_dir.c_str(), triedent::open_mode::read_only); auto s = db->start_write_session(); auto root = s->get_top_root(); s->validate(root); @@ -76,7 +76,7 @@ int main(int argc, char** argv) if (vm.count("gc")) { - auto db = std::make_shared(db_dir.c_str(), triedent::database::read_write, true); + auto db = std::make_shared(db_dir.c_str(), triedent::open_mode::gc); auto s = db->start_write_session(); s->start_collect_garbage(); s->recursive_retain(); diff --git a/libraries/triedent/src/region_allocator.cpp b/libraries/triedent/src/region_allocator.cpp index 8c268f9df..dadd9abbd 100644 --- a/libraries/triedent/src/region_allocator.cpp +++ b/libraries/triedent/src/region_allocator.cpp @@ -8,7 +8,7 @@ namespace triedent region_allocator::region_allocator(gc_queue& gc, object_db& obj_ids, const std::filesystem::path& path, - access_mode mode, + open_mode mode, std::uint64_t initial_size) : _gc(gc), _obj_ids(obj_ids), _file(path, mode), _done(false) { @@ -40,7 +40,7 @@ namespace triedent throw std::runtime_error("File size is smaller than required by the header: " + path.native()); - if (mode == access_mode::read_write) + if (_file.mode() == access_mode::read_write) { load_queue(); _thread = std::thread{[this] diff --git a/libraries/triedent/src/ring_allocator.cpp b/libraries/triedent/src/ring_allocator.cpp index d73776bbd..87975b40a 100644 --- a/libraries/triedent/src/ring_allocator.cpp +++ b/libraries/triedent/src/ring_allocator.cpp @@ -7,7 +7,7 @@ namespace triedent ring_allocator::ring_allocator(const std::filesystem::path& path, std::uint64_t size, std::uint8_t level, - access_mode mode, + open_mode mode, bool pin) : _file(path, mode, pin), _level(level), _free_min(0) { @@ -83,8 +83,8 @@ namespace triedent _self->_end_free_p = end_free_p | end_free_x; if constexpr (debug_gc) { - // std::osyncstream(std::cout) - // << "end_free_p: " << _self->_level << ":" << end_free_p << std::endl; + // std::osyncstream(std::cout) + // << "end_free_p: " << _self->_level << ":" << end_free_p << std::endl; } } _self->_free_cond.notify_all(); diff --git a/libraries/triedent/test/big.cpp b/libraries/triedent/test/big.cpp index 66a848858..3da108e88 100644 --- a/libraries/triedent/test/big.cpp +++ b/libraries/triedent/test/big.cpp @@ -94,8 +94,8 @@ int main(int argc, char** argv) } uint64_t total = insert_count; //2 * 1000 * 1000 * 1000; - auto _db = std::make_shared(db_dir.c_str(), triedent::database::read_write); - auto& db = *_db; + auto _db = std::make_shared(db_dir.c_str(), triedent::open_mode::read_write); + auto& db = *_db; db.print_stats(std::cerr); std::cerr << "\n"; auto s = db.start_write_session(); @@ -137,9 +137,9 @@ int main(int argc, char** argv) auto rs = db.start_read_session(); root_t rr; - std::mt19937 gen(c); - std::vector found_key; - std::vector found_value; + std::mt19937 gen(c); + std::vector found_key; + std::vector found_value; std::vector result_roots; uint64_t ch = 0; @@ -151,13 +151,16 @@ int main(int argc, char** argv) } while (r.load(std::memory_order_relaxed) == v) { - uint64_t h = (uint64_t(gen()) << 32) | gen(); - bool found = rs->get_less_than(rr, std::string_view((char*)&h, sizeof(h)), &found_key, &found_value, &result_roots); - if (found) { + uint64_t h = (uint64_t(gen()) << 32) | gen(); + bool found = rs->get_less_than(rr, std::string_view((char*)&h, sizeof(h)), + &found_key, &found_value, &result_roots); + if (found) + { ++total_lookups[c].total_lookups; } - else { - // std::cerr<< "no got value\n"; + else + { + // std::cerr<< "no got value\n"; } if (done.load(std::memory_order_relaxed)) break; @@ -250,14 +253,19 @@ int main(int argc, char** argv) db.print_stats(std::cerr); std::cerr << "\n"; } - auto add_comma = []( uint64_t s ) + auto add_comma = [](uint64_t s) { - if( s < 1000 ) return std::to_string(s); - if( s < 1000000 ) { - return std::to_string(s/1000) + ',' + std::to_string( (s % 1000)+1000 ).substr(1); + if (s < 1000) + return std::to_string(s); + if (s < 1000000) + { + return std::to_string(s / 1000) + ',' + std::to_string((s % 1000) + 1000).substr(1); } - if( s < 1000000000 ) { - return std::to_string(s/1000000) + ',' + std::to_string( ((s % 1000000)/1000)+1000 ).substr(1) + "," + std::to_string( (s % 1000)+1000 ).substr(1); + if (s < 1000000000) + { + return std::to_string(s / 1000000) + ',' + + std::to_string(((s % 1000000) / 1000) + 1000).substr(1) + "," + + std::to_string((s % 1000) + 1000).substr(1); } return std::to_string(s); }; @@ -270,13 +278,14 @@ int main(int argc, char** argv) auto read_end = get_total_lookups(); auto delta_read = read_end - read_start; read_start = read_end; - uint64_t r_per_sec = delta_read / ((std::chrono::duration(delta).count() / 1000)); + uint64_t r_per_sec = + delta_read / ((std::chrono::duration(delta).count() / 1000)); std::cerr << std::setw(12) - << add_comma(int64_t(status_count / - (std::chrono::duration(delta).count() / 1000))) + << add_comma(int64_t( + status_count / + (std::chrono::duration(delta).count() / 1000))) << " items/sec " << add_comma(i) << " total reads/sec: " << std::setw(12) - << add_comma(r_per_sec) - << "\n"; + << add_comma(r_per_sec) << "\n"; } uint64_t v[2]; diff --git a/libraries/triedent/test/temp_database.cpp b/libraries/triedent/test/temp_database.cpp index 0dc6b40d6..71e87d998 100644 --- a/libraries/triedent/test/temp_database.cpp +++ b/libraries/triedent/test/temp_database.cpp @@ -6,7 +6,6 @@ using namespace triedent; std::shared_ptr createDb(const database::config& cfg) { - temp_directory dir("triedent-test"); - database::create(dir.path, cfg); - return std::make_shared(dir.path, cfg, access_mode::read_write); + return std::make_shared(std::filesystem::temp_directory_path(), cfg, + open_mode::temporary); } diff --git a/libraries/triedent/test/test_cache_allocator.cpp b/libraries/triedent/test/test_cache_allocator.cpp index 79d99a1c9..1ad2ac779 100644 --- a/libraries/triedent/test/test_cache_allocator.cpp +++ b/libraries/triedent/test/test_cache_allocator.cpp @@ -17,12 +17,9 @@ extern std::vector data; TEST_CASE("cache_allocator") { - temp_directory dir("triedent-test"); - std::filesystem::create_directories(dir.path); - cache_allocator a{dir.path, + cache_allocator a{std::filesystem::temp_directory_path(), {.hot_bytes = 128, .warm_bytes = 128, .cool_bytes = 128, .cold_bytes = 1024}, - access_mode::read_write}; - dir.reset(); + open_mode::temporary}; std::vector> known_ids; std::mutex id_mutex; @@ -65,7 +62,7 @@ TEST_CASE("cache_allocator") }; std::vector threads; - std::atomic done{false}; + std::atomic done{false}; threads.emplace_back( [&] { @@ -81,7 +78,8 @@ TEST_CASE("cache_allocator") write_some(session); } done = true; - for( auto& t : threads ) { + for (auto& t : threads) + { t.join(); } threads.clear(); @@ -91,12 +89,9 @@ TEST_CASE("cache_allocator") TEST_CASE("cache_allocator long") { - temp_directory dir("triedent-test"); - std::filesystem::create_directories(dir.path); - cache_allocator a{dir.path, + cache_allocator a{std::filesystem::temp_directory_path(), {.hot_bytes = 128, .warm_bytes = 128, .cool_bytes = 128, .cold_bytes = 128}, - access_mode::read_write}; - dir.reset(); + open_mode::temporary}; std::vector ids{data.size()}; std::mutex mutex; @@ -161,9 +156,9 @@ TEST_CASE("cache_allocator long") } if (copy != data[idx]) { - // not supported on mac, std::osyncstream(std::cout) << copy << " != " << data[idx] << std::endl; + // not supported on mac, std::osyncstream(std::cout) << copy << " != " << data[idx] << std::endl; std::cout << copy << " != " << data[idx] << std::endl; - + failed.store(true); done.store(true); } @@ -219,7 +214,8 @@ TEST_CASE("cache_allocator long") std::this_thread::sleep_for(100ms); #endif done.store(true); - for( auto& t : threads ) { + for (auto& t : threads) + { t.join(); } threads.clear(); diff --git a/libraries/triedent/test/test_gc_queue.cpp b/libraries/triedent/test/test_gc_queue.cpp index 25f4feab6..8058e3c30 100644 --- a/libraries/triedent/test/test_gc_queue.cpp +++ b/libraries/triedent/test/test_gc_queue.cpp @@ -57,7 +57,8 @@ TEST_CASE("gc_queue") std::this_thread::sleep_for(10ms); done = true; - for( auto& t : threads ) { + for (auto& t : threads) + { t.join(); } diff --git a/libraries/triedent/test/test_location_lock.cpp b/libraries/triedent/test/test_location_lock.cpp index 75b6426fa..5370eff6b 100644 --- a/libraries/triedent/test/test_location_lock.cpp +++ b/libraries/triedent/test/test_location_lock.cpp @@ -47,7 +47,8 @@ TEST_CASE("location_lock") std::this_thread::sleep_for(100ms); done.store(true); - for( auto& t : threads ) { + for (auto& t : threads) + { t.join(); } diff --git a/libraries/triedent/test/test_mapping.cpp b/libraries/triedent/test/test_mapping.cpp index ad440cde1..446eb1856 100644 --- a/libraries/triedent/test/test_mapping.cpp +++ b/libraries/triedent/test/test_mapping.cpp @@ -11,7 +11,7 @@ using namespace triedent; TEST_CASE("mapping access") { temp_directory dir("triedent-test"); - mapping m{dir.path, read_write}; + mapping m{dir.path, open_mode::create}; m.resize(4096); auto p1 = (char*)m.data(); *p1 = 42; @@ -28,7 +28,7 @@ TEST_CASE("mapping access") TEST_CASE("extend mapping aligned") { temp_directory dir("triedent-test"); - mapping m{dir.path, read_write}; + mapping m{dir.path, open_mode::create}; for (std::size_t i = 1; i < 10; ++i) { @@ -52,7 +52,7 @@ TEST_CASE("extend mapping aligned") TEST_CASE("extend mapping unaligned") { temp_directory dir("triedent-test"); - mapping m{dir.path, read_write}; + mapping m{dir.path, open_mode::create}; std::size_t prev = 0; for (std::size_t i : @@ -81,15 +81,33 @@ TEST_CASE("file access") { temp_directory dir("triedent-test"); { - CHECK_THROWS(mapping(dir.path, read_only)); + CHECK_THROWS(mapping(dir.path, open_mode::read_only)); } { - mapping m{dir.path, read_write}; - CHECK_THROWS(mapping{dir.path, read_write}); - CHECK_THROWS(mapping{dir.path, read_only}); + mapping m{dir.path, open_mode::create}; + CHECK_THROWS(mapping{dir.path, open_mode::read_write}); + CHECK_THROWS(mapping{dir.path, open_mode::read_only}); } { - mapping m1{dir.path, read_only}; - mapping m2{dir.path, read_only}; + mapping m1{dir.path, open_mode::read_only}; + mapping m2{dir.path, open_mode::read_only}; + } +} + +TEST_CASE("file mode") +{ + temp_directory dir("triedent-test"); + { + mapping m{dir.path, open_mode::create_new}; + m.resize(256); + } + { + mapping m{dir.path, open_mode::create}; + CHECK(m.size() == 256); + } + CHECK_THROWS((void)mapping{dir.path, open_mode::create_new}); + { + mapping m{dir.path, open_mode::trunc}; + CHECK(m.size() == 0); } } diff --git a/libraries/triedent/test/test_region_allocator.cpp b/libraries/triedent/test/test_region_allocator.cpp index 831493d5d..34b22b609 100644 --- a/libraries/triedent/test/test_region_allocator.cpp +++ b/libraries/triedent/test/test_region_allocator.cpp @@ -15,12 +15,10 @@ extern std::vector data; TEST_CASE("region_allocator") { - temp_directory dir("triedent-test"); - std::filesystem::create_directories(dir.path); - gc_queue gc{256}; - object_db obj_ids{gc, dir.path / "obj_ids", access_mode::read_write}; - region_allocator a{gc, obj_ids, dir.path / "cold", access_mode::read_write, 128}; - dir.reset(); + auto path = std::filesystem::temp_directory_path(); + gc_queue gc{256}; + object_db obj_ids{gc, path, open_mode::temporary}; + region_allocator a{gc, obj_ids, path, open_mode::temporary, 128}; std::vector ids; gc_session session{gc}; for (const std::string& s : data) @@ -50,12 +48,10 @@ TEST_CASE("region_allocator") TEST_CASE("region_allocator threaded") { - temp_directory dir("triedent-test"); - std::filesystem::create_directories(dir.path); + auto path = std::filesystem::temp_directory_path(); gc_queue gc{256}; - object_db obj_ids{gc, dir.path / "obj_ids", access_mode::read_write}; - region_allocator a{gc, obj_ids, dir.path / "cold", access_mode::read_write, 128}; - dir.reset(); + object_db obj_ids{gc, path, open_mode::temporary}; + region_allocator a{gc, obj_ids, path, open_mode::temporary, 128}; std::vector ids{data.size()}; std::mutex mutex; @@ -176,8 +172,9 @@ TEST_CASE("region_allocator threaded") done.store(true); gc.notify_run(); gc.push(std::make_shared(0)); - - for( auto& t : threads ) { + + for (auto& t : threads) + { t.join(); } diff --git a/libraries/triedent/test/test_ring_allocator.cpp b/libraries/triedent/test/test_ring_allocator.cpp index 68910e7f9..a82a25ce3 100644 --- a/libraries/triedent/test/test_ring_allocator.cpp +++ b/libraries/triedent/test/test_ring_allocator.cpp @@ -38,9 +38,7 @@ std::vector data = { TEST_CASE("ring_allocator") { - temp_directory dir("triedent-test"); - ring_allocator a{dir.path, 128, 0, access_mode::read_write, false}; - dir.reset(); + ring_allocator a{std::filesystem::temp_directory_path(), 128, 0, open_mode::temporary, false}; std::vector offsets; std::atomic done{false}; gc_queue gc{256}; diff --git a/libraries/triedent/test/triedent-tests.cpp b/libraries/triedent/test/triedent-tests.cpp index 80c12465a..59a7e347d 100644 --- a/libraries/triedent/test/triedent-tests.cpp +++ b/libraries/triedent/test/triedent-tests.cpp @@ -167,7 +167,7 @@ TEST_CASE("recover") temp_directory dir("triedent-test"); database::create(dir.path, database::config{1ull << 27, 1ull << 27, 1ull << 27, 1ull << 27}); { - auto db = std::make_shared(dir.path, access_mode::read_write); + auto db = std::make_shared(dir.path, open_mode::read_write); std::shared_ptr root; auto session = db->start_write_session(); session->upsert(root, "abc"s, "v0"s); @@ -178,23 +178,23 @@ TEST_CASE("recover") session->set_top_root(top_root); } { - auto db = std::make_shared(dir.path, access_mode::read_write, true); + auto db = std::make_shared(dir.path, open_mode::gc); auto session = db->start_write_session(); session->start_collect_garbage(); } // An interrupted gc may leave reference counts too low. // Access MUST be forbidden until gc is finished to prevent // corruption. - CHECK_THROWS(std::make_shared(dir.path, access_mode::read_write)); + CHECK_THROWS(std::make_shared(dir.path, open_mode::read_write)); { - auto db = std::make_shared(dir.path, access_mode::read_write, true); + auto db = std::make_shared(dir.path, open_mode::gc); auto session = db->start_write_session(); session->start_collect_garbage(); session->recursive_retain(); session->end_collect_garbage(); } { - auto db = std::make_shared(dir.path, access_mode::read_write); + auto db = std::make_shared(dir.path, open_mode::read_write); auto session = db->start_write_session(); auto root = session->get_top_root(); std::vector> roots; diff --git a/programs/psinode/main.cpp b/programs/psinode/main.cpp index a2535ea13..0d99e7d49 100644 --- a/programs/psinode/main.cpp +++ b/programs/psinode/main.cpp @@ -1439,9 +1439,8 @@ void to_config(const PsinodeConfig& config, ConfigFile& file) if (!config.tls.trustfiles.empty()) { file.set( - "", "tls-trustfile", config.tls.trustfiles, - [](std::string_view text) { return std::string(text); }, - "A file containing trusted certificate authorities"); + "", "tls-trustfile", config.tls.trustfiles, [](std::string_view text) + { return std::string(text); }, "A file containing trusted certificate authorities"); } #endif if (!config.services.empty()) @@ -1452,9 +1451,8 @@ void to_config(const PsinodeConfig& config, ConfigFile& file) services.push_back(to_string(service)); } file.set( - "", "service", services, - [](std::string_view text) { return service_from_string(text).host; }, - "Native service root directory"); + "", "service", services, [](std::string_view text) + { return service_from_string(text).host; }, "Native service root directory"); } if (!std::holds_alternative(config.admin)) { @@ -1510,8 +1508,10 @@ void run(const std::string& db_path, // TODO: configurable WasmCache size auto sharedState = std::make_shared( - SharedDatabase{db_path, db_conf.hot_bytes, db_conf.warm_bytes, db_conf.cool_bytes, - db_conf.cold_bytes}, + SharedDatabase{ + db_path, + {db_conf.hot_bytes, db_conf.warm_bytes, db_conf.cool_bytes, db_conf.cold_bytes}, + triedent::open_mode::create}, WasmCache{128}); auto system = sharedState->getSystemContext(); auto proofSystem = sharedState->getSystemContext(); diff --git a/programs/psitest/main.cpp b/programs/psitest/main.cpp index 0ffdf512b..0cebfb9cf 100644 --- a/programs/psitest/main.cpp +++ b/programs/psitest/main.cpp @@ -183,6 +183,24 @@ struct file; struct HttpSocket; struct test_chain; +struct WasmMemoryCache +{ + std::vector> memories; + void init(psibase::SystemContext& ctx) + { + if (!memories.empty()) + { + ctx.executionMemories = std::move(memories.back()); + memories.pop_back(); + } + } + void cleanup(psibase::SystemContext& ctx) + { + if (!ctx.executionMemories.empty()) + memories.push_back(std::move(ctx.executionMemories)); + } +}; + struct state { const char* wasm; @@ -193,6 +211,8 @@ struct state cl_flags_t additional_args; std::vector files; std::vector> sockets; + psibase::WasmCache shared_wasm_cache{128}; + WasmMemoryCache shared_memory_cache; std::vector> chains; std::shared_ptr watchdogManager = std::make_shared(); std::vector result_key; @@ -218,14 +238,15 @@ struct test_chain_ref struct test_chain { - ::state& state; - std::set refs; - boost::filesystem::path dir; - psibase::SharedDatabase db; - psibase::WriterPtr writer; - std::unique_ptr sys; - std::shared_ptr revisionAtBlockStart; - std::unique_ptr blockContext; + ::state& state; + std::set refs; + psibase::SharedDatabase db; + psibase::WriterPtr writer; + std::unique_ptr sys; + std::shared_ptr revisionAtBlockStart; + std::unique_ptr blockContext; + // altBlockContext is created on demand to handle db reads between blocks + std::unique_ptr altBlockContext; std::unique_ptr nativeFunctionsTrace; std::unique_ptr nativeFunctionsTransactionContext; std::unique_ptr nativeFunctionsActionContext; @@ -247,21 +268,28 @@ struct test_chain } const std::string& getName() { return name; } - test_chain(::state& state, - uint64_t hot_bytes, - uint64_t warm_bytes, - uint64_t cool_bytes, - uint64_t cold_bytes) - : state{state} + test_chain(::state& state, psibase::SharedDatabase&& db) : state{state}, db{std::move(db)} + { + writer = this->db.createWriter(); + sys = std::make_unique( + psibase::SystemContext{this->db, + state.shared_wasm_cache, + {}, + state.watchdogManager, + std::make_shared()}); + state.shared_memory_cache.init(*sys); + } + + test_chain(::state& state, + const std::filesystem::path& path, + const triedent::database_config& config, + triedent::open_mode mode) + : test_chain{state, {path, config, mode}} { - dir = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path(); - db = {dir, hot_bytes, warm_bytes, cool_bytes, cold_bytes}; - writer = db.createWriter(); - sys = std::make_unique(psibase::SystemContext{ - db, {128}, {}, state.watchdogManager, std::make_shared()}); } - test_chain(const test_chain&) = delete; + explicit test_chain(const test_chain& other) : test_chain{other.state, other.db.clone()} {} + test_chain& operator=(const test_chain&) = delete; ~test_chain() @@ -273,10 +301,10 @@ struct test_chain nativeFunctionsTransactionContext.reset(); blockContext.reset(); revisionAtBlockStart.reset(); + state.shared_memory_cache.cleanup(*sys); sys.reset(); writer = {}; db = {}; - boost::filesystem::remove_all(dir); } // TODO: Support sub-second block times @@ -285,6 +313,11 @@ struct test_chain // TODO: undo control finishBlock(); revisionAtBlockStart = db.getHead(); + nativeFunctions.reset(); + nativeFunctionsActionContext.reset(); + nativeFunctionsTransactionContext.reset(); + altBlockContext.reset(); + blockContext = std::make_unique(*sys, revisionAtBlockStart, writer, true); @@ -349,12 +382,26 @@ struct test_chain } } + psibase::BlockContext* readBlockContext() + { + if (blockContext) + return blockContext.get(); + else + { + if (!altBlockContext) + altBlockContext = std::make_unique( + *sys, sys->sharedDatabase.getHead(), writer, true); + return altBlockContext.get(); + } + } + psibase::NativeFunctions& native() { static const psibase::SignedTransaction dummyTransaction; static const psibase::Action dummyAction; if (!nativeFunctions) { + auto* blockContext = readBlockContext(); if (!blockContext) throw std::runtime_error("no block context to read database from"); nativeFunctionsTrace = std::make_unique(); @@ -1061,8 +1108,53 @@ struct callbacks uint64_t cool_bytes, uint64_t cold_bytes) { - state.chains.push_back( - std::make_unique(state, hot_bytes, warm_bytes, cool_bytes, cold_bytes)); + state.chains.push_back(std::make_unique( + state, std::filesystem::temp_directory_path(), + triedent::database_config{hot_bytes, warm_bytes, cool_bytes, cold_bytes}, + triedent::open_mode::temporary)); + return state.chains.size() - 1; + } + + uint32_t testerOpenChain(span path, + wasi_oflags_t oflags, + wasi_rights_t fs_rights_base, + wasm_ptr config) + { + bool read = fs_rights_base & wasi_rights_fd_read; + bool write = fs_rights_base & wasi_rights_fd_write; + bool create = oflags & wasi_oflags_creat; + bool excl = oflags & wasi_oflags_excl; + bool trunc = oflags & wasi_oflags_trunc; + + psibase::check(read, "Chain cannot be opened without read access"); + + triedent::open_mode mode; + if (!write) + { + if (create || excl || trunc) + throw std::runtime_error("Unsupported combination of flags for openChain"); + mode = triedent::open_mode::read_only; + } + else if (!create && !excl && !trunc) + mode = triedent::open_mode::read_write; + else if (create && !excl && !trunc) + mode = triedent::open_mode::create; + else if (create && excl && !trunc) + mode = triedent::open_mode::create_new; + else if (create && !excl && trunc) + mode = triedent::open_mode::trunc; + else + throw std::runtime_error("Unsupported combination of flags for openChain"); + + state.chains.push_back(std::make_unique( + state, std::string_view{path.data(), path.size()}, *config, mode)); + return state.chains.size() - 1; + } + + uint32_t testerCloneChain(uint32_t chain) + { + auto& c = assert_chain(chain); + state.chains.push_back(std::make_unique(c)); return state.chains.size() - 1; } @@ -1085,13 +1177,6 @@ struct callbacks c.db = {}; } - uint32_t testerGetChainPath(uint32_t chain, span dest) - { - auto& c = assert_chain(chain, false); - memcpy(dest.data(), c.dir.c_str(), std::min(dest.size(), c.dir.size())); - return c.dir.size(); - } - // TODO: Support sub-second block times void testerStartBlock(uint32_t chain_index, uint32_t time_seconds) { @@ -1365,9 +1450,10 @@ void register_callbacks() // Tester Intrinsics rhf_t::add<&callbacks::testerCreateChain>("psibase", "createChain"); + rhf_t::add<&callbacks::testerOpenChain>("psibase", "openChain"); + rhf_t::add<&callbacks::testerCloneChain>("psibase", "cloneChain"); rhf_t::add<&callbacks::testerDestroyChain>("psibase", "destroyChain"); rhf_t::add<&callbacks::testerShutdownChain>("psibase", "shutdownChain"); - rhf_t::add<&callbacks::testerGetChainPath>("psibase", "getChainPath"); rhf_t::add<&callbacks::testerStartBlock>("psibase", "startBlock"); rhf_t::add<&callbacks::testerFinishBlock>("psibase", "finishBlock"); rhf_t::add<&callbacks::testerPushTransaction>("psibase", "pushTransaction"); diff --git a/services/user/Fractal/test/src/manual-fractal-test.cpp b/services/user/Fractal/test/src/manual-fractal-test.cpp index 258ac1587..8605782e7 100644 --- a/services/user/Fractal/test/src/manual-fractal-test.cpp +++ b/services/user/Fractal/test/src/manual-fractal-test.cpp @@ -24,38 +24,26 @@ namespace SCENARIO("Testing default psibase chain") { - DefaultTestChain t({"Default"}, true, {1ull << 32, 1ull << 32, 1ull << 32, 1ull << 32}); + { + TestChain t{"tester_psinode_db", O_RDWR | O_CREAT | O_TRUNC}; + t.boot({"DevDefault"}, true); - auto alice = t.from(t.addAccount("alice"_a)); - auto bob = t.from(t.addAccount("bob"_a)); - auto sysIssuer = t.from(Symbol::service).to(); - auto sysToken = Tokens::sysToken; + auto alice = t.from("alice"_a); - // Let sys token be tradeable - sysIssuer.setTokenConf(sysToken, "untradeable"_m, false); + auto create = alice.to().create(4, 1'000'000e4); + alice.to().mint(create.returnVal(), 100e4, memo); - // Distribute a few tokens - auto userBalance = 1'000'000e4; - sysIssuer.mint(sysToken, userBalance, memo); - sysIssuer.credit(sysToken, alice, 1'000e4, memo); - sysIssuer.credit(sysToken, bob, 1'000e4, memo); + // Create a fractal + alice.to().createIdentity(); + alice.to().newFractal("astronauts"_a, FractalNs::CoreFractal::service); - auto create = alice.to().create(4, 1'000'000e4); - alice.to().mint(create.returnVal(), 100e4, memo); - - // Create a fractal - alice.to().createIdentity(); - alice.to().newFractal("astronauts"_a, FractalNs::CoreFractal::service); - - // Make a couple blocks - t.finishBlock(); - t.startBlock(); - t.finishBlock(); + // Make a couple blocks + t.finishBlock(); + t.startBlock(); + t.finishBlock(); + } // Run the chain - std::system("rm -rf tester_psinode_db"); - std::system("mkdir tester_psinode_db"); - std::system(("cp -a " + t.getPath() + "/. tester_psinode_db/").c_str()); std::system( "psinode -o psibase.127.0.0.1.sslip.io tester_psinode_db -l 8080 --producer firstproducer"); }