Skip to content

Commit 35248d0

Browse files
authored
Merge branch 'master' into rpc-test-invoke
2 parents 657565b + bbd9114 commit 35248d0

37 files changed

Lines changed: 1636 additions & 106 deletions

Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/metrics.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ bucketlistDB-cache.hit | meter | number of cache hits on
5555
bucketlistDB-cache.miss | meter | number of cache misses on Live BucketList Disk random eviction cache
5656
bucketlistDB.cache.entries | counter | number of entries currently in Live BucketList index cache
5757
bucketlistDB.cache.bytes | counter | estimated size in bytes of entries in Live BucketList index cache
58+
clock.ntp.offset-ms | counter | last measured offset (ms) between the local clock and the configured NTP server (signed; positive means the local clock is behind true time). Requires NTP_DRIFT_CHECK_SERVER
59+
clock.ntp.probe-failure | meter | NTP drift-check probe failed to reach NTP server
5860
crypto.verify.hit | meter | number of signature cache hits
5961
crypto.verify.miss | meter | number of signature cache misses
6062
crypto.verify.total | meter | sum of both hits and misses
@@ -199,6 +201,7 @@ scp.timing.externalized | timer | time spent in ballot pro
199201
scp.timing.first-to-self-externalize-lag | timer | delay between first externalize message and local node externalizing
200202
scp.timing.self-to-others-externalize-lag | timer | delay between local node externalizing and later externalize messages from other nodes
201203
scp.timing.ballot-blocked-on-txset | timer | time balloting was blocked waiting for a txset download (milliseconds)
204+
scp.trigger.prepare-start-fallback | meter | experimental trigger timer fell back from the network-close-time anchor to the local prepare-start anchor
202205
scp.value.invalid | meter | SCP value is invalid
203206
scp.value.valid | meter | SCP value is valid
204207
scp.slot.values-referenced | histogram | number of values referenced per consensus round

docs/stellar-core_example.cfg

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,16 @@ EXPERIMENTAL_PARALLEL_TX_SET_DOWNLOAD = false
290290
# also enabled.
291291
BACKGROUND_TX_SIG_VERIFICATION = true
292292

293+
# NTP_DRIFT_CHECK_SERVER (string) default "pool.ntp.org"
294+
# Hostname of an NTP server that the node periodically queries (about every ten
295+
# minutes) to detect and warn about drift of the local clock. This is detection
296+
# only: stellar-core never adjusts the system clock -- keep an NTP daemon
297+
# running for that.
298+
# The check only runs on validators (NODE_IS_VALIDATOR=true), since clock drift
299+
# only hurts nodes that participate in consensus.
300+
# Set to the empty string ("") to disable the check entirely.
301+
NTP_DRIFT_CHECK_SERVER = "pool.ntp.org"
302+
293303
# PREFERRED_PEERS (list of strings) default is empty
294304
# These are IP:port strings that this server will add to its DB of peers.
295305
# This server will try to always stay connected to the other peers on this list.

src/bucket/BucketListSnapshot.cpp

Lines changed: 78 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -53,27 +53,44 @@ BucketListSnapshotData<BucketT>::BucketListSnapshotData(
5353
{
5454
}
5555

56+
//
57+
// BucketSnapshotMetrics
58+
//
59+
60+
template <class BucketT>
61+
BucketSnapshotMetrics<BucketT>::BucketSnapshotMetrics(MetricsRegistry& metrics)
62+
: mPointTimers([&metrics]() {
63+
UnorderedMap<LedgerEntryType, std::reference_wrapper<SimpleTimer>>
64+
timers;
65+
for (auto t : xdr::xdr_traits<LedgerEntryType>::enum_values())
66+
{
67+
auto const& label = xdr::xdr_traits<LedgerEntryType>::enum_name(
68+
static_cast<LedgerEntryType>(t));
69+
auto& metric = metrics.NewSimpleTimer(
70+
{BucketT::METRIC_STRING, label}, std::chrono::microseconds{1});
71+
timers.emplace(static_cast<LedgerEntryType>(t), metric);
72+
}
73+
return timers;
74+
}())
75+
, mBulkLoadMeter(
76+
metrics.NewMeter({BucketT::METRIC_STRING, "query", "loads"}, "query"))
77+
{
78+
}
79+
5680
//
5781
// SearchableBucketListSnapshot
5882
//
5983

6084
template <class BucketT>
6185
SearchableBucketListSnapshot<BucketT>::SearchableBucketListSnapshot(
6286
MetricsRegistry& metrics,
87+
std::shared_ptr<BucketSnapshotMetrics<BucketT> const> snapshotMetrics,
6388
std::shared_ptr<BucketListSnapshotData<BucketT> const> data)
6489
: mData(std::move(data))
6590
, mMetrics(metrics)
66-
, mBulkLoadMeter(
67-
metrics.NewMeter({BucketT::METRIC_STRING, "query", "loads"}, "query"))
91+
, mSnapshotMetrics(std::move(snapshotMetrics))
6892
{
69-
for (auto t : xdr::xdr_traits<LedgerEntryType>::enum_values())
70-
{
71-
auto const& label = xdr::xdr_traits<LedgerEntryType>::enum_name(
72-
static_cast<LedgerEntryType>(t));
73-
auto& metric = metrics.NewSimpleTimer({BucketT::METRIC_STRING, label},
74-
std::chrono::microseconds{1});
75-
mPointTimers.emplace(static_cast<LedgerEntryType>(t), metric);
76-
}
93+
releaseAssert(mSnapshotMetrics);
7794
}
7895

7996
template <class BucketT>
@@ -82,9 +99,8 @@ SearchableBucketListSnapshot<BucketT>::SearchableBucketListSnapshot(
8299
: mData(other.mData)
83100
// mStreams intentionally left empty — each copy gets its own stream cache
84101
, mMetrics(other.mMetrics)
85-
, mPointTimers(other.mPointTimers)
102+
, mSnapshotMetrics(other.mSnapshotMetrics)
86103
, mBulkTimers(other.mBulkTimers)
87-
, mBulkLoadMeter(other.mBulkLoadMeter)
88104
{
89105
}
90106

@@ -98,20 +114,45 @@ SearchableBucketListSnapshot<BucketT>::operator=(
98114
mData = other.mData;
99115
mStreams.clear();
100116
mMetrics = other.mMetrics;
101-
mPointTimers = other.mPointTimers;
117+
mSnapshotMetrics = other.mSnapshotMetrics;
102118
mBulkTimers = other.mBulkTimers;
103-
mBulkLoadMeter = other.mBulkLoadMeter;
119+
#ifdef BUILD_TESTS
120+
// Reset thread ownership so the copy can be claimed by another thread.
121+
mThreadId.store(std::thread::id{});
122+
#endif
104123
}
105124
return *this;
106125
}
107126

127+
// Bucket loads are not thread safe and a single snapshot instance should only
128+
// be queried by one thread. We cache the initial caller's thread id and assert
129+
// following queries are from the same thread. Note: this only guards the
130+
// bucket-loading query entry points; access to the immutable underlying
131+
// snapshot data is thread safe.
132+
template <class BucketT>
133+
void
134+
SearchableBucketListSnapshot<BucketT>::threadInvariant() const
135+
{
136+
#ifdef BUILD_TESTS
137+
auto const current = std::this_thread::get_id();
138+
std::thread::id unclaimed{};
139+
// Atomically claim ownership on first use, so any concurrent claimant sees
140+
// the CAS fail with `unclaimed` set to the owner's id and asserts.
141+
if (!mThreadId.compare_exchange_strong(unclaimed, current))
142+
{
143+
releaseAssert(unclaimed == current);
144+
}
145+
#endif
146+
}
147+
108148
// File streams are fairly expensive to create, so they are lazily created and
109149
// stored in mStreams.
110150
template <class BucketT>
111151
XDRInputFileStream&
112152
SearchableBucketListSnapshot<BucketT>::getStream(
113153
std::shared_ptr<BucketT const> const& bucket) const
114154
{
155+
threadInvariant();
115156
BucketT const* key = bucket.get();
116157
auto it = mStreams.find(key);
117158
if (it == mStreams.end())
@@ -307,9 +348,10 @@ SearchableBucketListSnapshot<BucketT>::load(LedgerKey const& k) const
307348
{
308349
ZoneScoped;
309350
releaseAssert(mData);
351+
threadInvariant();
310352

311-
auto timerIter = mPointTimers.find(k.type());
312-
releaseAssert(timerIter != mPointTimers.end());
353+
auto timerIter = mSnapshotMetrics->mPointTimers.find(k.type());
354+
releaseAssert(timerIter != mSnapshotMetrics->mPointTimers.end());
313355
auto timer = timerIter->second.get().TimeScope();
314356

315357
std::shared_ptr<typename BucketT::LoadT const> result{};
@@ -341,9 +383,13 @@ medida::Timer&
341383
SearchableBucketListSnapshot<BucketT>::getBulkLoadTimer(
342384
std::string const& label, size_t numEntries) const
343385
{
386+
// mBulkTimers is per-snapshot mutable state lazily populated here, so this
387+
// must be single-threaded. Enforced here as well as at the public query
388+
// entry points.
389+
threadInvariant();
344390
if (numEntries != 0)
345391
{
346-
mBulkLoadMeter.get().Mark(numEntries);
392+
mSnapshotMetrics->mBulkLoadMeter.get().Mark(numEntries);
347393
}
348394

349395
auto iter = mBulkTimers.find(label);
@@ -370,8 +416,10 @@ SearchableBucketListSnapshot<BucketT>::getSnapshotData() const
370416

371417
SearchableLiveBucketListSnapshot::SearchableLiveBucketListSnapshot(
372418
MetricsRegistry& metrics,
419+
std::shared_ptr<BucketSnapshotMetrics<LiveBucket> const> snapshotMetrics,
373420
std::shared_ptr<BucketListSnapshotData<LiveBucket> const> data)
374-
: SearchableBucketListSnapshot<LiveBucket>(metrics, std::move(data))
421+
: SearchableBucketListSnapshot<LiveBucket>(
422+
metrics, std::move(snapshotMetrics), std::move(data))
375423
{
376424
}
377425

@@ -383,6 +431,7 @@ SearchableBucketListSnapshot<BucketT>::loadKeys(
383431
{
384432
ZoneScoped;
385433
releaseAssert(mData);
434+
threadInvariant();
386435
auto timer = getBulkLoadTimer(label, inKeys.size()).TimeScope();
387436

388437
auto keys = inKeys;
@@ -406,6 +455,7 @@ SearchableLiveBucketListSnapshot::loadPoolShareTrustLinesByAccountAndAsset(
406455
{
407456
ZoneScoped;
408457
releaseAssert(mData);
458+
threadInvariant();
409459

410460
LedgerKeySet trustlinesToLoad;
411461

@@ -448,6 +498,7 @@ SearchableLiveBucketListSnapshot::loadInflationWinners(size_t maxWinners,
448498
{
449499
ZoneScoped;
450500
releaseAssert(mData);
501+
threadInvariant();
451502

452503
auto timer = getBulkLoadTimer("inflationWinners", 0).TimeScope();
453504

@@ -548,6 +599,7 @@ SearchableLiveBucketListSnapshot::scanForEviction(
548599
ZoneScoped;
549600
releaseAssert(mData);
550601
releaseAssert(stats);
602+
threadInvariant();
551603

552604
auto getBucketFromIter =
553605
[&levels = mData->levels](
@@ -599,6 +651,7 @@ SearchableLiveBucketListSnapshot::scanForEntriesOfType(
599651
{
600652
ZoneScoped;
601653
releaseAssert(mData);
654+
threadInvariant();
602655

603656
auto scanBucket = [&](std::shared_ptr<LiveBucket const> const& bucket) {
604657
if (bucket->isEmpty())
@@ -806,8 +859,11 @@ SearchableLiveBucketListSnapshot::scanForEvictionInBucket(
806859

807860
SearchableHotArchiveBucketListSnapshot::SearchableHotArchiveBucketListSnapshot(
808861
MetricsRegistry& metrics,
862+
std::shared_ptr<BucketSnapshotMetrics<HotArchiveBucket> const>
863+
snapshotMetrics,
809864
std::shared_ptr<BucketListSnapshotData<HotArchiveBucket> const> data)
810-
: SearchableBucketListSnapshot<HotArchiveBucket>(metrics, std::move(data))
865+
: SearchableBucketListSnapshot<HotArchiveBucket>(
866+
metrics, std::move(snapshotMetrics), std::move(data))
811867
{
812868
}
813869

@@ -817,6 +873,7 @@ SearchableHotArchiveBucketListSnapshot::scanAllEntries(
817873
{
818874
ZoneScoped;
819875
releaseAssert(mData);
876+
threadInvariant();
820877

821878
auto scanBucket =
822879
[&](std::shared_ptr<HotArchiveBucket const> const& bucket) {
@@ -841,6 +898,8 @@ SearchableHotArchiveBucketListSnapshot::scanAllEntries(
841898
// Explicit template instantiations
842899
template struct BucketListSnapshotData<LiveBucket>;
843900
template struct BucketListSnapshotData<HotArchiveBucket>;
901+
template struct BucketSnapshotMetrics<LiveBucket>;
902+
template struct BucketSnapshotMetrics<HotArchiveBucket>;
844903
template class SearchableBucketListSnapshot<LiveBucket>;
845904
template class SearchableBucketListSnapshot<HotArchiveBucket>;
846905

0 commit comments

Comments
 (0)