Skip to content

Commit c69069e

Browse files
authored
Cleanup how restoredKeys are tracked in LedgerTxn (#5134)
# Description Cleanup how restoredKeys are tracked in LedgerTxn <!--- Describe what this pull request does, which issue it's resolving (usually applicable for code changes). ---> # Checklist - [ ] Reviewed the [contributing](https://github.com/stellar/stellar-core/blob/master/CONTRIBUTING.md#submitting-changes) document - [ ] Rebased on top of master (no merge commits) - [ ] Ran `clang-format` v8.0.0 (via `make format` or the Visual Studio extension) - [ ] Compiles - [ ] Ran all tests - [ ] If change impacts performance, include supporting evidence per the [performance document](https://github.com/stellar/stellar-core/blob/master/performance-eval/performance-eval.md)
2 parents 4318e17 + 7b9c954 commit c69069e

9 files changed

Lines changed: 189 additions & 78 deletions

File tree

src/invariant/ArchivedStateConsistency.cpp

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -428,7 +428,13 @@ ArchivedStateConsistency::checkRestoreInvariants(
428428
"in live state: {}"),
429429
xdrToCerealString(key, "key"));
430430
}
431-
else if (liveEntry->second != entry)
431+
// For non-TTL entries, `entry` (the restored value) should be
432+
// identical to `liveEntry->second` (the value on the live BucketList)
433+
// since data/code entries are not modified during a restore. TTL
434+
// entries are excluded from this check because restoration updates the
435+
// TTL's liveUntilLedgerSeq, so the restored value will differ from the
436+
// on-disk value.
437+
else if (key.type() != TTL && liveEntry->second != entry)
432438
{
433439
return fmt::format(
434440
FMT_STRING("ArchivedStateConsistency invariant failed: "
@@ -438,14 +444,28 @@ ArchivedStateConsistency::checkRestoreInvariants(
438444
xdrToCerealString(entry, "entry_to_restore"));
439445
}
440446

441-
if (key.type() == TTL && isLive(entry, ledgerSeq))
447+
if (key.type() == TTL)
442448
{
443-
return fmt::format(
444-
FMT_STRING("ArchivedStateConsistency invariant failed: "
445-
"Restored entry from live BucketList is not "
446-
"expired: Entry: {}, TTL Entry: {}"),
447-
xdrToCerealString(entry, "entry"),
448-
xdrToCerealString(entry, "ttl_entry"));
449+
// `entry` is the TTL after restoration (with updated
450+
// liveUntilLedgerSeq). `liveEntry->second` is the original
451+
// on-disk TTL before restoration. We check that the restored
452+
// TTL is now live and the original was expired.
453+
if (!isLive(entry, ledgerSeq))
454+
{
455+
return fmt::format(
456+
FMT_STRING("ArchivedStateConsistency invariant failed: "
457+
"Restored entry's updated TTL is still "
458+
"expired: TTL Entry: {}"),
459+
xdrToCerealString(entry, "ttl_entry"));
460+
}
461+
if (isLive(liveEntry->second, ledgerSeq))
462+
{
463+
return fmt::format(
464+
FMT_STRING("ArchivedStateConsistency invariant failed: "
465+
"Restored entry from live BucketList is not "
466+
"expired: TTL Entry: {}"),
467+
xdrToCerealString(liveEntry->second, "ttl_entry"));
468+
}
449469
}
450470
}
451471

src/ledger/LedgerTxn.cpp

Lines changed: 46 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -251,11 +251,10 @@ RestoredEntries::addLiveBucketlistRestore(LedgerKey const& key,
251251
}
252252

253253
void
254-
RestoredEntries::addRestoresFrom(RestoredEntries const& other,
255-
bool allowDuplicates)
254+
RestoredEntries::addRestoresFrom(RestoredEntries const& other)
256255
{
257256
ZoneScoped;
258-
// This method is called from three different call sites. In 2 of them it is
257+
// This method is called from three different call sites. In all three it is
259258
// correct to assert that each restore is new/disjoint from any existing
260259
// restore:
261260
//
@@ -272,19 +271,19 @@ RestoredEntries::addRestoresFrom(RestoredEntries const& other,
272271
// entry -- it'd be a concurrency bug if not! -- so there should not be
273272
// any other restores of the same entry from other threads.
274273
//
275-
// In the third place we're committing from an ltx to its parent, and the
276-
// ltx was actually starting with a copy of the restored-maps from the
277-
// parent, so there are going to be duplicates. We allow duplicates in that
278-
// case.
279-
for (auto kvp : other.hotArchive)
274+
// - In the third call site we're committing from a child ltx to its
275+
// parent. Since child LedgerTxns only track their own restores (they
276+
// do not start with a copy of the parent's restored entries), there
277+
// should be no duplicates.
278+
for (auto const& kvp : other.hotArchive)
280279
{
281280
auto [_, inserted] = hotArchive.emplace(kvp.first, kvp.second);
282-
releaseAssert(inserted || allowDuplicates);
281+
releaseAssert(inserted);
283282
}
284-
for (auto kvp : other.liveBucketList)
283+
for (auto const& kvp : other.liveBucketList)
285284
{
286285
auto [_, inserted] = liveBucketList.emplace(kvp.first, kvp.second);
287-
releaseAssert(inserted || allowDuplicates);
286+
releaseAssert(inserted);
288287
}
289288
}
290289

@@ -435,15 +434,6 @@ LedgerTxn::Impl::Impl(LedgerTxn& self, AbstractLedgerTxnParent& parent,
435434
, mConsistency(LedgerTxnConsistency::EXACT)
436435
, mActiveThreadId(std::this_thread::get_id())
437436
{
438-
for (auto const& [key, entry] : mParent.getRestoredHotArchiveKeys())
439-
{
440-
mRestoredEntries.hotArchive.emplace(key, entry);
441-
}
442-
for (auto const& [key, entry] : mParent.getRestoredLiveBucketListKeys())
443-
{
444-
mRestoredEntries.liveBucketList.emplace(key, entry);
445-
}
446-
447437
mParent.addChild(self, mode);
448438
}
449439

@@ -703,10 +693,7 @@ LedgerTxn::Impl::commitChild(EntryIterator iter,
703693
printErrorAndAbort("unknown fatal error during commit to LedgerTxn");
704694
}
705695

706-
// The child will have started with a copy of the parents mRestoredEntries,
707-
// so we can see duplicates here, but duplicate restores would've been
708-
// caught during restoration in the restoreFrom* functions.
709-
mRestoredEntries.addRestoresFrom(restoredEntries, /*allowDuplicates=*/true);
696+
mRestoredEntries.addRestoresFrom(restoredEntries);
710697

711698
// std::unique_ptr<...>::swap does not throw
712699
mHeader.swap(childHeader);
@@ -915,6 +902,41 @@ LedgerTxn::Impl::markRestoredFromHotArchive(LedgerEntry const& ledgerEntry,
915902
addKey(ttlEntry);
916903
}
917904

905+
void
906+
LedgerTxn::markRestoredFromLiveBucketList(LedgerEntry const& ledgerEntry,
907+
LedgerEntry const& ttlEntry)
908+
{
909+
getImpl()->markRestoredFromLiveBucketList(ledgerEntry, ttlEntry);
910+
}
911+
912+
void
913+
LedgerTxn::Impl::markRestoredFromLiveBucketList(LedgerEntry const& ledgerEntry,
914+
LedgerEntry const& ttlEntry)
915+
{
916+
abortIfWrongThread("markRestoredFromLiveBucketList");
917+
throwIfSealed();
918+
throwIfChild();
919+
920+
if (!isPersistentEntry(ledgerEntry.data))
921+
{
922+
throw std::runtime_error(
923+
"Key type not supported for live BucketList restore");
924+
}
925+
926+
// Mark the keys as restored
927+
auto addKey = [this](LedgerEntry const& entry) {
928+
auto [_, inserted] = mRestoredEntries.liveBucketList.emplace(
929+
LedgerEntryKey(entry), entry);
930+
if (!inserted)
931+
{
932+
throw std::runtime_error(
933+
"Key already restored from Live BucketList");
934+
}
935+
};
936+
addKey(ledgerEntry);
937+
addKey(ttlEntry);
938+
}
939+
918940
LedgerTxnEntry
919941
LedgerTxn::restoreFromLiveBucketList(LedgerEntry const& entry, uint32_t ttl)
920942
{

src/ledger/LedgerTxn.h

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -359,8 +359,7 @@ struct RestoredEntries
359359
LedgerEntry const& entry,
360360
LedgerKey const& ttlKey,
361361
LedgerEntry const& ttlEntry);
362-
void addRestoresFrom(RestoredEntries const& other,
363-
bool allowDuplicates = false);
362+
void addRestoresFrom(RestoredEntries const& other);
364363
};
365364

366365
class AbstractLedgerTxn;
@@ -617,6 +616,13 @@ class AbstractLedgerTxn : public AbstractLedgerTxnParent
617616
// restored. This just adds the information to the map tracking entries
618617
// restored from the hot archive. The actual restoration of the entry is
619618
// handled separately.
619+
// - markRestoredFromLiveBucketList:
620+
// Indicates that an entry in the live BucketList is being restored.
621+
// Used by the parallel apply path to signal to LedgerTxn that the
622+
// entry and TTL should be treated as if they have been restored. This
623+
// just adds the information to the map tracking entries restored from
624+
// the live BucketList. The actual restoration of the entry is handled
625+
// separately.
620626
// All of these functions throw if the AbstractLedgerTxn is sealed or if
621627
// the AbstractLedgerTxn has a child.
622628
virtual LedgerTxnHeader loadHeader() = 0;
@@ -626,6 +632,9 @@ class AbstractLedgerTxn : public AbstractLedgerTxnParent
626632
uint32_t ttl) = 0;
627633
virtual void markRestoredFromHotArchive(LedgerEntry const& ledgerEntry,
628634
LedgerEntry const& ttlEntry) = 0;
635+
virtual void
636+
markRestoredFromLiveBucketList(LedgerEntry const& ledgerEntry,
637+
LedgerEntry const& ttlEntry) = 0;
629638
virtual LedgerTxnEntry load(InternalLedgerKey const& key) = 0;
630639
virtual ConstLedgerTxnEntry
631640
loadWithoutRecord(InternalLedgerKey const& key) = 0;
@@ -774,6 +783,8 @@ class LedgerTxn : public AbstractLedgerTxn
774783
uint32_t ttl) override;
775784
void markRestoredFromHotArchive(LedgerEntry const& ledgerEntry,
776785
LedgerEntry const& ttlEntry) override;
786+
void markRestoredFromLiveBucketList(LedgerEntry const& ledgerEntry,
787+
LedgerEntry const& ttlEntry) override;
777788

778789
UnorderedMap<LedgerKey, LedgerEntry> getAllOffers() override;
779790

src/ledger/LedgerTxnImpl.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,9 +360,18 @@ class LedgerTxn::Impl
360360

361361
// markRestoredFromHotArchive has the basic exception safety guarantee. If
362362
// it throws an exception, then
363+
// - the restored entries map may contain only a partial record (e.g. the
364+
// data entry without its corresponding TTL entry).
363365
void markRestoredFromHotArchive(LedgerEntry const& ledgerEntry,
364366
LedgerEntry const& ttlEntry);
365367

368+
// markRestoredFromLiveBucketList has the basic exception safety guarantee.
369+
// If it throws an exception, then
370+
// - the restored entries map may contain only a partial record (e.g. the
371+
// data entry without its corresponding TTL entry).
372+
void markRestoredFromLiveBucketList(LedgerEntry const& ledgerEntry,
373+
LedgerEntry const& ttlEntry);
374+
366375
// restoreFromLiveBucketList has the basic exception safety guarantee. If it
367376
// throws an exception, then
368377
LedgerTxnEntry restoreFromLiveBucketList(LedgerTxn& self,

src/ledger/test/InMemoryLedgerTxn.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,14 @@ InMemoryLedgerTxn::restoreFromLiveBucketList(LedgerEntry const& entry,
282282
"called restoreFromLiveBucketList on InMemoryLedgerTxn");
283283
}
284284

285+
void
286+
InMemoryLedgerTxn::markRestoredFromLiveBucketList(
287+
LedgerEntry const& ledgerEntry, LedgerEntry const& ttlEntry)
288+
{
289+
throw std::runtime_error(
290+
"called markRestoredFromLiveBucketList on InMemoryLedgerTxn");
291+
}
292+
285293
LedgerTxnEntry
286294
InMemoryLedgerTxn::load(InternalLedgerKey const& key)
287295
{

src/ledger/test/InMemoryLedgerTxn.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@ class InMemoryLedgerTxn : public LedgerTxn
114114
void erase(InternalLedgerKey const& key) override;
115115
LedgerTxnEntry restoreFromLiveBucketList(LedgerEntry const& entry,
116116
uint32_t ttl) override;
117+
void markRestoredFromLiveBucketList(LedgerEntry const& ledgerEntry,
118+
LedgerEntry const& ttlEntry) override;
117119
LedgerTxnEntry load(InternalLedgerKey const& key) override;
118120
ConstLedgerTxnEntry
119121
loadWithoutRecord(InternalLedgerKey const& key) override;

src/ledger/test/LedgerTxnTests.cpp

Lines changed: 55 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -261,18 +261,17 @@ TEST_CASE("LedgerTxn commit into LedgerTxn", "[ledgertxn]")
261261

262262
SECTION("commited to parent")
263263
{
264+
auto getTTLEntry = [](LedgerEntry const& entry,
265+
uint32_t liveUntilLedgerSeq) -> LedgerEntry {
266+
LedgerEntry ttl;
267+
ttl.data.type(TTL);
268+
ttl.data.ttl().liveUntilLedgerSeq = liveUntilLedgerSeq;
269+
ttl.data.ttl().keyHash = getTTLKey(entry).ttl().keyHash;
270+
return ttl;
271+
};
272+
264273
SECTION("hot archive")
265274
{
266-
auto getTTLEntry =
267-
[](LedgerEntry const& entry,
268-
uint32_t liveUntilLedgerSeq) -> LedgerEntry {
269-
LedgerEntry ttl;
270-
ttl.data.type(TTL);
271-
ttl.data.ttl().liveUntilLedgerSeq = liveUntilLedgerSeq;
272-
ttl.data.ttl().keyHash = getTTLKey(entry).ttl().keyHash;
273-
return ttl;
274-
};
275-
276275
ltx1.markRestoredFromHotArchive(
277276
randomEntries[0], getTTLEntry(randomEntries[0], 42));
278277

@@ -313,9 +312,51 @@ TEST_CASE("LedgerTxn commit into LedgerTxn", "[ledgertxn]")
313312
}
314313
}
315314

315+
SECTION("mark live BL")
316+
{
317+
ltx1.markRestoredFromLiveBucketList(
318+
randomEntries[0], getTTLEntry(randomEntries[0], 42));
319+
320+
SECTION("rollback")
321+
{
322+
{
323+
LedgerTxn ltx2(ltx1);
324+
ltx2.markRestoredFromLiveBucketList(
325+
randomEntries[1],
326+
getTTLEntry(randomEntries[1], 42));
327+
}
328+
329+
REQUIRE(ltx1.getRestoredHotArchiveKeys().empty());
330+
auto keys = ltx1.getRestoredLiveBucketListKeys();
331+
332+
// Data key + TTL
333+
REQUIRE(keys.size() == 2);
334+
checkKey(keys, randomKeys[0]);
335+
}
336+
337+
SECTION("commit")
338+
{
339+
{
340+
LedgerTxn ltx2(ltx1);
341+
ltx2.markRestoredFromLiveBucketList(
342+
randomEntries[1],
343+
getTTLEntry(randomEntries[1], 42));
344+
ltx2.commit();
345+
}
346+
347+
REQUIRE(ltx1.getRestoredHotArchiveKeys().empty());
348+
auto keys = ltx1.getRestoredLiveBucketListKeys();
349+
350+
// (data key + TTL) * 2
351+
REQUIRE(keys.size() == 4);
352+
checkKey(keys, randomKeys[0]);
353+
checkKey(keys, randomKeys[1]);
354+
}
355+
}
356+
316357
SECTION("live BL")
317358
{
318-
auto getTTLEntry = [](LedgerKey const& key) {
359+
auto getTTLEntryFromKey = [](LedgerKey const& key) {
319360
LedgerEntry ttl;
320361
ttl.data.type(TTL);
321362
ttl.data.ttl().liveUntilLedgerSeq = 42;
@@ -325,15 +366,15 @@ TEST_CASE("LedgerTxn commit into LedgerTxn", "[ledgertxn]")
325366

326367
// Populate live BL with key, then restore it
327368
ltx1.create(randomEntries[0]);
328-
ltx1.create(getTTLEntry(randomKeys[0]));
369+
ltx1.create(getTTLEntryFromKey(randomKeys[0]));
329370
ltx1.restoreFromLiveBucketList(randomEntries[0], 42);
330371

331372
SECTION("rollback")
332373
{
333374
{
334375
LedgerTxn ltx2(ltx1);
335376
ltx2.create(randomEntries[1]);
336-
ltx2.create(getTTLEntry(randomKeys[1]));
377+
ltx2.create(getTTLEntryFromKey(randomKeys[1]));
337378
ltx2.restoreFromLiveBucketList(randomEntries[1], 42);
338379
}
339380

@@ -350,7 +391,7 @@ TEST_CASE("LedgerTxn commit into LedgerTxn", "[ledgertxn]")
350391
{
351392
LedgerTxn ltx2(ltx1);
352393
ltx2.create(randomEntries[1]);
353-
ltx2.create(getTTLEntry(randomKeys[1]));
394+
ltx2.create(getTTLEntryFromKey(randomKeys[1]));
354395
ltx2.restoreFromLiveBucketList(randomEntries[1], 42);
355396
ltx2.commit();
356397
}

src/transactions/ParallelApplyUtils.cpp

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -426,7 +426,10 @@ GlobalParallelApplyLedgerState::commitChangesToLedgerTxn(
426426

427427
// While the final state of a restored key that will be written to the
428428
// Live BucketList is already handled in mGlobalEntryMap, we need to
429-
// let the ltx know what keys need to be removed from the Hot Archive.
429+
// let the ltx know what keys were restored so that:
430+
// 1. Hot Archive restores can be removed from the Hot Archive BucketList
431+
// 2. The ArchivedStateConsistency invariant can validate both hot archive
432+
// and live BucketList restores
430433
for (auto const& kvp : mGlobalRestoredEntries.hotArchive)
431434
{
432435
// We will search for the ttl key in the hot archive when the entry
@@ -439,6 +442,20 @@ GlobalParallelApplyLedgerState::commitChangesToLedgerTxn(
439442
ltxInner.markRestoredFromHotArchive(kvp.second, it->second);
440443
}
441444
}
445+
// Live BucketList restores are only tracked in LedgerTxn for the
446+
// ArchivedStateConsistency invariant, but we unconditionally track it for
447+
// now.
448+
for (auto const& kvp : mGlobalRestoredEntries.liveBucketList)
449+
{
450+
if (kvp.first.type() != TTL)
451+
{
452+
auto it = mGlobalRestoredEntries.liveBucketList.find(
453+
getTTLKey(kvp.first));
454+
releaseAssertOrThrow(it !=
455+
mGlobalRestoredEntries.liveBucketList.end());
456+
ltxInner.markRestoredFromLiveBucketList(kvp.second, it->second);
457+
}
458+
}
442459
ltxInner.commit();
443460
}
444461

0 commit comments

Comments
 (0)