0
0
Fork 0
mirror of https://github.com/bitcoin/bitcoin.git synced 2025-02-12 11:19:08 -05:00

Add CChainState::ResizeCoinsCaches

Also adds CCoinsViewCache::ReallocateCache() to attempt to free
memory that the cacheCoins's allocator may be hanging onto when
downsizing the cache.

Adds `CChainState::m_coins{tip,db}_cache_size_bytes` data members
so that we can reference cache size on a per-chainstate basis for
flushing.
This commit is contained in:
James O'Beirne 2019-09-16 15:01:12 -04:00 committed by James O'Beirne
parent b223111da2
commit f36aaa6392
8 changed files with 73 additions and 16 deletions

View file

@ -245,6 +245,14 @@ bool CCoinsViewCache::HaveInputs(const CTransaction& tx) const
return true; return true;
} }
void CCoinsViewCache::ReallocateCache()
{
// Cache should be empty when we're calling this.
assert(cacheCoins.size() == 0);
cacheCoins.~CCoinsMap();
::new (&cacheCoins) CCoinsMap();
}
static const size_t MIN_TRANSACTION_OUTPUT_WEIGHT = WITNESS_SCALE_FACTOR * ::GetSerializeSize(CTxOut(), PROTOCOL_VERSION); static const size_t MIN_TRANSACTION_OUTPUT_WEIGHT = WITNESS_SCALE_FACTOR * ::GetSerializeSize(CTxOut(), PROTOCOL_VERSION);
static const size_t MAX_OUTPUTS_PER_BLOCK = MAX_BLOCK_WEIGHT / MIN_TRANSACTION_OUTPUT_WEIGHT; static const size_t MAX_OUTPUTS_PER_BLOCK = MAX_BLOCK_WEIGHT / MIN_TRANSACTION_OUTPUT_WEIGHT;

View file

@ -318,6 +318,13 @@ public:
//! Check whether all prevouts of the transaction are present in the UTXO set represented by this view //! Check whether all prevouts of the transaction are present in the UTXO set represented by this view
bool HaveInputs(const CTransaction& tx) const; bool HaveInputs(const CTransaction& tx) const;
//! Force a reallocation of the cache map. This is required when downsizing
//! the cache because the map's allocator may be hanging onto a lot of
//! memory despite having called .clear().
//!
//! See: https://stackoverflow.com/questions/42114044/how-to-release-unordered-map-memory
void ReallocateCache();
private: private:
/** /**
* @note this is marked const, but may actually append to `cacheCoins`, increasing * @note this is marked const, but may actually append to `cacheCoins`, increasing

View file

@ -1533,7 +1533,7 @@ bool AppInitMain(const util::Ref& context, NodeContext& node)
int64_t nCoinDBCache = std::min(nTotalCache / 2, (nTotalCache / 4) + (1 << 23)); // use 25%-50% of the remainder for disk cache int64_t nCoinDBCache = std::min(nTotalCache / 2, (nTotalCache / 4) + (1 << 23)); // use 25%-50% of the remainder for disk cache
nCoinDBCache = std::min(nCoinDBCache, nMaxCoinsDBCache << 20); // cap total coins db cache nCoinDBCache = std::min(nCoinDBCache, nMaxCoinsDBCache << 20); // cap total coins db cache
nTotalCache -= nCoinDBCache; nTotalCache -= nCoinDBCache;
nCoinCacheUsage = nTotalCache; // the rest goes to in-memory cache int64_t nCoinCacheUsage = nTotalCache; // the rest goes to in-memory cache
int64_t nMempoolSizeMax = gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000; int64_t nMempoolSizeMax = gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000;
LogPrintf("Cache configuration:\n"); LogPrintf("Cache configuration:\n");
LogPrintf("* Using %.1f MiB for block index database\n", nBlockTreeDBCache * (1.0 / 1024 / 1024)); LogPrintf("* Using %.1f MiB for block index database\n", nBlockTreeDBCache * (1.0 / 1024 / 1024));
@ -1645,7 +1645,7 @@ bool AppInitMain(const util::Ref& context, NodeContext& node)
} }
// The on-disk coinsdb is now in a good state, create the cache // The on-disk coinsdb is now in a good state, create the cache
chainstate->InitCoinsCache(); chainstate->InitCoinsCache(nCoinCacheUsage);
assert(chainstate->CanFlushToDisk()); assert(chainstate->CanFlushToDisk());
if (!is_coinsview_empty(chainstate)) { if (!is_coinsview_empty(chainstate)) {

View file

@ -139,7 +139,7 @@ TestingSetup::TestingSetup(const std::string& chainName, const std::vector<const
::ChainstateActive().InitCoinsDB( ::ChainstateActive().InitCoinsDB(
/* cache_size_bytes */ 1 << 23, /* in_memory */ true, /* should_wipe */ false); /* cache_size_bytes */ 1 << 23, /* in_memory */ true, /* should_wipe */ false);
assert(!::ChainstateActive().CanFlushToDisk()); assert(!::ChainstateActive().CanFlushToDisk());
::ChainstateActive().InitCoinsCache(); ::ChainstateActive().InitCoinsCache(1 << 23);
assert(::ChainstateActive().CanFlushToDisk()); assert(::ChainstateActive().CanFlushToDisk());
if (!LoadGenesisBlock(chainparams)) { if (!LoadGenesisBlock(chainparams)) {
throw std::runtime_error("LoadGenesisBlock failed."); throw std::runtime_error("LoadGenesisBlock failed.");

View file

@ -28,13 +28,11 @@ BOOST_AUTO_TEST_CASE(chainstatemanager)
// Create a legacy (IBD) chainstate. // Create a legacy (IBD) chainstate.
// //
ENTER_CRITICAL_SECTION(cs_main); CChainState& c1 = *WITH_LOCK(::cs_main, return &manager.InitializeChainstate());
CChainState& c1 = manager.InitializeChainstate();
LEAVE_CRITICAL_SECTION(cs_main);
chainstates.push_back(&c1); chainstates.push_back(&c1);
c1.InitCoinsDB( c1.InitCoinsDB(
/* cache_size_bytes */ 1 << 23, /* in_memory */ true, /* should_wipe */ false); /* cache_size_bytes */ 1 << 23, /* in_memory */ true, /* should_wipe */ false);
WITH_LOCK(::cs_main, c1.InitCoinsCache()); WITH_LOCK(::cs_main, c1.InitCoinsCache(1 << 23));
BOOST_CHECK(!manager.IsSnapshotActive()); BOOST_CHECK(!manager.IsSnapshotActive());
BOOST_CHECK(!manager.IsSnapshotValidated()); BOOST_CHECK(!manager.IsSnapshotValidated());
@ -57,12 +55,13 @@ BOOST_AUTO_TEST_CASE(chainstatemanager)
// Create a snapshot-based chainstate. // Create a snapshot-based chainstate.
// //
ENTER_CRITICAL_SECTION(cs_main); ENTER_CRITICAL_SECTION(cs_main);
CChainState& c2 = manager.InitializeChainstate(GetRandHash()); CChainState& c2 = *WITH_LOCK(::cs_main,
return &manager.InitializeChainstate(GetRandHash()));
LEAVE_CRITICAL_SECTION(cs_main); LEAVE_CRITICAL_SECTION(cs_main);
chainstates.push_back(&c2); chainstates.push_back(&c2);
c2.InitCoinsDB( c2.InitCoinsDB(
/* cache_size_bytes */ 1 << 23, /* in_memory */ true, /* should_wipe */ false); /* cache_size_bytes */ 1 << 23, /* in_memory */ true, /* should_wipe */ false);
WITH_LOCK(::cs_main, c2.InitCoinsCache()); WITH_LOCK(::cs_main, c2.InitCoinsCache(1 << 23));
// Unlike c1, which doesn't have any blocks. Gets us different tip, height. // Unlike c1, which doesn't have any blocks. Gets us different tip, height.
c2.LoadGenesisBlock(chainparams); c2.LoadGenesisBlock(chainparams);
BlockValidationState _; BlockValidationState _;

View file

@ -21,7 +21,7 @@ BOOST_AUTO_TEST_CASE(getcoinscachesizestate)
BlockManager blockman{}; BlockManager blockman{};
CChainState chainstate{blockman}; CChainState chainstate{blockman};
chainstate.InitCoinsDB(/*cache_size_bytes*/ 1 << 10, /*in_memory*/ true, /*should_wipe*/ false); chainstate.InitCoinsDB(/*cache_size_bytes*/ 1 << 10, /*in_memory*/ true, /*should_wipe*/ false);
WITH_LOCK(::cs_main, chainstate.InitCoinsCache()); WITH_LOCK(::cs_main, chainstate.InitCoinsCache(1 << 10));
CTxMemPool tx_pool{}; CTxMemPool tx_pool{};
constexpr bool is_64_bit = sizeof(void*) == 8; constexpr bool is_64_bit = sizeof(void*) == 8;

View file

@ -135,7 +135,6 @@ bool fPruneMode = false;
bool fRequireStandard = true; bool fRequireStandard = true;
bool fCheckBlockIndex = false; bool fCheckBlockIndex = false;
bool fCheckpointsEnabled = DEFAULT_CHECKPOINTS_ENABLED; bool fCheckpointsEnabled = DEFAULT_CHECKPOINTS_ENABLED;
size_t nCoinCacheUsage = 5000 * 300;
uint64_t nPruneTarget = 0; uint64_t nPruneTarget = 0;
int64_t nMaxTipAge = DEFAULT_MAX_TIP_AGE; int64_t nMaxTipAge = DEFAULT_MAX_TIP_AGE;
@ -1279,9 +1278,10 @@ void CChainState::InitCoinsDB(
leveldb_name, cache_size_bytes, in_memory, should_wipe); leveldb_name, cache_size_bytes, in_memory, should_wipe);
} }
void CChainState::InitCoinsCache() void CChainState::InitCoinsCache(size_t cache_size_bytes)
{ {
assert(m_coins_views != nullptr); assert(m_coins_views != nullptr);
m_coinstip_cache_size_bytes = cache_size_bytes;
m_coins_views->InitCache(); m_coins_views->InitCache();
} }
@ -2228,7 +2228,7 @@ CoinsCacheSizeState CChainState::GetCoinsCacheSizeState(const CTxMemPool& tx_poo
{ {
return this->GetCoinsCacheSizeState( return this->GetCoinsCacheSizeState(
tx_pool, tx_pool,
nCoinCacheUsage, m_coinstip_cache_size_bytes,
gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000); gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000);
} }
@ -4300,7 +4300,7 @@ bool CVerifyDB::VerifyDB(const CChainParams& chainparams, CCoinsView *coinsview,
} }
} }
// check level 3: check for inconsistencies during memory-only disconnect of tip blocks // check level 3: check for inconsistencies during memory-only disconnect of tip blocks
if (nCheckLevel >= 3 && (coins.DynamicMemoryUsage() + ::ChainstateActive().CoinsTip().DynamicMemoryUsage()) <= nCoinCacheUsage) { if (nCheckLevel >= 3 && (coins.DynamicMemoryUsage() + ::ChainstateActive().CoinsTip().DynamicMemoryUsage()) <= ::ChainstateActive().m_coinstip_cache_size_bytes) {
assert(coins.GetBestBlock() == pindex->GetBlockHash()); assert(coins.GetBestBlock() == pindex->GetBlockHash());
DisconnectResult res = ::ChainstateActive().DisconnectBlock(block, pindex, coins); DisconnectResult res = ::ChainstateActive().DisconnectBlock(block, pindex, coins);
if (res == DISCONNECT_FAILED) { if (res == DISCONNECT_FAILED) {
@ -4965,6 +4965,39 @@ std::string CChainState::ToString()
tip ? tip->nHeight : -1, tip ? tip->GetBlockHash().ToString() : "null"); tip ? tip->nHeight : -1, tip ? tip->GetBlockHash().ToString() : "null");
} }
bool CChainState::ResizeCoinsCaches(size_t coinstip_size, size_t coinsdb_size)
{
if (coinstip_size == m_coinstip_cache_size_bytes &&
coinsdb_size == m_coinsdb_cache_size_bytes) {
// Cache sizes are unchanged, no need to continue.
return true;
}
size_t old_coinstip_size = m_coinstip_cache_size_bytes;
m_coinstip_cache_size_bytes = coinstip_size;
m_coinsdb_cache_size_bytes = coinsdb_size;
CoinsDB().ResizeCache(coinsdb_size);
LogPrintf("[%s] resized coinsdb cache to %.1f MiB\n",
this->ToString(), coinsdb_size * (1.0 / 1024 / 1024));
LogPrintf("[%s] resized coinstip cache to %.1f MiB\n",
this->ToString(), coinstip_size * (1.0 / 1024 / 1024));
BlockValidationState state;
const CChainParams& chainparams = Params();
bool ret;
if (coinstip_size > old_coinstip_size) {
// Likely no need to flush if cache sizes have grown.
ret = FlushStateToDisk(chainparams, state, FlushStateMode::IF_NEEDED);
} else {
// Otherwise, flush state to disk and deallocate the in-memory coins map.
ret = FlushStateToDisk(chainparams, state, FlushStateMode::ALWAYS);
CoinsTip().ReallocateCache();
}
return ret;
}
std::string CBlockFileInfo::ToString() const std::string CBlockFileInfo::ToString() const
{ {
return strprintf("CBlockFileInfo(blocks=%u, size=%u, heights=%u...%u, time=%s...%s)", nBlocks, nSize, nHeightFirst, nHeightLast, FormatISO8601Date(nTimeFirst), FormatISO8601Date(nTimeLast)); return strprintf("CBlockFileInfo(blocks=%u, size=%u, heights=%u...%u, time=%s...%s)", nBlocks, nSize, nHeightFirst, nHeightLast, FormatISO8601Date(nTimeFirst), FormatISO8601Date(nTimeLast));

View file

@ -127,7 +127,6 @@ extern bool g_parallel_script_checks;
extern bool fRequireStandard; extern bool fRequireStandard;
extern bool fCheckBlockIndex; extern bool fCheckBlockIndex;
extern bool fCheckpointsEnabled; extern bool fCheckpointsEnabled;
extern size_t nCoinCacheUsage;
/** A fee rate smaller than this is considered zero fee (for relaying, mining and transaction creation) */ /** A fee rate smaller than this is considered zero fee (for relaying, mining and transaction creation) */
extern CFeeRate minRelayTxFee; extern CFeeRate minRelayTxFee;
/** If the tip is older than this (in seconds), the node is considered to be in initial block download. */ /** If the tip is older than this (in seconds), the node is considered to be in initial block download. */
@ -519,7 +518,7 @@ public:
//! Initialize the in-memory coins cache (to be done after the health of the on-disk database //! Initialize the in-memory coins cache (to be done after the health of the on-disk database
//! is verified). //! is verified).
void InitCoinsCache() EXCLUSIVE_LOCKS_REQUIRED(::cs_main); void InitCoinsCache(size_t cache_size_bytes) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
//! @returns whether or not the CoinsViews object has been fully initialized and we can //! @returns whether or not the CoinsViews object has been fully initialized and we can
//! safely flush this object to disk. //! safely flush this object to disk.
@ -568,6 +567,17 @@ public:
//! Destructs all objects related to accessing the UTXO set. //! Destructs all objects related to accessing the UTXO set.
void ResetCoinsViews() { m_coins_views.reset(); } void ResetCoinsViews() { m_coins_views.reset(); }
//! The cache size of the on-disk coins view.
size_t m_coinsdb_cache_size_bytes{0};
//! The cache size of the in-memory coins view.
size_t m_coinstip_cache_size_bytes{0};
//! Resize the CoinsViews caches dynamically and flush state to disk.
//! @returns true unless an error occurred during the flush.
bool ResizeCoinsCaches(size_t coinstip_size, size_t coinsdb_size)
EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
/** /**
* Update the on-disk chain state. * Update the on-disk chain state.
* The caches and indexes are flushed depending on the mode we're called with * The caches and indexes are flushed depending on the mode we're called with