@@ -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
6084template <class BucketT >
6185SearchableBucketListSnapshot<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
7996template <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.
110150template <class BucketT >
111151XDRInputFileStream&
112152SearchableBucketListSnapshot<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&
341383SearchableBucketListSnapshot<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
371417SearchableLiveBucketListSnapshot::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
807860SearchableHotArchiveBucketListSnapshot::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
842899template struct BucketListSnapshotData <LiveBucket>;
843900template struct BucketListSnapshotData <HotArchiveBucket>;
901+ template struct BucketSnapshotMetrics <LiveBucket>;
902+ template struct BucketSnapshotMetrics <HotArchiveBucket>;
844903template class SearchableBucketListSnapshot <LiveBucket>;
845904template class SearchableBucketListSnapshot <HotArchiveBucket>;
846905
0 commit comments