Skip to content

Commit f36aaa6

Browse files
committed
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.
1 parent b223111 commit f36aaa6

File tree

8 files changed

+73
-16
lines changed

8 files changed

+73
-16
lines changed

src/coins.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,14 @@ bool CCoinsViewCache::HaveInputs(const CTransaction& tx) const
245245
return true;
246246
}
247247

248+
void CCoinsViewCache::ReallocateCache()
249+
{
250+
// Cache should be empty when we're calling this.
251+
assert(cacheCoins.size() == 0);
252+
cacheCoins.~CCoinsMap();
253+
::new (&cacheCoins) CCoinsMap();
254+
}
255+
248256
static const size_t MIN_TRANSACTION_OUTPUT_WEIGHT = WITNESS_SCALE_FACTOR * ::GetSerializeSize(CTxOut(), PROTOCOL_VERSION);
249257
static const size_t MAX_OUTPUTS_PER_BLOCK = MAX_BLOCK_WEIGHT / MIN_TRANSACTION_OUTPUT_WEIGHT;
250258

src/coins.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,13 @@ class CCoinsViewCache : public CCoinsViewBacked
318318
//! Check whether all prevouts of the transaction are present in the UTXO set represented by this view
319319
bool HaveInputs(const CTransaction& tx) const;
320320

321+
//! Force a reallocation of the cache map. This is required when downsizing
322+
//! the cache because the map's allocator may be hanging onto a lot of
323+
//! memory despite having called .clear().
324+
//!
325+
//! See: https://stackoverflow.com/questions/42114044/how-to-release-unordered-map-memory
326+
void ReallocateCache();
327+
321328
private:
322329
/**
323330
* @note this is marked const, but may actually append to `cacheCoins`, increasing

src/init.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1533,7 +1533,7 @@ bool AppInitMain(const util::Ref& context, NodeContext& node)
15331533
int64_t nCoinDBCache = std::min(nTotalCache / 2, (nTotalCache / 4) + (1 << 23)); // use 25%-50% of the remainder for disk cache
15341534
nCoinDBCache = std::min(nCoinDBCache, nMaxCoinsDBCache << 20); // cap total coins db cache
15351535
nTotalCache -= nCoinDBCache;
1536-
nCoinCacheUsage = nTotalCache; // the rest goes to in-memory cache
1536+
int64_t nCoinCacheUsage = nTotalCache; // the rest goes to in-memory cache
15371537
int64_t nMempoolSizeMax = gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000;
15381538
LogPrintf("Cache configuration:\n");
15391539
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)
16451645
}
16461646

16471647
// The on-disk coinsdb is now in a good state, create the cache
1648-
chainstate->InitCoinsCache();
1648+
chainstate->InitCoinsCache(nCoinCacheUsage);
16491649
assert(chainstate->CanFlushToDisk());
16501650

16511651
if (!is_coinsview_empty(chainstate)) {

src/test/util/setup_common.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ TestingSetup::TestingSetup(const std::string& chainName, const std::vector<const
139139
::ChainstateActive().InitCoinsDB(
140140
/* cache_size_bytes */ 1 << 23, /* in_memory */ true, /* should_wipe */ false);
141141
assert(!::ChainstateActive().CanFlushToDisk());
142-
::ChainstateActive().InitCoinsCache();
142+
::ChainstateActive().InitCoinsCache(1 << 23);
143143
assert(::ChainstateActive().CanFlushToDisk());
144144
if (!LoadGenesisBlock(chainparams)) {
145145
throw std::runtime_error("LoadGenesisBlock failed.");

src/test/validation_chainstatemanager_tests.cpp

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,11 @@ BOOST_AUTO_TEST_CASE(chainstatemanager)
2828

2929
// Create a legacy (IBD) chainstate.
3030
//
31-
ENTER_CRITICAL_SECTION(cs_main);
32-
CChainState& c1 = manager.InitializeChainstate();
33-
LEAVE_CRITICAL_SECTION(cs_main);
31+
CChainState& c1 = *WITH_LOCK(::cs_main, return &manager.InitializeChainstate());
3432
chainstates.push_back(&c1);
3533
c1.InitCoinsDB(
3634
/* cache_size_bytes */ 1 << 23, /* in_memory */ true, /* should_wipe */ false);
37-
WITH_LOCK(::cs_main, c1.InitCoinsCache());
35+
WITH_LOCK(::cs_main, c1.InitCoinsCache(1 << 23));
3836

3937
BOOST_CHECK(!manager.IsSnapshotActive());
4038
BOOST_CHECK(!manager.IsSnapshotValidated());
@@ -57,12 +55,13 @@ BOOST_AUTO_TEST_CASE(chainstatemanager)
5755
// Create a snapshot-based chainstate.
5856
//
5957
ENTER_CRITICAL_SECTION(cs_main);
60-
CChainState& c2 = manager.InitializeChainstate(GetRandHash());
58+
CChainState& c2 = *WITH_LOCK(::cs_main,
59+
return &manager.InitializeChainstate(GetRandHash()));
6160
LEAVE_CRITICAL_SECTION(cs_main);
6261
chainstates.push_back(&c2);
6362
c2.InitCoinsDB(
6463
/* cache_size_bytes */ 1 << 23, /* in_memory */ true, /* should_wipe */ false);
65-
WITH_LOCK(::cs_main, c2.InitCoinsCache());
64+
WITH_LOCK(::cs_main, c2.InitCoinsCache(1 << 23));
6665
// Unlike c1, which doesn't have any blocks. Gets us different tip, height.
6766
c2.LoadGenesisBlock(chainparams);
6867
BlockValidationState _;

src/test/validation_flush_tests.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ BOOST_AUTO_TEST_CASE(getcoinscachesizestate)
2121
BlockManager blockman{};
2222
CChainState chainstate{blockman};
2323
chainstate.InitCoinsDB(/*cache_size_bytes*/ 1 << 10, /*in_memory*/ true, /*should_wipe*/ false);
24-
WITH_LOCK(::cs_main, chainstate.InitCoinsCache());
24+
WITH_LOCK(::cs_main, chainstate.InitCoinsCache(1 << 10));
2525
CTxMemPool tx_pool{};
2626

2727
constexpr bool is_64_bit = sizeof(void*) == 8;

src/validation.cpp

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,6 @@ bool fPruneMode = false;
135135
bool fRequireStandard = true;
136136
bool fCheckBlockIndex = false;
137137
bool fCheckpointsEnabled = DEFAULT_CHECKPOINTS_ENABLED;
138-
size_t nCoinCacheUsage = 5000 * 300;
139138
uint64_t nPruneTarget = 0;
140139
int64_t nMaxTipAge = DEFAULT_MAX_TIP_AGE;
141140

@@ -1279,9 +1278,10 @@ void CChainState::InitCoinsDB(
12791278
leveldb_name, cache_size_bytes, in_memory, should_wipe);
12801279
}
12811280

1282-
void CChainState::InitCoinsCache()
1281+
void CChainState::InitCoinsCache(size_t cache_size_bytes)
12831282
{
12841283
assert(m_coins_views != nullptr);
1284+
m_coinstip_cache_size_bytes = cache_size_bytes;
12851285
m_coins_views->InitCache();
12861286
}
12871287

@@ -2228,7 +2228,7 @@ CoinsCacheSizeState CChainState::GetCoinsCacheSizeState(const CTxMemPool& tx_poo
22282228
{
22292229
return this->GetCoinsCacheSizeState(
22302230
tx_pool,
2231-
nCoinCacheUsage,
2231+
m_coinstip_cache_size_bytes,
22322232
gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000);
22332233
}
22342234

@@ -4300,7 +4300,7 @@ bool CVerifyDB::VerifyDB(const CChainParams& chainparams, CCoinsView *coinsview,
43004300
}
43014301
}
43024302
// check level 3: check for inconsistencies during memory-only disconnect of tip blocks
4303-
if (nCheckLevel >= 3 && (coins.DynamicMemoryUsage() + ::ChainstateActive().CoinsTip().DynamicMemoryUsage()) <= nCoinCacheUsage) {
4303+
if (nCheckLevel >= 3 && (coins.DynamicMemoryUsage() + ::ChainstateActive().CoinsTip().DynamicMemoryUsage()) <= ::ChainstateActive().m_coinstip_cache_size_bytes) {
43044304
assert(coins.GetBestBlock() == pindex->GetBlockHash());
43054305
DisconnectResult res = ::ChainstateActive().DisconnectBlock(block, pindex, coins);
43064306
if (res == DISCONNECT_FAILED) {
@@ -4965,6 +4965,39 @@ std::string CChainState::ToString()
49654965
tip ? tip->nHeight : -1, tip ? tip->GetBlockHash().ToString() : "null");
49664966
}
49674967

4968+
bool CChainState::ResizeCoinsCaches(size_t coinstip_size, size_t coinsdb_size)
4969+
{
4970+
if (coinstip_size == m_coinstip_cache_size_bytes &&
4971+
coinsdb_size == m_coinsdb_cache_size_bytes) {
4972+
// Cache sizes are unchanged, no need to continue.
4973+
return true;
4974+
}
4975+
size_t old_coinstip_size = m_coinstip_cache_size_bytes;
4976+
m_coinstip_cache_size_bytes = coinstip_size;
4977+
m_coinsdb_cache_size_bytes = coinsdb_size;
4978+
CoinsDB().ResizeCache(coinsdb_size);
4979+
4980+
LogPrintf("[%s] resized coinsdb cache to %.1f MiB\n",
4981+
this->ToString(), coinsdb_size * (1.0 / 1024 / 1024));
4982+
LogPrintf("[%s] resized coinstip cache to %.1f MiB\n",
4983+
this->ToString(), coinstip_size * (1.0 / 1024 / 1024));
4984+
4985+
BlockValidationState state;
4986+
const CChainParams& chainparams = Params();
4987+
4988+
bool ret;
4989+
4990+
if (coinstip_size > old_coinstip_size) {
4991+
// Likely no need to flush if cache sizes have grown.
4992+
ret = FlushStateToDisk(chainparams, state, FlushStateMode::IF_NEEDED);
4993+
} else {
4994+
// Otherwise, flush state to disk and deallocate the in-memory coins map.
4995+
ret = FlushStateToDisk(chainparams, state, FlushStateMode::ALWAYS);
4996+
CoinsTip().ReallocateCache();
4997+
}
4998+
return ret;
4999+
}
5000+
49685001
std::string CBlockFileInfo::ToString() const
49695002
{
49705003
return strprintf("CBlockFileInfo(blocks=%u, size=%u, heights=%u...%u, time=%s...%s)", nBlocks, nSize, nHeightFirst, nHeightLast, FormatISO8601Date(nTimeFirst), FormatISO8601Date(nTimeLast));

src/validation.h

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,6 @@ extern bool g_parallel_script_checks;
127127
extern bool fRequireStandard;
128128
extern bool fCheckBlockIndex;
129129
extern bool fCheckpointsEnabled;
130-
extern size_t nCoinCacheUsage;
131130
/** A fee rate smaller than this is considered zero fee (for relaying, mining and transaction creation) */
132131
extern CFeeRate minRelayTxFee;
133132
/** If the tip is older than this (in seconds), the node is considered to be in initial block download. */
@@ -519,7 +518,7 @@ class CChainState {
519518

520519
//! Initialize the in-memory coins cache (to be done after the health of the on-disk database
521520
//! is verified).
522-
void InitCoinsCache() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
521+
void InitCoinsCache(size_t cache_size_bytes) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
523522

524523
//! @returns whether or not the CoinsViews object has been fully initialized and we can
525524
//! safely flush this object to disk.
@@ -568,6 +567,17 @@ class CChainState {
568567
//! Destructs all objects related to accessing the UTXO set.
569568
void ResetCoinsViews() { m_coins_views.reset(); }
570569

570+
//! The cache size of the on-disk coins view.
571+
size_t m_coinsdb_cache_size_bytes{0};
572+
573+
//! The cache size of the in-memory coins view.
574+
size_t m_coinstip_cache_size_bytes{0};
575+
576+
//! Resize the CoinsViews caches dynamically and flush state to disk.
577+
//! @returns true unless an error occurred during the flush.
578+
bool ResizeCoinsCaches(size_t coinstip_size, size_t coinsdb_size)
579+
EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
580+
571581
/**
572582
* Update the on-disk chain state.
573583
* The caches and indexes are flushed depending on the mode we're called with

0 commit comments

Comments
 (0)