Skip to content

Commit 4318e17

Browse files
authored
Make parallel apply compatible with RUN_STANDALONE (#5146)
# Description This change makes RUN_STANDALONE compatible with parallel ledger apply. There are a few motivations for this. First, many tests enable `RUN_STANDALONE`, so we silently disable parallel apply in many unit tests. This change increases test coverage (and we'll need to do it eventually anyway if we make parallel apply the unconditional default). 2nd, this allows us to use parallel apply in apply load tests, which significantly reduces variance of tests like max-sac-tps test. This touches a few tests I'm not very familiar with (and was AI assisted), but it looks reasonable to me and CI passes, but I'd like for someone more familiar with these unit tests to take a look. # Checklist - [x] Reviewed the [contributing](https://github.com/stellar/stellar-core/blob/master/CONTRIBUTING.md#submitting-changes) document - [x] Rebased on top of master (no merge commits) - [x] Ran `clang-format` v8.0.0 (via `make format` or the Visual Studio extension) - [x] Compiles - [x] 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 a0f7010 + 8cfc272 commit 4318e17

16 files changed

Lines changed: 128 additions & 25 deletions

docs/apply-load-max-sac-tps.cfg

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ APPLY_LOAD_BL_LAST_BATCH_LEDGERS = 0
3939
# Minimal core config boilerplate
4040

4141
RUN_STANDALONE=true
42-
PARALLEL_LEDGER_APPLY=false
4342
NODE_IS_VALIDATOR=true
4443
UNSAFE_QUORUM=true
4544
NETWORK_PASSPHRASE="Standalone Network ; February 2017"

src/bucket/test/BucketManagerTests.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -692,6 +692,10 @@ TEST_CASE_VERSIONS(
692692
cfg.MAX_CONCURRENT_SUBPROCESSES = 1;
693693
cfg.ARTIFICIALLY_ACCELERATE_TIME_FOR_TESTING = true;
694694
cfg.ARTIFICIALLY_PESSIMIZE_MERGES_FOR_TESTING = true;
695+
// Test loop calls forgetUnreferencedBuckets and
696+
// setNextLedgerEntryBatchForBucketTesting while ledgers close
697+
// automatically, which races with background apply.
698+
cfg.PARALLEL_LEDGER_APPLY = false;
695699
stellar::historytestutils::TmpDirHistoryConfigurator tcfg;
696700
cfg = tcfg.configure(cfg, true);
697701

src/herder/test/HerderTests.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5282,6 +5282,11 @@ externalize(SecretKey const& sk, LedgerManager& lm, HerderImpl& herder,
52825282
xdr::xvector<UpgradeType, 6>{}, sk);
52835283
herder.getHerderSCPDriver().valueExternalized(ledgerSeq,
52845284
xdr::xdr_to_opaque(sv));
5285+
// With background apply, crank until the ledger is fully applied
5286+
while (lm.getLastClosedLedgerNum() < ledgerSeq)
5287+
{
5288+
app.getClock().crank(true);
5289+
}
52855290
}
52865291

52875292
TEST_CASE("do not flood invalid transactions", "[herder]")

src/herder/test/TransactionQueueTests.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3069,6 +3069,12 @@ TEST_CASE("remove applied", "[herder][transactionqueue]")
30693069
app->getConfig().NODE_SEED);
30703070
herder.getHerderSCPDriver().valueExternalized(ledgerSeq,
30713071
xdr::xdr_to_opaque(sv));
3072+
3073+
// With background apply, crank until the ledger is fully applied
3074+
while (lm.getLastClosedLedgerNum() < ledgerSeq)
3075+
{
3076+
clock.crank(true);
3077+
}
30723078
}
30733079

30743080
REQUIRE(tq.getTransactions({}).size() == 1);

src/history/test/HistoryTests.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1599,6 +1599,14 @@ TEST_CASE_VERSIONS(
15991599

16001600
while (hm.getPublishQueueCount() != 1)
16011601
{
1602+
// With background apply, wait for any in-progress
1603+
// ledger close to finish before writing to the shared
1604+
// test-entry vectors that finalizeLedgerTxnChanges
1605+
// reads on the apply thread.
1606+
while (lm.isApplying())
1607+
{
1608+
clock.crank(true);
1609+
}
16021610
auto lcl = lm.getLastClosedLedgerHeader();
16031611
lcl.header.ledgerSeq += 1;
16041612
// Generate entries excluding soroban types to avoid worrying
@@ -1969,6 +1977,12 @@ TEST_CASE("Introduce and fix gap without starting catchup",
19691977
// Fill in the second gap. All buffered ledgers should be applied, but we
19701978
// wait for another ledger to close to get in sync
19711979
catchupSimulation.externalizeLedger(herder, nextLedger + 4);
1980+
1981+
// With background apply, crank until all queued ledgers are applied
1982+
while (lm.getLastClosedLedgerNum() < nextLedger + 5)
1983+
{
1984+
app->getClock().crank(true);
1985+
}
19721986
REQUIRE(lm.isSynced());
19731987
REQUIRE(lam.getLargestLedgerSeqHeard() == lm.getLastClosedLedgerNum());
19741988
REQUIRE(!lam.isCatchupInitialized());

src/invariant/test/ConservationOfLumensTests.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,11 @@ TEST_CASE(
315315
{
316316
auto cfg = getTestConfig();
317317
cfg.INVARIANT_CHECKS = {"ConservationOfLumens"};
318+
// This test directly modifies LedgerTxnRoot header (totalCoins), which
319+
// creates a hash mismatch between the DB header and what SCP externalized.
320+
// This is incompatible with background apply where the cross-check runs on
321+
// a thread that doesn't have access to the cached LCL header.
322+
cfg.PARALLEL_LEDGER_APPLY = false;
318323

319324
SorobanTest test(cfg);
320325

@@ -378,6 +383,9 @@ TEST_CASE("ConservationOfLumens snapshot invariant detects bucket corruption",
378383
auto cfg = getTestConfig();
379384
cfg.INVARIANT_CHECKS = {}; // Disable automatic invariant checks because we
380385
// will invoke it manually
386+
// This test directly modifies LedgerTxnRoot header (totalCoins), which is
387+
// incompatible with background apply (see comment in the test above).
388+
cfg.PARALLEL_LEDGER_APPLY = false;
381389

382390
VirtualClock clock;
383391
auto app = createTestApplication<BucketTestUtils::BucketTestApplication>(

src/ledger/LedgerManagerImpl.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1519,6 +1519,17 @@ LedgerManagerImpl::applyLedger(LedgerCloseData const& ledgerData,
15191519
CLOG_ERROR(Ledger, "{}", xdrToCerealString(prevHeader, "Full LCL"));
15201520
CLOG_ERROR(Ledger, "{}", POSSIBLY_CORRUPTED_LOCAL_DATA);
15211521

1522+
#ifdef BUILD_TESTS
1523+
if (!threadIsMain())
1524+
{
1525+
throw std::runtime_error(
1526+
"txset mismatch on background apply thread. This usually means "
1527+
"a test directly modified the LedgerTxnRoot header (e.g. "
1528+
"totalCoins). Set cfg.PARALLEL_LEDGER_APPLY = false for such "
1529+
"tests.");
1530+
}
1531+
#endif
1532+
15221533
throw std::runtime_error("txset mismatch");
15231534
}
15241535

src/ledger/test/LedgerCloseMetaStreamTests.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,13 @@ TEST_CASE("METADATA_DEBUG_LEDGERS works", "[metadebug]")
248248
{
249249
// Generate just enough meta to not triggers garbage collection
250250
closeLedgers(cfg.METADATA_DEBUG_LEDGERS);
251+
252+
// Drain any remaining background apply before stopping, so the
253+
// debug tx set file and LCL are consistent when we read them.
254+
while (lm.isApplying())
255+
{
256+
clock.crank(true);
257+
}
251258
app->gracefulStop();
252259

253260
// Verify presence of the latest debug tx set

src/main/ApplicationImpl.cpp

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -987,6 +987,15 @@ ApplicationImpl::manualClose(std::optional<uint32_t> const& manualLedgerSeq,
987987

988988
if (mConfig.RUN_STANDALONE)
989989
{
990+
// With background apply, triggerNextLedger posts work to the
991+
// apply thread. Crank until the ledger is fully applied and
992+
// LCL has advanced.
993+
while (getLedgerManager().getLastClosedLedgerNum() <
994+
targetLedgerSeq)
995+
{
996+
getClock().crank(true);
997+
}
998+
990999
auto const newLedgerSeq =
9911000
getLedgerManager().getLastClosedLedgerNum();
9921001
if (newLedgerSeq != targetLedgerSeq)
@@ -1563,7 +1572,15 @@ ApplicationImpl::postOnLedgerCloseThread(std::function<void()>&& f,
15631572
asio::post(*mLedgerCloseIOContext, [this, f = std::move(f), isSlow]() {
15641573
JITTER_INJECT_DELAY();
15651574
mPostOnLedgerCloseThreadDelay.Update(isSlow.checkElapsedTime());
1566-
f();
1575+
try
1576+
{
1577+
f();
1578+
}
1579+
catch (...)
1580+
{
1581+
getClock().finishedBackgroundWork();
1582+
throw;
1583+
}
15671584
getClock().finishedBackgroundWork();
15681585
});
15691586
}

src/main/CommandLine.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1921,6 +1921,9 @@ runApplyLoad(CommandLineArgs const& args)
19211921

19221922
// Apply Load may exceed TX_SET byte size limits, so ignore them
19231923
config.IGNORE_MESSAGE_LIMITS_FOR_TESTING = true;
1924+
1925+
// Always use background ledger close for max-sac-tps
1926+
config.PARALLEL_LEDGER_APPLY = true;
19241927
}
19251928

19261929
VirtualClock clock(VirtualClock::REAL_TIME);

0 commit comments

Comments
 (0)