Skip to content

Commit 82903a7

Browse files
committed
Merge bitcoin/bitcoin#17487: coins: allow write to disk without cache drop
1d7935b test: add test for coins view flush behavior using Sync() (James O'Beirne) 2c3cbd6 test: add use of Sync() to coins tests (James O'Beirne) 6d8affc test: refactor: clarify the coins simulation (James O'Beirne) 79cedc3 coins: add Sync() method to allow flush without cacheCoins drop (James O'Beirne) Pull request description: This is part of the [assumeutxo project](https://github.com/bitcoin/bitcoin/projects/11): Parent PR: #15606 Issue: #15605 Specification: https://github.com/jamesob/assumeutxo-docs/tree/master/proposal --- In certain circumstances, we may want to flush chainstate data to disk without emptying `cacheCoins`, which affects performance. UTXO snapshot activation is one such case, as we populate `cacheCoins` with the snapshot contents and want to persist immediately afterwards but also enter IBD. See also #15265, which makes the case that under normal operation a flush-without-erase doesn't necessarily add much benefit. I open this PR even in light of the previous discussion because (i) flush-without-erase almost certainly provides benefit in the case of snapshot activation (especially on spinning disk hardware) and (ii) this diff is fairly small and gives us convenient options for more granular cache management without changing existing policy. See also #15218. ACKs for top commit: sipa: ACK 1d7935b achow101: ACK 1d7935b Sjors: tACK 1d7935b Tree-SHA512: 897583963e98661767d2d09c9a22f6019da24125558cd88770bfe2d017d924f23a9075b729e4b1febdec5b0709a38e8fa1ef94d62aa88650556b06cb4826c845
2 parents 0a1d372 + 1d7935b commit 82903a7

File tree

5 files changed

+288
-28
lines changed

5 files changed

+288
-28
lines changed

src/coins.cpp

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
bool CCoinsView::GetCoin(const COutPoint &outpoint, Coin &coin) const { return false; }
1414
uint256 CCoinsView::GetBestBlock() const { return uint256(); }
1515
std::vector<uint256> CCoinsView::GetHeadBlocks() const { return std::vector<uint256>(); }
16-
bool CCoinsView::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) { return false; }
16+
bool CCoinsView::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock, bool erase) { return false; }
1717
std::unique_ptr<CCoinsViewCursor> CCoinsView::Cursor() const { return nullptr; }
1818

1919
bool CCoinsView::HaveCoin(const COutPoint &outpoint) const
@@ -28,7 +28,7 @@ bool CCoinsViewBacked::HaveCoin(const COutPoint &outpoint) const { return base->
2828
uint256 CCoinsViewBacked::GetBestBlock() const { return base->GetBestBlock(); }
2929
std::vector<uint256> CCoinsViewBacked::GetHeadBlocks() const { return base->GetHeadBlocks(); }
3030
void CCoinsViewBacked::SetBackend(CCoinsView &viewIn) { base = &viewIn; }
31-
bool CCoinsViewBacked::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) { return base->BatchWrite(mapCoins, hashBlock); }
31+
bool CCoinsViewBacked::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock, bool erase) { return base->BatchWrite(mapCoins, hashBlock, erase); }
3232
std::unique_ptr<CCoinsViewCursor> CCoinsViewBacked::Cursor() const { return base->Cursor(); }
3333
size_t CCoinsViewBacked::EstimateSize() const { return base->EstimateSize(); }
3434

@@ -176,8 +176,10 @@ void CCoinsViewCache::SetBestBlock(const uint256 &hashBlockIn) {
176176
hashBlock = hashBlockIn;
177177
}
178178

179-
bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlockIn) {
180-
for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end(); it = mapCoins.erase(it)) {
179+
bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlockIn, bool erase) {
180+
for (CCoinsMap::iterator it = mapCoins.begin();
181+
it != mapCoins.end();
182+
it = erase ? mapCoins.erase(it) : std::next(it)) {
181183
// Ignore non-dirty entries (optimization).
182184
if (!(it->second.flags & CCoinsCacheEntry::DIRTY)) {
183185
continue;
@@ -190,7 +192,14 @@ bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlockIn
190192
// Create the coin in the parent cache, move the data up
191193
// and mark it as dirty.
192194
CCoinsCacheEntry& entry = cacheCoins[it->first];
193-
entry.coin = std::move(it->second.coin);
195+
if (erase) {
196+
// The `move` call here is purely an optimization; we rely on the
197+
// `mapCoins.erase` call in the `for` expression to actually remove
198+
// the entry from the child map.
199+
entry.coin = std::move(it->second.coin);
200+
} else {
201+
entry.coin = it->second.coin;
202+
}
194203
cachedCoinsUsage += entry.coin.DynamicMemoryUsage();
195204
entry.flags = CCoinsCacheEntry::DIRTY;
196205
// We can mark it FRESH in the parent if it was FRESH in the child
@@ -218,7 +227,14 @@ bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlockIn
218227
} else {
219228
// A normal modification.
220229
cachedCoinsUsage -= itUs->second.coin.DynamicMemoryUsage();
221-
itUs->second.coin = std::move(it->second.coin);
230+
if (erase) {
231+
// The `move` call here is purely an optimization; we rely on the
232+
// `mapCoins.erase` call in the `for` expression to actually remove
233+
// the entry from the child map.
234+
itUs->second.coin = std::move(it->second.coin);
235+
} else {
236+
itUs->second.coin = it->second.coin;
237+
}
222238
cachedCoinsUsage += itUs->second.coin.DynamicMemoryUsage();
223239
itUs->second.flags |= CCoinsCacheEntry::DIRTY;
224240
// NOTE: It isn't safe to mark the coin as FRESH in the parent
@@ -233,12 +249,29 @@ bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlockIn
233249
}
234250

235251
bool CCoinsViewCache::Flush() {
236-
bool fOk = base->BatchWrite(cacheCoins, hashBlock);
252+
bool fOk = base->BatchWrite(cacheCoins, hashBlock, /*erase=*/ true);
237253
cacheCoins.clear();
238254
cachedCoinsUsage = 0;
239255
return fOk;
240256
}
241257

258+
bool CCoinsViewCache::Sync()
259+
{
260+
bool fOk = base->BatchWrite(cacheCoins, hashBlock, /*erase=*/ false);
261+
// Instead of clearing `cacheCoins` as we would in Flush(), just clear the
262+
// FRESH/DIRTY flags of any coin that isn't spent.
263+
for (auto it = cacheCoins.begin(); it != cacheCoins.end(); ) {
264+
if (it->second.coin.IsSpent()) {
265+
cachedCoinsUsage -= it->second.coin.DynamicMemoryUsage();
266+
it = cacheCoins.erase(it);
267+
} else {
268+
it->second.flags = 0;
269+
++it;
270+
}
271+
}
272+
return fOk;
273+
}
274+
242275
void CCoinsViewCache::Uncache(const COutPoint& hash)
243276
{
244277
CCoinsMap::iterator it = cacheCoins.find(hash);

src/coins.h

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ class CCoinsView
176176

177177
//! Do a bulk modification (multiple Coin changes + BestBlock change).
178178
//! The passed mapCoins can be modified.
179-
virtual bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock);
179+
virtual bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock, bool erase = true);
180180

181181
//! Get a cursor to iterate over the whole state
182182
virtual std::unique_ptr<CCoinsViewCursor> Cursor() const;
@@ -202,7 +202,7 @@ class CCoinsViewBacked : public CCoinsView
202202
uint256 GetBestBlock() const override;
203203
std::vector<uint256> GetHeadBlocks() const override;
204204
void SetBackend(CCoinsView &viewIn);
205-
bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) override;
205+
bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock, bool erase = true) override;
206206
std::unique_ptr<CCoinsViewCursor> Cursor() const override;
207207
size_t EstimateSize() const override;
208208
};
@@ -235,7 +235,7 @@ class CCoinsViewCache : public CCoinsViewBacked
235235
bool HaveCoin(const COutPoint &outpoint) const override;
236236
uint256 GetBestBlock() const override;
237237
void SetBestBlock(const uint256 &hashBlock);
238-
bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) override;
238+
bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock, bool erase = true) override;
239239
std::unique_ptr<CCoinsViewCursor> Cursor() const override {
240240
throw std::logic_error("CCoinsViewCache cursor iteration not supported.");
241241
}
@@ -282,12 +282,22 @@ class CCoinsViewCache : public CCoinsViewBacked
282282
bool SpendCoin(const COutPoint &outpoint, Coin* moveto = nullptr);
283283

284284
/**
285-
* Push the modifications applied to this cache to its base.
286-
* Failure to call this method before destruction will cause the changes to be forgotten.
285+
* Push the modifications applied to this cache to its base and wipe local state.
286+
* Failure to call this method or Sync() before destruction will cause the changes
287+
* to be forgotten.
287288
* If false is returned, the state of this cache (and its backing view) will be undefined.
288289
*/
289290
bool Flush();
290291

292+
/**
293+
* Push the modifications applied to this cache to its base while retaining
294+
* the contents of this cache (except for spent coins, which we erase).
295+
* Failure to call this method or Flush() before destruction will cause the changes
296+
* to be forgotten.
297+
* If false is returned, the state of this cache (and its backing view) will be undefined.
298+
*/
299+
bool Sync();
300+
291301
/**
292302
* Removes the UTXO with the given outpoint from the cache, if it is
293303
* not modified.

0 commit comments

Comments
 (0)