From 34e48a11392e24147283446007b4e69b549f5f07 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Mon, 4 May 2015 00:20:46 +0200 Subject: [PATCH 1/6] Add memusage.h --- src/Makefile.am | 1 + src/memusage.h | 111 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 src/memusage.h diff --git a/src/Makefile.am b/src/Makefile.am index e4c3186668afb..faeaadf017d0b 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -130,6 +130,7 @@ BITCOIN_CORE_H = \ limitedmap.h \ logging.h \ main.h \ + memusage.h \ masternode.h \ masternode-payments.h \ masternode-budget.h \ diff --git a/src/memusage.h b/src/memusage.h new file mode 100644 index 0000000000000..9f7de9e2e165d --- /dev/null +++ b/src/memusage.h @@ -0,0 +1,111 @@ +// Copyright (c) 2015 The Bitcoin developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_MEMUSAGE_H +#define BITCOIN_MEMUSAGE_H + +#include + +#include +#include +#include + +#include +#include + +namespace memusage +{ + +/** Compute the total memory used by allocating alloc bytes. */ +static size_t MallocUsage(size_t alloc); + +/** Compute the memory used for dynamically allocated but owned data structures. + * For generic data types, this is *not* recursive. DynamicUsage(vector >) + * will compute the memory used for the vector's, but not for the ints inside. + * This is for efficiency reasons, as these functions are intended to be fast. If + * application data structures require more accurate inner accounting, they should + * do the recursion themselves, or use more efficient caching + updating on modification. + */ +template static size_t DynamicUsage(const std::vector& v); +template static size_t DynamicUsage(const std::set& s); +template static size_t DynamicUsage(const std::map& m); +template static size_t DynamicUsage(const boost::unordered_set& s); +template static size_t DynamicUsage(const boost::unordered_map& s); +template static size_t DynamicUsage(const X& x); + +static inline size_t MallocUsage(size_t alloc) +{ + // Measured on libc6 2.19 on Linux. + if (sizeof(void*) == 8) { + return ((alloc + 31) >> 4) << 4; + } else if (sizeof(void*) == 4) { + return ((alloc + 15) >> 3) << 3; + } else { + assert(0); + } +} + +// STL data structures + +template +struct stl_tree_node +{ +private: + int color; + void* parent; + void* left; + void* right; + X x; +}; + +template +static inline size_t DynamicUsage(const std::vector& v) +{ + return MallocUsage(v.capacity() * sizeof(X)); +} + +template +static inline size_t DynamicUsage(const std::set& s) +{ + return MallocUsage(sizeof(stl_tree_node)) * s.size(); +} + +template +static inline size_t DynamicUsage(const std::map& m) +{ + return MallocUsage(sizeof(stl_tree_node >)) * m.size(); +} + +// Boost data structures + +template +struct boost_unordered_node : private X +{ +private: + void* ptr; +}; + +template +static inline size_t DynamicUsage(const boost::unordered_set& s) +{ + return MallocUsage(sizeof(boost_unordered_node)) * s.size() + MallocUsage(sizeof(void*) * s.bucket_count()); +} + +template +static inline size_t DynamicUsage(const boost::unordered_map& m) +{ + return MallocUsage(sizeof(boost_unordered_node >)) * m.size() + MallocUsage(sizeof(void*) * m.bucket_count()); +} + +// Dispatch to class method as fallback + +template +static inline size_t DynamicUsage(const X& x) +{ + return x.DynamicMemoryUsage(); +} + +} + +#endif From e59840af667acb400bede8944bb41e538365a3cd Mon Sep 17 00:00:00 2001 From: furszy Date: Tue, 14 Apr 2020 16:52:00 -0300 Subject: [PATCH 2/6] Keep track of memory usage in CCoinsViewCache. Coming from upstream@046392dc1dd965b4ec1ba60a14a714e3e3fa7a88 --- src/coins.cpp | 24 +++++++++++++++++++++--- src/coins.h | 19 ++++++++++++++++++- src/test/coins_tests.cpp | 27 ++++++++++++++++++++++++--- 3 files changed, 63 insertions(+), 7 deletions(-) diff --git a/src/coins.cpp b/src/coins.cpp index 699d143de85fb..efe2dc284d601 100644 --- a/src/coins.cpp +++ b/src/coins.cpp @@ -5,6 +5,7 @@ #include "coins.h" +#include "memusage.h" #include "random.h" #include @@ -76,13 +77,17 @@ bool CCoinsViewBacked::GetStats(CCoinsStats& stats) const { return base->GetStat CCoinsKeyHasher::CCoinsKeyHasher() : salt(GetRandHash()) {} -CCoinsViewCache::CCoinsViewCache(CCoinsView* baseIn) : CCoinsViewBacked(baseIn), hasModifier(false) {} +CCoinsViewCache::CCoinsViewCache(CCoinsView* baseIn) : CCoinsViewBacked(baseIn), hasModifier(false), cachedCoinsUsage(0) {} CCoinsViewCache::~CCoinsViewCache() { assert(!hasModifier); } +size_t CCoinsViewCache::DynamicMemoryUsage() const { + return memusage::DynamicUsage(cacheCoins) + cachedCoinsUsage; +} + CCoinsMap::const_iterator CCoinsViewCache::FetchCoins(const uint256& txid) const { CCoinsMap::iterator it = cacheCoins.find(txid); @@ -98,6 +103,7 @@ CCoinsMap::const_iterator CCoinsViewCache::FetchCoins(const uint256& txid) const // version as fresh. ret->second.flags = CCoinsCacheEntry::FRESH; } + cachedCoinsUsage += memusage::DynamicUsage(ret->second.coins); return ret; } @@ -115,6 +121,7 @@ CCoinsModifier CCoinsViewCache::ModifyCoins(const uint256& txid) { assert(!hasModifier); std::pair ret = cacheCoins.insert(std::make_pair(txid, CCoinsCacheEntry())); + size_t cachedCoinUsage = 0; if (ret.second) { if (!base->GetCoins(txid, ret.first->second.coins)) { // The parent view does not have this entry; mark it as fresh. @@ -124,10 +131,12 @@ CCoinsModifier CCoinsViewCache::ModifyCoins(const uint256& txid) // The parent view only has a pruned entry for this; mark it as fresh. ret.first->second.flags = CCoinsCacheEntry::FRESH; } + } else { + cachedCoinUsage = memusage::DynamicUsage(ret.first->second.coins); } // Assume that whenever ModifyCoins is called, the entry will be modified. ret.first->second.flags |= CCoinsCacheEntry::DIRTY; - return CCoinsModifier(*this, ret.first); + return CCoinsModifier(*this, ret.first, cachedCoinUsage); } const CCoins* CCoinsViewCache::AccessCoins(const uint256& txid) const @@ -177,6 +186,7 @@ bool CCoinsViewCache::BatchWrite(CCoinsMap& mapCoins, const uint256& hashBlockIn assert(it->second.flags & CCoinsCacheEntry::FRESH); CCoinsCacheEntry& entry = cacheCoins[it->first]; entry.coins.swap(it->second.coins); + cachedCoinsUsage += memusage::DynamicUsage(entry.coins); entry.flags = CCoinsCacheEntry::DIRTY | CCoinsCacheEntry::FRESH; } } else { @@ -184,10 +194,13 @@ bool CCoinsViewCache::BatchWrite(CCoinsMap& mapCoins, const uint256& hashBlockIn // The grandparent does not have an entry, and the child is // modified and being pruned. This means we can just delete // it from the parent. + cachedCoinsUsage -= memusage::DynamicUsage(itUs->second.coins); cacheCoins.erase(itUs); } else { // A normal modification. + cachedCoinsUsage -= memusage::DynamicUsage(itUs->second.coins); itUs->second.coins.swap(it->second.coins); + cachedCoinsUsage += memusage::DynamicUsage(itUs->second.coins); itUs->second.flags |= CCoinsCacheEntry::DIRTY; } } @@ -203,6 +216,7 @@ bool CCoinsViewCache::Flush() { bool fOk = base->BatchWrite(cacheCoins, hashBlock); cacheCoins.clear(); + cachedCoinsUsage = 0; return fOk; } @@ -269,7 +283,7 @@ double CCoinsViewCache::GetPriority(const CTransaction& tx, int nHeight) const return tx.ComputePriority(dResult); } -CCoinsModifier::CCoinsModifier(CCoinsViewCache& cache_, CCoinsMap::iterator it_) : cache(cache_), it(it_) +CCoinsModifier::CCoinsModifier(CCoinsViewCache& cache_, CCoinsMap::iterator it_, size_t usage) : cache(cache_), it(it_), cachedCoinUsage(usage) { assert(!cache.hasModifier); cache.hasModifier = true; @@ -280,7 +294,11 @@ CCoinsModifier::~CCoinsModifier() assert(cache.hasModifier); cache.hasModifier = false; it->second.coins.Cleanup(); + cache.cachedCoinsUsage -= cachedCoinUsage; // Subtract the old usage if ((it->second.flags & CCoinsCacheEntry::FRESH) && it->second.coins.IsPruned()) { cache.cacheCoins.erase(it); + } else { + // If the coin still exists after the modification, add the new usage + cache.cachedCoinsUsage += memusage::DynamicUsage(it->second.coins); } } diff --git a/src/coins.h b/src/coins.h index f02b1eda43379..9bd5da3ce19a5 100644 --- a/src/coins.h +++ b/src/coins.h @@ -8,6 +8,7 @@ #define BITCOIN_COINS_H #include "compressor.h" +#include "memusage.h" #include "consensus/consensus.h" // can be removed once policy/ established #include "script/standard.h" #include "serialize.h" @@ -284,6 +285,15 @@ class CCoins return false; return true; } + + size_t DynamicMemoryUsage() const { + size_t ret = memusage::DynamicUsage(vout); + for(const CTxOut &out : vout) { + const std::vector *script = &out.scriptPubKey; + ret += memusage::DynamicUsage(*script); + } + return ret; + } }; class CCoinsKeyHasher @@ -390,7 +400,8 @@ class CCoinsModifier private: CCoinsViewCache& cache; CCoinsMap::iterator it; - CCoinsModifier(CCoinsViewCache& cache_, CCoinsMap::iterator it_); + size_t cachedCoinUsage; // Cached memory usage of the CCoins object before modification + CCoinsModifier(CCoinsViewCache& cache_, CCoinsMap::iterator it_, size_t usage); public: CCoins* operator->() { return &it->second.coins; } @@ -413,6 +424,9 @@ class CCoinsViewCache : public CCoinsViewBacked mutable uint256 hashBlock; mutable CCoinsMap cacheCoins; + /* Cached dynamic memory usage for the inner CCoins objects. */ + mutable size_t cachedCoinsUsage; + public: CCoinsViewCache(CCoinsView* baseIn); ~CCoinsViewCache(); @@ -448,6 +462,9 @@ class CCoinsViewCache : public CCoinsViewBacked //! Calculate the size of the cache (in number of transactions) unsigned int GetCacheSize() const; + //! Calculate the size of the cache (in bytes) + size_t DynamicMemoryUsage() const; + /** * Amount of pivx coming in to a transaction * Note that lightweight clients may not know anything besides the hash of previous transactions, diff --git a/src/test/coins_tests.cpp b/src/test/coins_tests.cpp index 7c5a9e4d5d737..fb25e09464771 100644 --- a/src/test/coins_tests.cpp +++ b/src/test/coins_tests.cpp @@ -61,6 +61,24 @@ class CCoinsViewTest : public CCoinsView bool GetStats(CCoinsStats& stats) const { return false; } }; + +class CCoinsViewCacheTest : public CCoinsViewCache +{ +public: + CCoinsViewCacheTest(CCoinsView* base) : CCoinsViewCache(base) {} + + void SelfTest() const + { + // Manually recompute the dynamic usage of the whole data, and compare it. + size_t ret = memusage::DynamicUsage(cacheCoins); + for (CCoinsMap::iterator it = cacheCoins.begin(); it != cacheCoins.end(); it++) { + ret += memusage::DynamicUsage(it->second.coins); + } + BOOST_CHECK_EQUAL(memusage::DynamicUsage(*this), ret); + } + +}; + } BOOST_FIXTURE_TEST_SUITE(coins_tests, BasicTestingSetup) @@ -92,8 +110,8 @@ BOOST_AUTO_TEST_CASE(coins_cache_simulation_test) // The cache stack. CCoinsViewTest base; // A CCoinsViewTest at the bottom. - std::vector stack; // A stack of CCoinsViewCaches on top. - stack.push_back(new CCoinsViewCache(&base)); // Start with one cache. + std::vector stack; // A stack of CCoinsViewCaches on top. + stack.push_back(new CCoinsViewCacheTest(&base)); // Start with one cache. // Use a limited set of random transaction ids, so we do test overwriting entries. std::vector txids; @@ -138,6 +156,9 @@ BOOST_AUTO_TEST_CASE(coins_cache_simulation_test) missed_an_entry = true; } } + for (const CCoinsViewCacheTest *test : stack) { + test->SelfTest(); + } } if (InsecureRandRange(100) == 0) { @@ -154,7 +175,7 @@ BOOST_AUTO_TEST_CASE(coins_cache_simulation_test) } else { removed_all_caches = true; } - stack.push_back(new CCoinsViewCache(tip)); + stack.push_back(new CCoinsViewCacheTest(tip)); if (stack.size() == 4) { reached_4_caches = true; } From 5e505dfb65432126dd977614077ff688fc4315ed Mon Sep 17 00:00:00 2001 From: furszy Date: Tue, 14 Apr 2020 16:54:33 -0300 Subject: [PATCH 3/6] Use accurate memory for flushing decisions Coming from upstream@fc684ad8afae19c209701230837d338c5a6c1f72 --- src/init.cpp | 2 +- src/main.cpp | 6 +++--- src/main.h | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index 39ad0a19b1446..ca088738a4ba2 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1431,7 +1431,7 @@ bool AppInit2() nTotalCache -= nBlockTreeDBCache; size_t nCoinDBCache = nTotalCache / 2; // use half of the remaining cache for coindb cache nTotalCache -= nCoinDBCache; - nCoinCacheSize = nTotalCache / 300; // coins in memory require around 300 bytes + nCoinCacheUsage = nTotalCache; bool fLoaded = false; while (!fLoaded && !ShutdownRequested()) { diff --git a/src/main.cpp b/src/main.cpp index 68731541bc376..a3c3f6785f0eb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -94,7 +94,7 @@ bool fTxIndex = true; bool fIsBareMultisigStd = true; bool fCheckBlockIndex = false; bool fVerifyingBlocks = false; -unsigned int nCoinCacheSize = 5000; +size_t nCoinCacheUsage = 5000 * 300; /* If the tip is older than this (in seconds), the node is considered to be in initial block download. */ int64_t nMaxTipAge = DEFAULT_MAX_TIP_AGE; @@ -2588,7 +2588,7 @@ bool static FlushStateToDisk(CValidationState& state, FlushStateMode mode) static int64_t nLastWrite = 0; try { if ((mode == FLUSH_STATE_ALWAYS) || - ((mode == FLUSH_STATE_PERIODIC || mode == FLUSH_STATE_IF_NEEDED) && pcoinsTip->GetCacheSize() > nCoinCacheSize) || + ((mode == FLUSH_STATE_PERIODIC || mode == FLUSH_STATE_IF_NEEDED) && pcoinsTip->DynamicMemoryUsage() > nCoinCacheUsage) || (mode == FLUSH_STATE_PERIODIC && GetTimeMicros() > nLastWrite + DATABASE_WRITE_INTERVAL * 1000000)) { // Typical CCoins structures on disk are around 100 bytes in size. // Pushing a new one to the database can cause it to be written @@ -4532,7 +4532,7 @@ bool CVerifyDB::VerifyDB(CCoinsView* coinsview, int nCheckLevel, int nCheckDepth } } // check level 3: check for inconsistencies during memory-only disconnect of tip blocks - if (nCheckLevel >= 3 && pindex == pindexState && (coins.GetCacheSize() + pcoinsTip->GetCacheSize()) <= nCoinCacheSize) { + if (nCheckLevel >= 3 && pindex == pindexState && (coins.DynamicMemoryUsage() + pcoinsTip->DynamicMemoryUsage()) <= nCoinCacheUsage) { bool fClean = true; if (!DisconnectBlock(block, state, pindex, coins, &fClean)) return error("VerifyDB() : *** irrecoverable inconsistency in block data at %d, hash=%s", pindex->nHeight, pindex->GetBlockHash().ToString()); diff --git a/src/main.h b/src/main.h index d2fb6436b3bce..f4a2fbe99cdcb 100644 --- a/src/main.h +++ b/src/main.h @@ -140,7 +140,7 @@ extern int nScriptCheckThreads; extern bool fTxIndex; extern bool fIsBareMultisigStd; extern bool fCheckBlockIndex; -extern unsigned int nCoinCacheSize; +extern unsigned int nCoinCacheUsage; extern CFeeRate minRelayTxFee; extern int64_t nMaxTipAge; extern bool fVerifyingBlocks; From 4338b0899d216a85011bc668bf86acb1d75a7ad3 Mon Sep 17 00:00:00 2001 From: furszy Date: Tue, 14 Apr 2020 17:01:27 -0300 Subject: [PATCH 4/6] Cache tweak and logging improvements Coming from upstream@b3ed4236beb7f68e1720ceb3da15e0c3682ef629 --- src/init.cpp | 18 ++++++++++-------- src/main.cpp | 4 ++-- src/txdb.h | 2 +- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index ca088738a4ba2..5d30c84244f20 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1420,18 +1420,20 @@ bool AppInit2() boost::filesystem::create_directories(GetDataDir() / "blocks"); // cache size calculations - size_t nTotalCache = (GetArg("-dbcache", nDefaultDbCache) << 20); - if (nTotalCache < (nMinDbCache << 20)) - nTotalCache = (nMinDbCache << 20); // total cache cannot be less than nMinDbCache - else if (nTotalCache > (nMaxDbCache << 20)) - nTotalCache = (nMaxDbCache << 20); // total cache cannot be greater than nMaxDbCache - size_t nBlockTreeDBCache = nTotalCache / 8; + int64_t nTotalCache = (GetArg("-dbcache", nDefaultDbCache) << 20); + nTotalCache = std::max(nTotalCache, nMinDbCache << 20); // total cache cannot be less than nMinDbCache + nTotalCache = std::min(nTotalCache, nMaxDbCache << 20); // total cache cannot be greater than nMaxDbcache + int64_t nBlockTreeDBCache = nTotalCache / 8; if (nBlockTreeDBCache > (1 << 21) && !GetBoolArg("-txindex", true)) nBlockTreeDBCache = (1 << 21); // block tree db cache shouldn't be larger than 2 MiB nTotalCache -= nBlockTreeDBCache; - size_t nCoinDBCache = nTotalCache / 2; // use half of the remaining cache for coindb cache + int64_t nCoinDBCache = std::min(nTotalCache / 2, (nTotalCache / 4) + (1 << 23)); // use 25%-50% of the remainder for disk cache nTotalCache -= nCoinDBCache; - nCoinCacheUsage = nTotalCache; + nCoinCacheUsage = nTotalCache; // the rest goes to in-memory cache + LogPrintf("Cache configuration:\n"); + LogPrintf("* Using %.1fMiB for block index database\n", nBlockTreeDBCache * (1.0 / 1024 / 1024)); + LogPrintf("* Using %.1fMiB for chain state database\n", nCoinDBCache * (1.0 / 1024 / 1024)); + LogPrintf("* Using %.1fMiB for in-memory UTXO set\n", nCoinCacheUsage * (1.0 / 1024 / 1024)); bool fLoaded = false; while (!fLoaded && !ShutdownRequested()) { diff --git a/src/main.cpp b/src/main.cpp index a3c3f6785f0eb..cca82808facaf 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2664,10 +2664,10 @@ void static UpdateTip(CBlockIndex* pindexNew) } const CBlockIndex* pChainTip = chainActive.Tip(); - LogPrintf("UpdateTip: new best=%s height=%d version=%d log2_work=%.16f tx=%lu date=%s progress=%f cache=%u\n", + LogPrintf("UpdateTip: new best=%s height=%d version=%d log2_work=%.16f tx=%lu date=%s progress=%f cache=%.1fMiB(%utx)\n", pChainTip->GetBlockHash().GetHex(), pChainTip->nHeight, pChainTip->nVersion, log(pChainTip->nChainWork.getdouble()) / log(2.0), (unsigned long)pChainTip->nChainTx, DateTimeStrFormat("%Y-%m-%d %H:%M:%S", pChainTip->GetBlockTime()), - Checkpoints::GuessVerificationProgress(pChainTip), (unsigned int)pcoinsTip->GetCacheSize()); + Checkpoints::GuessVerificationProgress(pChainTip), pcoinsTip->DynamicMemoryUsage() * (1.0 / (1<<20)), pcoinsTip->GetCacheSize()); // Check the version of the last 100 blocks to see if we need to upgrade: static bool fWarned = false; diff --git a/src/txdb.h b/src/txdb.h index 5bbe640deb224..46c52c5c49e5b 100644 --- a/src/txdb.h +++ b/src/txdb.h @@ -22,7 +22,7 @@ class uint256; //! -dbcache default (MiB) static const int64_t nDefaultDbCache = 100; //! max. -dbcache in (MiB) -static const int64_t nMaxDbCache = sizeof(void*) > 4 ? 4096 : 1024; +static const int64_t nMaxDbCache = sizeof(void*) > 4 ? 16384 : 1024; //! min. -dbcache in (MiB) static const int64_t nMinDbCache = 4; From 0b6928291c324f9aa32b6fd02f60a8aefea1c936 Mon Sep 17 00:00:00 2001 From: furszy Date: Tue, 14 Apr 2020 21:35:38 -0300 Subject: [PATCH 5/6] Write block index more frequently than cache flushes Coming from upstream@67708acff9c18e380fa6136ff0ae718959ead4b5 --- src/main.cpp | 54 +++++++++++++++++++++++++++++++++++++++------------- src/main.h | 8 +++++--- 2 files changed, 46 insertions(+), 16 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index cca82808facaf..c91dcf7bfe4db 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2586,16 +2586,39 @@ bool static FlushStateToDisk(CValidationState& state, FlushStateMode mode) { LOCK(cs_main); static int64_t nLastWrite = 0; + static int64_t nLastFlush = 0; + static int64_t nLastSetChain = 0; try { - if ((mode == FLUSH_STATE_ALWAYS) || - ((mode == FLUSH_STATE_PERIODIC || mode == FLUSH_STATE_IF_NEEDED) && pcoinsTip->DynamicMemoryUsage() > nCoinCacheUsage) || - (mode == FLUSH_STATE_PERIODIC && GetTimeMicros() > nLastWrite + DATABASE_WRITE_INTERVAL * 1000000)) { - // Typical CCoins structures on disk are around 100 bytes in size. + int64_t nNow = GetTimeMicros(); + // Avoid writing/flushing immediately after startup. + if (nLastWrite == 0) { + nLastWrite = nNow; + } + if (nLastFlush == 0) { + nLastFlush = nNow; + } + if (nLastSetChain == 0) { + nLastSetChain = nNow; + } + size_t cacheSize = pcoinsTip->DynamicMemoryUsage(); + // The cache is large and close to the limit, but we have time now (not in the middle of a block processing). + bool fCacheLarge = mode == FLUSH_STATE_PERIODIC && cacheSize * (10.0/9) > nCoinCacheUsage; + // The cache is over the limit, we have to write now. + bool fCacheCritical = mode == FLUSH_STATE_IF_NEEDED && cacheSize > nCoinCacheUsage; + // It's been a while since we wrote the block index to disk. Do this frequently, so we don't need to redownload after a crash. + bool fPeriodicWrite = mode == FLUSH_STATE_PERIODIC && nNow > nLastWrite + (int64_t)DATABASE_WRITE_INTERVAL * 1000000; + // It's been very long since we flushed the cache. Do this infrequently, to optimize cache usage. + bool fPeriodicFlush = mode == FLUSH_STATE_PERIODIC && nNow > nLastFlush + (int64_t)DATABASE_FLUSH_INTERVAL * 1000000; + // Combine all conditions that result in a full cache flush. + bool fDoFullFlush = (mode == FLUSH_STATE_ALWAYS) || fCacheLarge || fCacheCritical || fPeriodicFlush; + // Write blocks and block index to disk. + if (fDoFullFlush || fPeriodicWrite) { + // Typical CCoins structures on disk are around 128 bytes in size. // Pushing a new one to the database can cause it to be written // twice (once in the log, and once in the tables). This is already // an overestimation, as most will delete an existing entry or // overwrite one. Still, use a conservative safety factor of 2. - if (!CheckDiskSpace(100 * 2 * 2 * pcoinsTip->GetCacheSize())) + if (fDoFullFlush && !CheckDiskSpace(128 * 2 * 2 * pcoinsTip->GetCacheSize())) return state.Error("out of disk space"); // First make sure all block and undo data is flushed to disk. FlushBlockFile(); @@ -2625,17 +2648,22 @@ bool static FlushStateToDisk(CValidationState& state, FlushStateMode mode) return AbortNode(state, "Failed to write money supply to DB"); } } - // Finally flush the chainstate (which may refer to block index entries). - if (!pcoinsTip->Flush()) { - return AbortNode(state, "Failed to write to coin database"); - } + nLastWrite = nNow; + } + // Flush best chain related state. This can only be done if the blocks / block index write was also done. + if (fDoFullFlush) { + // Flush the chainstate (which may refer to block index entries). + if (!pcoinsTip->Flush()) + return AbortNode(state, "Failed to write to coin database"); + nLastFlush = nNow; + } + if ((mode == FLUSH_STATE_ALWAYS || mode == FLUSH_STATE_PERIODIC) && nNow > nLastSetChain + (int64_t)DATABASE_WRITE_INTERVAL * 1000000) { // Update best block in wallet (so we can detect restored wallets). - if (mode != FLUSH_STATE_IF_NEEDED) { - GetMainSignals().SetBestChain(chainActive.GetLocator()); - } - nLastWrite = GetTimeMicros(); + GetMainSignals().SetBestChain(chainActive.GetLocator()); + nLastSetChain = nNow; } + } catch (const std::runtime_error& e) { return AbortNode(state, std::string("System error while flushing: ") + e.what()); } diff --git a/src/main.h b/src/main.h index f4a2fbe99cdcb..8849f7fab465c 100644 --- a/src/main.h +++ b/src/main.h @@ -96,8 +96,10 @@ static const unsigned int MAX_HEADERS_RESULTS = 2000; * degree of disordering of blocks on disk (which make reindexing and in the future perhaps pruning * harder). We'll probably want to make this a per-peer adaptive value at some point. */ static const unsigned int BLOCK_DOWNLOAD_WINDOW = 1024; -/** Time to wait (in seconds) between writing blockchain state to disk. */ -static const unsigned int DATABASE_WRITE_INTERVAL = 3600; +/** Time to wait (in seconds) between writing blocks/block index to disk. */ +static const unsigned int DATABASE_WRITE_INTERVAL = 60 * 60; +/** Time to wait (in seconds) between flushing chainstate to disk. */ +static const unsigned int DATABASE_FLUSH_INTERVAL = 24 * 60 * 60; /** Maximum length of reject messages. */ static const unsigned int MAX_REJECT_MESSAGE_LENGTH = 111; @@ -140,7 +142,7 @@ extern int nScriptCheckThreads; extern bool fTxIndex; extern bool fIsBareMultisigStd; extern bool fCheckBlockIndex; -extern unsigned int nCoinCacheUsage; +extern size_t nCoinCacheUsage; extern CFeeRate minRelayTxFee; extern int64_t nMaxTipAge; extern bool fVerifyingBlocks; From 9880245a36eb3b1591e3a22c3ec82787ae682c07 Mon Sep 17 00:00:00 2001 From: furszy Date: Tue, 14 Apr 2020 21:44:49 -0300 Subject: [PATCH 6/6] Relocate calls to CheckDiskSpace Make sure we're checking disk space for block index writes and allow for pruning to happen before chainstate writes. Coming from upstream@86a5f4b54ebf5f3251f4c172cf9a5041ae43c082 --- src/main.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index c91dcf7bfe4db..2558e038d7c95 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2613,12 +2613,8 @@ bool static FlushStateToDisk(CValidationState& state, FlushStateMode mode) bool fDoFullFlush = (mode == FLUSH_STATE_ALWAYS) || fCacheLarge || fCacheCritical || fPeriodicFlush; // Write blocks and block index to disk. if (fDoFullFlush || fPeriodicWrite) { - // Typical CCoins structures on disk are around 128 bytes in size. - // Pushing a new one to the database can cause it to be written - // twice (once in the log, and once in the tables). This is already - // an overestimation, as most will delete an existing entry or - // overwrite one. Still, use a conservative safety factor of 2. - if (fDoFullFlush && !CheckDiskSpace(128 * 2 * 2 * pcoinsTip->GetCacheSize())) + // Depend on nMinDiskSpace to ensure we can write block index + if (!CheckDiskSpace(0)) return state.Error("out of disk space"); // First make sure all block and undo data is flushed to disk. FlushBlockFile(); @@ -2653,6 +2649,13 @@ bool static FlushStateToDisk(CValidationState& state, FlushStateMode mode) // Flush best chain related state. This can only be done if the blocks / block index write was also done. if (fDoFullFlush) { + // Typical CCoins structures on disk are around 128 bytes in size. + // Pushing a new one to the database can cause it to be written + // twice (once in the log, and once in the tables). This is already + // an overestimation, as most will delete an existing entry or + // overwrite one. Still, use a conservative safety factor of 2. + if (!CheckDiskSpace(128 * 2 * 2 * pcoinsTip->GetCacheSize())) + return state.Error("out of disk space"); // Flush the chainstate (which may refer to block index entries). if (!pcoinsTip->Flush()) return AbortNode(state, "Failed to write to coin database");