From 3f07882ed67ebe816e86805f7c4019bc775ea892 Mon Sep 17 00:00:00 2001 From: Scott Determan Date: Sat, 21 Oct 2023 20:23:46 -0400 Subject: [PATCH 1/7] Split tagged cache into .h and .ipp files cc --- include/xrpl/basics/TaggedCache.h | 515 +---------- include/xrpl/basics/TaggedCache.ipp | 819 ++++++++++++++++++ src/test/basics/KeyCache_test.cpp | 1 + src/test/basics/TaggedCache_test.cpp | 1 + src/xrpld/app/ledger/ConsensusTransSetSF.cpp | 1 + src/xrpld/app/ledger/LedgerHistory.cpp | 1 + src/xrpld/app/ledger/detail/LedgerMaster.cpp | 2 +- .../app/ledger/detail/TransactionMaster.cpp | 1 + src/xrpld/app/main/Application.cpp | 1 + src/xrpld/app/misc/NetworkOPs.cpp | 1 + src/xrpld/app/misc/SHAMapStoreImp.h | 2 +- src/xrpld/app/rdb/backend/detail/Node.cpp | 1 + src/xrpld/ledger/detail/CachedView.cpp | 1 + src/xrpld/nodestore/Database.h | 2 +- .../nodestore/detail/DatabaseNodeImp.cpp | 1 + src/xrpld/rpc/handlers/GetCounts.cpp | 1 + src/xrpld/shamap/FullBelowCache.h | 1 + 17 files changed, 863 insertions(+), 489 deletions(-) create mode 100644 include/xrpl/basics/TaggedCache.ipp diff --git a/include/xrpl/basics/TaggedCache.h b/include/xrpl/basics/TaggedCache.h index 1fcdc3707b6..9ff63b1e0ce 100644 --- a/include/xrpl/basics/TaggedCache.h +++ b/include/xrpl/basics/TaggedCache.h @@ -69,230 +69,57 @@ class TaggedCache clock_type& clock, beast::Journal journal, beast::insight::Collector::ptr const& collector = - beast::insight::NullCollector::New()) - : m_journal(journal) - , m_clock(clock) - , m_stats( - name, - std::bind(&TaggedCache::collect_metrics, this), - collector) - , m_name(name) - , m_target_size(size) - , m_target_age(expiration) - , m_cache_count(0) - , m_hits(0) - , m_misses(0) - { - } + beast::insight::NullCollector::New()); public: /** Return the clock associated with the cache. */ clock_type& - clock() - { - return m_clock; - } + clock(); /** Returns the number of items in the container. */ std::size_t - size() const - { - std::lock_guard lock(m_mutex); - return m_cache.size(); - } + size() const; void - setTargetSize(int s) - { - std::lock_guard lock(m_mutex); - m_target_size = s; - - if (s > 0) - { - for (auto& partition : m_cache.map()) - { - partition.rehash(static_cast( - (s + (s >> 2)) / - (partition.max_load_factor() * m_cache.partitions()) + - 1)); - } - } - - JLOG(m_journal.debug()) << m_name << " target size set to " << s; - } + setTargetSize(int s); clock_type::duration - getTargetAge() const - { - std::lock_guard lock(m_mutex); - return m_target_age; - } + getTargetAge() const; void - setTargetAge(clock_type::duration s) - { - std::lock_guard lock(m_mutex); - m_target_age = s; - JLOG(m_journal.debug()) - << m_name << " target age set to " << m_target_age.count(); - } + setTargetAge(clock_type::duration s); int - getCacheSize() const - { - std::lock_guard lock(m_mutex); - return m_cache_count; - } + getCacheSize() const; int - getTrackSize() const - { - std::lock_guard lock(m_mutex); - return m_cache.size(); - } + getTrackSize() const; float - getHitRate() - { - std::lock_guard lock(m_mutex); - auto const total = static_cast(m_hits + m_misses); - return m_hits * (100.0f / std::max(1.0f, total)); - } + getHitRate(); void - clear() - { - std::lock_guard lock(m_mutex); - m_cache.clear(); - m_cache_count = 0; - } + clear(); void - reset() - { - std::lock_guard lock(m_mutex); - m_cache.clear(); - m_cache_count = 0; - m_hits = 0; - m_misses = 0; - } + reset(); /** Refresh the last access time on a key if present. @return `true` If the key was found. */ template bool - touch_if_exists(KeyComparable const& key) - { - std::lock_guard lock(m_mutex); - auto const iter(m_cache.find(key)); - if (iter == m_cache.end()) - { - ++m_stats.misses; - return false; - } - iter->second.touch(m_clock.now()); - ++m_stats.hits; - return true; - } + touch_if_exists(KeyComparable const& key); using SweptPointersVector = std::pair< std::vector>, std::vector>>; void - sweep() - { - // Keep references to all the stuff we sweep - // For performance, each worker thread should exit before the swept data - // is destroyed but still within the main cache lock. - std::vector allStuffToSweep(m_cache.partitions()); - - clock_type::time_point const now(m_clock.now()); - clock_type::time_point when_expire; - - auto const start = std::chrono::steady_clock::now(); - { - std::lock_guard lock(m_mutex); - - if (m_target_size == 0 || - (static_cast(m_cache.size()) <= m_target_size)) - { - when_expire = now - m_target_age; - } - else - { - when_expire = - now - m_target_age * m_target_size / m_cache.size(); - - clock_type::duration const minimumAge(std::chrono::seconds(1)); - if (when_expire > (now - minimumAge)) - when_expire = now - minimumAge; - - JLOG(m_journal.trace()) - << m_name << " is growing fast " << m_cache.size() << " of " - << m_target_size << " aging at " - << (now - when_expire).count() << " of " - << m_target_age.count(); - } - - std::vector workers; - workers.reserve(m_cache.partitions()); - std::atomic allRemovals = 0; - - for (std::size_t p = 0; p < m_cache.partitions(); ++p) - { - workers.push_back(sweepHelper( - when_expire, - now, - m_cache.map()[p], - allStuffToSweep[p], - allRemovals, - lock)); - } - for (std::thread& worker : workers) - worker.join(); - - m_cache_count -= allRemovals; - } - // At this point allStuffToSweep will go out of scope outside the lock - // and decrement the reference count on each strong pointer. - JLOG(m_journal.debug()) - << m_name << " TaggedCache sweep lock duration " - << std::chrono::duration_cast( - std::chrono::steady_clock::now() - start) - .count() - << "ms"; - } + sweep(); bool - del(const key_type& key, bool valid) - { - // Remove from cache, if !valid, remove from map too. Returns true if - // removed from cache - std::lock_guard lock(m_mutex); - - auto cit = m_cache.find(key); - - if (cit == m_cache.end()) - return false; - - Entry& entry = cit->second; - - bool ret = false; - - if (entry.isCached()) - { - --m_cache_count; - entry.ptr.reset(); - ret = true; - } - - if (!valid || entry.isExpired()) - m_cache.erase(cit); - - return ret; - } + del(const key_type& key, bool valid); /** Replace aliased objects with originals. @@ -312,95 +139,18 @@ class TaggedCache canonicalize( const key_type& key, std::shared_ptr& data, - std::function const&)>&& replace) - { - // Return canonical value, store if needed, refresh in cache - // Return values: true=we had the data already - std::lock_guard lock(m_mutex); - - auto cit = m_cache.find(key); - - if (cit == m_cache.end()) - { - m_cache.emplace( - std::piecewise_construct, - std::forward_as_tuple(key), - std::forward_as_tuple(m_clock.now(), data)); - ++m_cache_count; - return false; - } - - Entry& entry = cit->second; - entry.touch(m_clock.now()); - - if (entry.isCached()) - { - if (replace(entry.ptr)) - { - entry.ptr = data; - entry.weak_ptr = data; - } - else - { - data = entry.ptr; - } - - return true; - } - - auto cachedData = entry.lock(); - - if (cachedData) - { - if (replace(entry.ptr)) - { - entry.ptr = data; - entry.weak_ptr = data; - } - else - { - entry.ptr = cachedData; - data = cachedData; - } - - ++m_cache_count; - return true; - } - - entry.ptr = data; - entry.weak_ptr = data; - ++m_cache_count; - - return false; - } + std::function const&)>&& replace); bool canonicalize_replace_cache( const key_type& key, - std::shared_ptr const& data) - { - return canonicalize( - key, - const_cast&>(data), - [](std::shared_ptr const&) { return true; }); - } + std::shared_ptr const& data); bool - canonicalize_replace_client(const key_type& key, std::shared_ptr& data) - { - return canonicalize( - key, data, [](std::shared_ptr const&) { return false; }); - } + canonicalize_replace_client(const key_type& key, std::shared_ptr& data); std::shared_ptr - fetch(const key_type& key) - { - std::lock_guard l(m_mutex); - auto ret = initialFetch(key, l); - if (!ret) - ++m_misses; - return ret; - } + fetch(const key_type& key); /** Insert the element into the container. If the key already exists, nothing happens. @@ -409,26 +159,11 @@ class TaggedCache template auto insert(key_type const& key, T const& value) - -> std::enable_if_t - { - auto p = std::make_shared(std::cref(value)); - return canonicalize_replace_client(key, p); - } + -> std::enable_if_t; template auto - insert(key_type const& key) -> std::enable_if_t - { - std::lock_guard lock(m_mutex); - clock_type::time_point const now(m_clock.now()); - auto [it, inserted] = m_cache.emplace( - std::piecewise_construct, - std::forward_as_tuple(key), - std::forward_as_tuple(now)); - if (!inserted) - it->second.last_access = now; - return inserted; - } + insert(key_type const& key) -> std::enable_if_t; // VFALCO NOTE It looks like this returns a copy of the data in // the output parameter 'data'. This could be expensive. @@ -436,50 +171,18 @@ class TaggedCache // simply return an iterator. // bool - retrieve(const key_type& key, T& data) - { - // retrieve the value of the stored data - auto entry = fetch(key); - - if (!entry) - return false; - - data = *entry; - return true; - } + retrieve(const key_type& key, T& data); mutex_type& - peekMutex() - { - return m_mutex; - } + peekMutex(); std::vector - getKeys() const - { - std::vector v; - - { - std::lock_guard lock(m_mutex); - v.reserve(m_cache.size()); - for (auto const& _ : m_cache) - v.push_back(_.first); - } - - return v; - } + getKeys() const; // CachedSLEs functions. /** Returns the fraction of cache hits. */ double - rate() const - { - std::lock_guard lock(m_mutex); - auto const tot = m_hits + m_misses; - if (tot == 0) - return 0; - return double(m_hits) / tot; - } + rate() const; /** Fetch an item from the cache. If the digest was not found, Handler @@ -488,72 +191,15 @@ class TaggedCache */ template std::shared_ptr - fetch(key_type const& digest, Handler const& h) - { - { - std::lock_guard l(m_mutex); - if (auto ret = initialFetch(digest, l)) - return ret; - } - - auto sle = h(); - if (!sle) - return {}; - - std::lock_guard l(m_mutex); - ++m_misses; - auto const [it, inserted] = - m_cache.emplace(digest, Entry(m_clock.now(), std::move(sle))); - if (!inserted) - it->second.touch(m_clock.now()); - return it->second.ptr; - } + fetch(key_type const& digest, Handler const& h); // End CachedSLEs functions. private: std::shared_ptr - initialFetch(key_type const& key, std::lock_guard const& l) - { - auto cit = m_cache.find(key); - if (cit == m_cache.end()) - return {}; - - Entry& entry = cit->second; - if (entry.isCached()) - { - ++m_hits; - entry.touch(m_clock.now()); - return entry.ptr; - } - entry.ptr = entry.lock(); - if (entry.isCached()) - { - // independent of cache size, so not counted as a hit - ++m_cache_count; - entry.touch(m_clock.now()); - return entry.ptr; - } - - m_cache.erase(cit); - return {}; - } + initialFetch(key_type const& key, std::lock_guard const& l); void - collect_metrics() - { - m_stats.size.set(getCacheSize()); - - { - beast::insight::Gauge::value_type hit_rate(0); - { - std::lock_guard lock(m_mutex); - auto const total(m_hits + m_misses); - if (total != 0) - hit_rate = (m_hits * 100) / total; - } - m_stats.hit_rate.set(hit_rate); - } - } + collect_metrics(); private: struct Stats @@ -657,72 +303,7 @@ class TaggedCache typename KeyValueCacheType::map_type& partition, SweptPointersVector& stuffToSweep, std::atomic& allRemovals, - std::lock_guard const&) - { - return std::thread([&, this]() { - int cacheRemovals = 0; - int mapRemovals = 0; - - // Keep references to all the stuff we sweep - // so that we can destroy them outside the lock. - stuffToSweep.first.reserve(partition.size()); - stuffToSweep.second.reserve(partition.size()); - { - auto cit = partition.begin(); - while (cit != partition.end()) - { - if (cit->second.isWeak()) - { - // weak - if (cit->second.isExpired()) - { - stuffToSweep.second.push_back( - std::move(cit->second.weak_ptr)); - ++mapRemovals; - cit = partition.erase(cit); - } - else - { - ++cit; - } - } - else if (cit->second.last_access <= when_expire) - { - // strong, expired - ++cacheRemovals; - if (cit->second.ptr.use_count() == 1) - { - stuffToSweep.first.push_back( - std::move(cit->second.ptr)); - ++mapRemovals; - cit = partition.erase(cit); - } - else - { - // remains weakly cached - cit->second.ptr.reset(); - ++cit; - } - } - else - { - // strong, not expired - ++cit; - } - } - } - - if (mapRemovals || cacheRemovals) - { - JLOG(m_journal.debug()) - << "TaggedCache partition sweep " << m_name - << ": cache = " << partition.size() << "-" << cacheRemovals - << ", map-=" << mapRemovals; - } - - allRemovals += cacheRemovals; - }); - } + std::lock_guard const&); [[nodiscard]] std::thread sweepHelper( @@ -731,45 +312,7 @@ class TaggedCache typename KeyOnlyCacheType::map_type& partition, SweptPointersVector&, std::atomic& allRemovals, - std::lock_guard const&) - { - return std::thread([&, this]() { - int cacheRemovals = 0; - int mapRemovals = 0; - - // Keep references to all the stuff we sweep - // so that we can destroy them outside the lock. - { - auto cit = partition.begin(); - while (cit != partition.end()) - { - if (cit->second.last_access > now) - { - cit->second.last_access = now; - ++cit; - } - else if (cit->second.last_access <= when_expire) - { - cit = partition.erase(cit); - } - else - { - ++cit; - } - } - } - - if (mapRemovals || cacheRemovals) - { - JLOG(m_journal.debug()) - << "TaggedCache partition sweep " << m_name - << ": cache = " << partition.size() << "-" << cacheRemovals - << ", map-=" << mapRemovals; - } - - allRemovals += cacheRemovals; - }); - }; + std::lock_guard const&); beast::Journal m_journal; clock_type& m_clock; diff --git a/include/xrpl/basics/TaggedCache.ipp b/include/xrpl/basics/TaggedCache.ipp new file mode 100644 index 00000000000..ebf70f9af84 --- /dev/null +++ b/include/xrpl/basics/TaggedCache.ipp @@ -0,0 +1,819 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_BASICS_TAGGEDCACHE_IPP_INCLUDED +#define RIPPLE_BASICS_TAGGEDCACHE_IPP_INCLUDED + +#include + +namespace ripple { + +template < + class Key, + class T, + bool IsKeyCache, + class Hash, + class KeyEqual, + class Mutex> +TaggedCache::TaggedCache( + std::string const& name, + int size, + clock_type::duration expiration, + clock_type& clock, + beast::Journal journal, + beast::insight::Collector::ptr const& collector) + : m_journal(journal) + , m_clock(clock) + , m_stats(name, std::bind(&TaggedCache::collect_metrics, this), collector) + , m_name(name) + , m_target_size(size) + , m_target_age(expiration) + , m_cache_count(0) + , m_hits(0) + , m_misses(0) +{ +} + +/** Return the clock associated with the cache. */ +template < + class Key, + class T, + bool IsKeyCache, + class Hash, + class KeyEqual, + class Mutex> +auto +TaggedCache::clock() -> clock_type& +{ + return m_clock; +} + +/** Returns the number of items in the container. */ +template < + class Key, + class T, + bool IsKeyCache, + class Hash, + class KeyEqual, + class Mutex> +std::size_t +TaggedCache::size() const +{ + std::lock_guard lock(m_mutex); + return m_cache.size(); +} + +template < + class Key, + class T, + bool IsKeyCache, + class Hash, + class KeyEqual, + class Mutex> +void +TaggedCache::setTargetSize(int s) +{ + std::lock_guard lock(m_mutex); + m_target_size = s; + + if (s > 0) + { + for (auto& partition : m_cache.map()) + { + partition.rehash(static_cast( + (s + (s >> 2)) / + (partition.max_load_factor() * m_cache.partitions()) + + 1)); + } + } + + JLOG(m_journal.debug()) << m_name << " target size set to " << s; +} + +template < + class Key, + class T, + bool IsKeyCache, + class Hash, + class KeyEqual, + class Mutex> +auto +TaggedCache::getTargetAge() const + -> clock_type::duration +{ + std::lock_guard lock(m_mutex); + return m_target_age; +} + +template < + class Key, + class T, + bool IsKeyCache, + class Hash, + class KeyEqual, + class Mutex> +void +TaggedCache::setTargetAge( + clock_type::duration s) +{ + std::lock_guard lock(m_mutex); + m_target_age = s; + JLOG(m_journal.debug()) + << m_name << " target age set to " << m_target_age.count(); +} + +template < + class Key, + class T, + bool IsKeyCache, + class Hash, + class KeyEqual, + class Mutex> +int +TaggedCache::getCacheSize() const +{ + std::lock_guard lock(m_mutex); + return m_cache_count; +} + +template < + class Key, + class T, + bool IsKeyCache, + class Hash, + class KeyEqual, + class Mutex> +int +TaggedCache::getTrackSize() const +{ + std::lock_guard lock(m_mutex); + return m_cache.size(); +} + +template < + class Key, + class T, + bool IsKeyCache, + class Hash, + class KeyEqual, + class Mutex> +float +TaggedCache::getHitRate() +{ + std::lock_guard lock(m_mutex); + auto const total = static_cast(m_hits + m_misses); + return m_hits * (100.0f / std::max(1.0f, total)); +} + +template < + class Key, + class T, + bool IsKeyCache, + class Hash, + class KeyEqual, + class Mutex> +void +TaggedCache::clear() +{ + std::lock_guard lock(m_mutex); + m_cache.clear(); + m_cache_count = 0; +} + +template < + class Key, + class T, + bool IsKeyCache, + class Hash, + class KeyEqual, + class Mutex> +void +TaggedCache::reset() +{ + std::lock_guard lock(m_mutex); + m_cache.clear(); + m_cache_count = 0; + m_hits = 0; + m_misses = 0; +} + +template < + class Key, + class T, + bool IsKeyCache, + class Hash, + class KeyEqual, + class Mutex> +template +bool +TaggedCache::touch_if_exists( + KeyComparable const& key) +{ + std::lock_guard lock(m_mutex); + auto const iter(m_cache.find(key)); + if (iter == m_cache.end()) + { + ++m_stats.misses; + return false; + } + iter->second.touch(m_clock.now()); + ++m_stats.hits; + return true; +} + +template < + class Key, + class T, + bool IsKeyCache, + class Hash, + class KeyEqual, + class Mutex> +void +TaggedCache::sweep() +{ + // Keep references to all the stuff we sweep + // For performance, each worker thread should exit before the swept data + // is destroyed but still within the main cache lock. + std::vector allStuffToSweep(m_cache.partitions()); + + clock_type::time_point const now(m_clock.now()); + clock_type::time_point when_expire; + + auto const start = std::chrono::steady_clock::now(); + { + std::lock_guard lock(m_mutex); + + if (m_target_size == 0 || + (static_cast(m_cache.size()) <= m_target_size)) + { + when_expire = now - m_target_age; + } + else + { + when_expire = now - m_target_age * m_target_size / m_cache.size(); + + clock_type::duration const minimumAge(std::chrono::seconds(1)); + if (when_expire > (now - minimumAge)) + when_expire = now - minimumAge; + + JLOG(m_journal.trace()) + << m_name << " is growing fast " << m_cache.size() << " of " + << m_target_size << " aging at " << (now - when_expire).count() + << " of " << m_target_age.count(); + } + + std::vector workers; + workers.reserve(m_cache.partitions()); + std::atomic allRemovals = 0; + + for (std::size_t p = 0; p < m_cache.partitions(); ++p) + { + workers.push_back(sweepHelper( + when_expire, + now, + m_cache.map()[p], + allStuffToSweep[p], + allRemovals, + lock)); + } + for (std::thread& worker : workers) + worker.join(); + + m_cache_count -= allRemovals; + } + // At this point allStuffToSweep will go out of scope outside the lock + // and decrement the reference count on each strong pointer. + JLOG(m_journal.debug()) + << m_name << " TaggedCache sweep lock duration " + << std::chrono::duration_cast( + std::chrono::steady_clock::now() - start) + .count() + << "ms"; +} + +template < + class Key, + class T, + bool IsKeyCache, + class Hash, + class KeyEqual, + class Mutex> +bool +TaggedCache::del( + const key_type& key, + bool valid) +{ + // Remove from cache, if !valid, remove from map too. Returns true if + // removed from cache + std::lock_guard lock(m_mutex); + + auto cit = m_cache.find(key); + + if (cit == m_cache.end()) + return false; + + Entry& entry = cit->second; + + bool ret = false; + + if (entry.isCached()) + { + --m_cache_count; + entry.ptr.reset(); + ret = true; + } + + if (!valid || entry.isExpired()) + m_cache.erase(cit); + + return ret; +} + +template < + class Key, + class T, + bool IsKeyCache, + class Hash, + class KeyEqual, + class Mutex> +bool +TaggedCache::canonicalize( + const key_type& key, + std::shared_ptr& data, + std::function const&)>&& replace) +{ + // Return canonical value, store if needed, refresh in cache + // Return values: true=we had the data already + std::lock_guard lock(m_mutex); + + auto cit = m_cache.find(key); + + if (cit == m_cache.end()) + { + m_cache.emplace( + std::piecewise_construct, + std::forward_as_tuple(key), + std::forward_as_tuple(m_clock.now(), data)); + ++m_cache_count; + return false; + } + + Entry& entry = cit->second; + entry.touch(m_clock.now()); + + if (entry.isCached()) + { + if (replace(entry.ptr)) + { + entry.ptr = data; + entry.weak_ptr = data; + } + else + { + data = entry.ptr; + } + + return true; + } + + auto cachedData = entry.lock(); + + if (cachedData) + { + if (replace(entry.ptr)) + { + entry.ptr = data; + entry.weak_ptr = data; + } + else + { + entry.ptr = cachedData; + data = cachedData; + } + + ++m_cache_count; + return true; + } + + entry.ptr = data; + entry.weak_ptr = data; + ++m_cache_count; + + return false; +} + +template < + class Key, + class T, + bool IsKeyCache, + class Hash, + class KeyEqual, + class Mutex> +bool +TaggedCache:: + canonicalize_replace_cache( + const key_type& key, + std::shared_ptr const& data) +{ + return canonicalize( + key, + const_cast&>(data), + [](std::shared_ptr const&) { return true; }); +} + +template < + class Key, + class T, + bool IsKeyCache, + class Hash, + class KeyEqual, + class Mutex> +bool +TaggedCache:: + canonicalize_replace_client(const key_type& key, std::shared_ptr& data) +{ + return canonicalize( + key, data, [](std::shared_ptr const&) { return false; }); +} + +template < + class Key, + class T, + bool IsKeyCache, + class Hash, + class KeyEqual, + class Mutex> +std::shared_ptr +TaggedCache::fetch( + const key_type& key) +{ + std::lock_guard l(m_mutex); + auto ret = initialFetch(key, l); + if (!ret) + ++m_misses; + return ret; +} + +template < + class Key, + class T, + bool IsKeyCache, + class Hash, + class KeyEqual, + class Mutex> +template +auto +TaggedCache::insert( + key_type const& key, + T const& value) -> std::enable_if_t +{ + auto p = std::make_shared(std::cref(value)); + return canonicalize_replace_client(key, p); +} + +template < + class Key, + class T, + bool IsKeyCache, + class Hash, + class KeyEqual, + class Mutex> +template +auto +TaggedCache::insert( + key_type const& key) -> std::enable_if_t +{ + std::lock_guard lock(m_mutex); + clock_type::time_point const now(m_clock.now()); + auto [it, inserted] = m_cache.emplace( + std::piecewise_construct, + std::forward_as_tuple(key), + std::forward_as_tuple(now)); + if (!inserted) + it->second.last_access = now; + return inserted; +} + +template < + class Key, + class T, + bool IsKeyCache, + class Hash, + class KeyEqual, + class Mutex> +bool +TaggedCache::retrieve( + const key_type& key, + T& data) +{ + // retrieve the value of the stored data + auto entry = fetch(key); + + if (!entry) + return false; + + data = *entry; + return true; +} + +template < + class Key, + class T, + bool IsKeyCache, + class Hash, + class KeyEqual, + class Mutex> +auto +TaggedCache::peekMutex() + -> mutex_type& +{ + return m_mutex; +} + +template < + class Key, + class T, + bool IsKeyCache, + class Hash, + class KeyEqual, + class Mutex> +auto +TaggedCache::getKeys() const + -> std::vector +{ + std::vector v; + + { + std::lock_guard lock(m_mutex); + v.reserve(m_cache.size()); + for (auto const& _ : m_cache) + v.push_back(_.first); + } + + return v; +} + +template < + class Key, + class T, + bool IsKeyCache, + class Hash, + class KeyEqual, + class Mutex> +double +TaggedCache::rate() const +{ + std::lock_guard lock(m_mutex); + auto const tot = m_hits + m_misses; + if (tot == 0) + return 0; + return double(m_hits) / tot; +} + +template < + class Key, + class T, + bool IsKeyCache, + class Hash, + class KeyEqual, + class Mutex> +template +std::shared_ptr +TaggedCache::fetch( + key_type const& digest, + Handler const& h) +{ + { + std::lock_guard l(m_mutex); + if (auto ret = initialFetch(digest, l)) + return ret; + } + + auto sle = h(); + if (!sle) + return {}; + + std::lock_guard l(m_mutex); + ++m_misses; + auto const [it, inserted] = + m_cache.emplace(digest, Entry(m_clock.now(), std::move(sle))); + if (!inserted) + it->second.touch(m_clock.now()); + return it->second.ptr; +} +// End CachedSLEs functions. + +template < + class Key, + class T, + bool IsKeyCache, + class Hash, + class KeyEqual, + class Mutex> +std::shared_ptr +TaggedCache::initialFetch( + key_type const& key, + std::lock_guard const& l) +{ + auto cit = m_cache.find(key); + if (cit == m_cache.end()) + return {}; + + Entry& entry = cit->second; + if (entry.isCached()) + { + ++m_hits; + entry.touch(m_clock.now()); + return entry.ptr; + } + entry.ptr = entry.lock(); + if (entry.isCached()) + { + // independent of cache size, so not counted as a hit + ++m_cache_count; + entry.touch(m_clock.now()); + return entry.ptr; + } + + m_cache.erase(cit); + return {}; +} + +template < + class Key, + class T, + bool IsKeyCache, + class Hash, + class KeyEqual, + class Mutex> +void +TaggedCache::collect_metrics() +{ + m_stats.size.set(getCacheSize()); + + { + beast::insight::Gauge::value_type hit_rate(0); + { + std::lock_guard lock(m_mutex); + auto const total(m_hits + m_misses); + if (total != 0) + hit_rate = (m_hits * 100) / total; + } + m_stats.hit_rate.set(hit_rate); + } +} + +template < + class Key, + class T, + bool IsKeyCache, + class Hash, + class KeyEqual, + class Mutex> +std::thread +TaggedCache::sweepHelper( + clock_type::time_point const& when_expire, + [[maybe_unused]] clock_type::time_point const& now, + typename KeyValueCacheType::map_type& partition, + SweptPointersVector& stuffToSweep, + std::atomic& allRemovals, + std::lock_guard const&) +{ + return std::thread([&, this]() { + int cacheRemovals = 0; + int mapRemovals = 0; + + // Keep references to all the stuff we sweep + // so that we can destroy them outside the lock. + stuffToSweep.first.reserve(partition.size()); + stuffToSweep.second.reserve(partition.size()); + { + auto cit = partition.begin(); + while (cit != partition.end()) + { + if (cit->second.isWeak()) + { + // weak + if (cit->second.isExpired()) + { + stuffToSweep.second.push_back( + std::move(cit->second.weak_ptr)); + ++mapRemovals; + cit = partition.erase(cit); + } + else + { + ++cit; + } + } + else if (cit->second.last_access <= when_expire) + { + // strong, expired + ++cacheRemovals; + if (cit->second.ptr.use_count() == 1) + { + stuffToSweep.first.push_back( + std::move(cit->second.ptr)); + ++mapRemovals; + cit = partition.erase(cit); + } + else + { + // remains weakly cached + cit->second.ptr.reset(); + ++cit; + } + } + else + { + // strong, not expired + ++cit; + } + } + } + + if (mapRemovals || cacheRemovals) + { + JLOG(m_journal.debug()) + << "TaggedCache partition sweep " << m_name + << ": cache = " << partition.size() << "-" << cacheRemovals + << ", map-=" << mapRemovals; + } + + allRemovals += cacheRemovals; + }); +} + +template < + class Key, + class T, + bool IsKeyCache, + class Hash, + class KeyEqual, + class Mutex> +std::thread +TaggedCache::sweepHelper( + clock_type::time_point const& when_expire, + clock_type::time_point const& now, + typename KeyOnlyCacheType::map_type& partition, + SweptPointersVector&, + std::atomic& allRemovals, + std::lock_guard const&) +{ + return std::thread([&, this]() { + int cacheRemovals = 0; + int mapRemovals = 0; + + // Keep references to all the stuff we sweep + // so that we can destroy them outside the lock. + { + auto cit = partition.begin(); + while (cit != partition.end()) + { + if (cit->second.last_access > now) + { + cit->second.last_access = now; + ++cit; + } + else if (cit->second.last_access <= when_expire) + { + cit = partition.erase(cit); + } + else + { + ++cit; + } + } + } + + if (mapRemovals || cacheRemovals) + { + JLOG(m_journal.debug()) + << "TaggedCache partition sweep " << m_name + << ": cache = " << partition.size() << "-" << cacheRemovals + << ", map-=" << mapRemovals; + } + + allRemovals += cacheRemovals; + }); +} + +} // namespace ripple + +#endif diff --git a/src/test/basics/KeyCache_test.cpp b/src/test/basics/KeyCache_test.cpp index eab0e87d0a7..3a364da6637 100644 --- a/src/test/basics/KeyCache_test.cpp +++ b/src/test/basics/KeyCache_test.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include #include diff --git a/src/test/basics/TaggedCache_test.cpp b/src/test/basics/TaggedCache_test.cpp index 6fbba1a4795..83667a1aa55 100644 --- a/src/test/basics/TaggedCache_test.cpp +++ b/src/test/basics/TaggedCache_test.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include #include diff --git a/src/xrpld/app/ledger/ConsensusTransSetSF.cpp b/src/xrpld/app/ledger/ConsensusTransSetSF.cpp index 4aed7d94bf3..290f4c43bb1 100644 --- a/src/xrpld/app/ledger/ConsensusTransSetSF.cpp +++ b/src/xrpld/app/ledger/ConsensusTransSetSF.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include diff --git a/src/xrpld/app/ledger/LedgerHistory.cpp b/src/xrpld/app/ledger/LedgerHistory.cpp index 30139e2155f..61a49e15f6b 100644 --- a/src/xrpld/app/ledger/LedgerHistory.cpp +++ b/src/xrpld/app/ledger/LedgerHistory.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include diff --git a/src/xrpld/app/ledger/detail/LedgerMaster.cpp b/src/xrpld/app/ledger/detail/LedgerMaster.cpp index d1eeabeb619..ba1cd6f634d 100644 --- a/src/xrpld/app/ledger/detail/LedgerMaster.cpp +++ b/src/xrpld/app/ledger/detail/LedgerMaster.cpp @@ -42,7 +42,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/src/xrpld/app/ledger/detail/TransactionMaster.cpp b/src/xrpld/app/ledger/detail/TransactionMaster.cpp index e2e1213a37e..1f0b0ea0dc8 100644 --- a/src/xrpld/app/ledger/detail/TransactionMaster.cpp +++ b/src/xrpld/app/ledger/detail/TransactionMaster.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include diff --git a/src/xrpld/app/main/Application.cpp b/src/xrpld/app/main/Application.cpp index a9d66679010..2fcca89c645 100644 --- a/src/xrpld/app/main/Application.cpp +++ b/src/xrpld/app/main/Application.cpp @@ -59,6 +59,7 @@ #include #include #include +#include #include #include #include diff --git a/src/xrpld/app/misc/NetworkOPs.cpp b/src/xrpld/app/misc/NetworkOPs.cpp index 02eb0435b57..a9279f9ff4d 100644 --- a/src/xrpld/app/misc/NetworkOPs.cpp +++ b/src/xrpld/app/misc/NetworkOPs.cpp @@ -49,6 +49,7 @@ #include #include #include +#include #include #include #include diff --git a/src/xrpld/app/misc/SHAMapStoreImp.h b/src/xrpld/app/misc/SHAMapStoreImp.h index 7d36f092be8..c9ecb61443c 100644 --- a/src/xrpld/app/misc/SHAMapStoreImp.h +++ b/src/xrpld/app/misc/SHAMapStoreImp.h @@ -26,8 +26,8 @@ #include #include #include - #include +#include #include #include #include diff --git a/src/xrpld/app/rdb/backend/detail/Node.cpp b/src/xrpld/app/rdb/backend/detail/Node.cpp index 2ea6bd12c62..33223cef42a 100644 --- a/src/xrpld/app/rdb/backend/detail/Node.cpp +++ b/src/xrpld/app/rdb/backend/detail/Node.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include diff --git a/src/xrpld/ledger/detail/CachedView.cpp b/src/xrpld/ledger/detail/CachedView.cpp index 5502c40e6d5..79ab13ab2cf 100644 --- a/src/xrpld/ledger/detail/CachedView.cpp +++ b/src/xrpld/ledger/detail/CachedView.cpp @@ -18,6 +18,7 @@ //============================================================================== #include +#include #include #include diff --git a/src/xrpld/nodestore/Database.h b/src/xrpld/nodestore/Database.h index bd25046fee2..706d211785c 100644 --- a/src/xrpld/nodestore/Database.h +++ b/src/xrpld/nodestore/Database.h @@ -23,7 +23,7 @@ #include #include #include -#include +#include #include #include diff --git a/src/xrpld/nodestore/detail/DatabaseNodeImp.cpp b/src/xrpld/nodestore/detail/DatabaseNodeImp.cpp index 85e5d3c0da9..3159219964c 100644 --- a/src/xrpld/nodestore/detail/DatabaseNodeImp.cpp +++ b/src/xrpld/nodestore/detail/DatabaseNodeImp.cpp @@ -18,6 +18,7 @@ //============================================================================== #include +#include #include namespace ripple { diff --git a/src/xrpld/rpc/handlers/GetCounts.cpp b/src/xrpld/rpc/handlers/GetCounts.cpp index 690106ebbd2..5e74c9bd527 100644 --- a/src/xrpld/rpc/handlers/GetCounts.cpp +++ b/src/xrpld/rpc/handlers/GetCounts.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include diff --git a/src/xrpld/shamap/FullBelowCache.h b/src/xrpld/shamap/FullBelowCache.h index eed7e6294a0..d7df9379cf8 100644 --- a/src/xrpld/shamap/FullBelowCache.h +++ b/src/xrpld/shamap/FullBelowCache.h @@ -22,6 +22,7 @@ #include #include +#include #include #include #include From da72b7dd81582223aaa369e1738ed6290dfb3303 Mon Sep 17 00:00:00 2001 From: Scott Determan Date: Wed, 25 Oct 2023 08:42:02 -0400 Subject: [PATCH 2/7] [remove] Scripts to measure and plot memory savings --- draft_pr_remove_me/README.md | 12 + draft_pr_remove_me/memstats.py | 126 + draft_pr_remove_me/memstats2 | 73 + draft_pr_remove_me/notes.md | 12 + draft_pr_remove_me/nov_5/data.raw | 2639 +++++++++++++++++ .../nov_5/mem_diff_intrusive_ptr.png | Bin 0 -> 20068 bytes .../nov_5/mem_percent_change.png | Bin 0 -> 22775 bytes .../nov_5/mem_usage_intrusive_ptr.png | Bin 0 -> 26950 bytes .../oct_25/mem_diff_intrusive_ptr.png | Bin 0 -> 23776 bytes .../oct_25/mem_percent_change.png | Bin 0 -> 22690 bytes .../oct_25/mem_usage_intrusive_ptr.png | Bin 0 -> 24850 bytes .../oct_30/mem_diff_intrusive_ptr.png | Bin 0 -> 23700 bytes .../oct_30/mem_percent_change.png | Bin 0 -> 24496 bytes .../oct_30/mem_usage_intrusive_ptr.png | Bin 0 -> 26289 bytes 14 files changed, 2862 insertions(+) create mode 100644 draft_pr_remove_me/README.md create mode 100644 draft_pr_remove_me/memstats.py create mode 100755 draft_pr_remove_me/memstats2 create mode 100644 draft_pr_remove_me/notes.md create mode 100644 draft_pr_remove_me/nov_5/data.raw create mode 100644 draft_pr_remove_me/nov_5/mem_diff_intrusive_ptr.png create mode 100644 draft_pr_remove_me/nov_5/mem_percent_change.png create mode 100644 draft_pr_remove_me/nov_5/mem_usage_intrusive_ptr.png create mode 100644 draft_pr_remove_me/oct_25/mem_diff_intrusive_ptr.png create mode 100644 draft_pr_remove_me/oct_25/mem_percent_change.png create mode 100644 draft_pr_remove_me/oct_25/mem_usage_intrusive_ptr.png create mode 100644 draft_pr_remove_me/oct_30/mem_diff_intrusive_ptr.png create mode 100644 draft_pr_remove_me/oct_30/mem_percent_change.png create mode 100644 draft_pr_remove_me/oct_30/mem_usage_intrusive_ptr.png diff --git a/draft_pr_remove_me/README.md b/draft_pr_remove_me/README.md new file mode 100644 index 00000000000..c90f31f66d4 --- /dev/null +++ b/draft_pr_remove_me/README.md @@ -0,0 +1,12 @@ +This directory will be removed when the PR is no long a "draft" pr. It contains some plots to compare the memory usage in this patch with the tip of develop as well as some scripts to create those plots. The scripts are tailored to my own setup, but should be straightforward to modify for other people. + +The script `memstats2` collects memory statistics on all running rippled processes, and outputs a file with a head that includes information about the processes and data with columns for: process id, time (in seconds), size of resident memory (in gigabytes), inner node counts, and treenode cache size. The script assumes it is running on a linux system (it uses the `/proc` filesystem) and assumes the `jq` program is available (to parse json responses from rippled). + +The script `memstats.py` is a python program to create plots from the data collected from the `memstat2` script. + +The remaining files are the plots created with `memstats.py` from an overnight run. Two rippleds were started simultaneously on my development machine and used identical config files (other than the location of the database files). The script `memstats2` was started to collect data. After a 12 hour run, the script `memstats.py` was run to create the plots. There are three plots: + +mem_usage_intrusive_ptr.png - This shows the size of resident memory for the two running rippled's. +mem_diff_intrusive_ptr.png - This shows the difference between the size of resident memory between the two running rippled (this shows the memory saving in gigabytes). +mem_percent_change.png - This shows the percent memory savings i.e. `100*(old_code_size-new_code_size)/old_code_size` + diff --git a/draft_pr_remove_me/memstats.py b/draft_pr_remove_me/memstats.py new file mode 100644 index 00000000000..47f944c5fdc --- /dev/null +++ b/draft_pr_remove_me/memstats.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Mon Oct 23 13:18:42 2023 + +@author: swd + +Analyze the memstats2 output +""" + +import pandas as pd +import seaborn as sns +from scipy.signal import medfilt +import matplotlib.pyplot as plt + +data_dir= "/home/swd/memstats/data/nov_5" + +def raw_file_to_df(data_file_name): + n_to_skip = 0 + mine_pid = 0 + with open(data_file_name, 'r') as file: + for line in file: + if (line.startswith('pid')): + break + n_to_skip += 1 + if 'projs/ripple/mine' in line: + mine_pid = int(line.split()[0]) + df = pd.read_csv(data_file_name, header=0, + delimiter=r'\s+', skiprows=n_to_skip) + df['branch'] = 'dev' + df.loc[df['pid'] == mine_pid, 'branch'] = 'intr_ptr' + df['uptime_min'] = (df['time'] - df['time'].iloc[0])/60.0 + df['uptime_hr'] = df['uptime_min']/60 + + return df + + +def get_timescale(df): + if df['uptime_hr'].iloc[-1] < 5: + return 'uptime_min', 'min' + return 'uptime_hr', 'hrs' + + +def plot_df(df, ignore_min=30): + + x_col, units = get_timescale(df) + y_col = 'res_gb' + + sns.set(style="whitegrid") + sns.relplot(kind='line', data=df[df['uptime_min'] + > ignore_min], x=x_col, y=y_col, hue='branch') + + plt.xlabel(f'Up Time ({units})') + plt.ylabel('Resident (gb)') + plt.title('Memory Usage Intrusive Pointer') + plt.subplots_adjust(top=0.9) + plt.savefig(f"{data_dir}/mem_usage_intrusive_ptr.png") + plt.show() + + +def plot_diff(df, filtered=True, ignore_min=30): + x_col, units = get_timescale(df) + diff = pd.DataFrame() + diff['diff'] = df[df['branch'] == 'dev']['res_gb'].values - \ + df[df['branch'] == 'intr_ptr']['res_gb'].values + if filtered: + window_size = 11 + diff['filtered_diff'] = medfilt(diff['diff'], kernel_size=window_size) + diff['uptime'] = df[df['branch'] == 'dev'][x_col].values + diff['uptime_min'] = df[df['branch'] == 'dev']['uptime_min'].values + y_column = 'diff' if not filtered else 'filtered_diff' + + sns.set(style="whitegrid") + sns.relplot( + kind='line', data=diff[diff['uptime_min'] > ignore_min], x='uptime', y=y_column) + + plt.xlabel(f'Up Time ({units})') + plt.ylabel('Delta (gb)') + title = 'Memory Difference Intrusive Pointer' + if filtered: + title += ' (filtered)' + plt.title(title) + + plt.subplots_adjust(top=0.9) + plt.savefig(f"{data_dir}/mem_diff_intrusive_ptr.png") + plt.show() + + +def plot_percent_change(df, filtered=True, ignore_min=30): + x_col, units = get_timescale(df) + diff = pd.DataFrame() + col_name = '% change' + diff[col_name] = 100*(df[df['branch'] == 'dev']['res_gb'].values - + df[df['branch'] == 'intr_ptr']['res_gb'].values) / \ + df[df['branch'] == 'dev']['res_gb'].values + if filtered: + window_size = 11 + diff['filtered_' + + col_name] = medfilt(diff[col_name], kernel_size=window_size) + diff['uptime'] = df[df['branch'] == 'dev'][x_col].values + diff['uptime_min'] = df[df['branch'] == 'dev']['uptime_min'].values + y_column = col_name + if filtered: + y_column = 'filtered_'+col_name + + sns.set(style="whitegrid") + sns.relplot( + kind='line', data=diff[diff['uptime_min'] > ignore_min], x='uptime', y=y_column) + + plt.xlabel(f'Up Time ({units})') + plt.ylabel('% Change (delta/old)') + title = 'Percent Change Memory Intrusive Pointer' + if filtered: + title += ' (filtered)' + plt.title(title) + + plt.subplots_adjust(top=0.9) + plt.savefig(f"{data_dir}/mem_percent_change.png") + plt.show() + +def doit(): + data_file_name = f"{data_dir}/data.raw" + df = raw_file_to_df(data_file_name) + plot_df(df) + plot_diff(df, filtered=True) + plot_percent_change(df, filtered=True) diff --git a/draft_pr_remove_me/memstats2 b/draft_pr_remove_me/memstats2 new file mode 100755 index 00000000000..cce85a45281 --- /dev/null +++ b/draft_pr_remove_me/memstats2 @@ -0,0 +1,73 @@ +#!/usr/bin/env zsh + +while getopts ":p:o:" opt; do + case $opt in + # pids is an array + p) pids=(${OPTARG}) + ;; + o) out=${OPTARG} + ;; + \?) + ;; + esac +done + +if [[ -z $pid ]]; then + # pid is an array + pids=($(pidof rippled)) +fi + +if [[ -z $out ]]; then + echo "Must specify output file" + exit 1 +fi + +get_config(){ + # param is the process id + for i in $(cat /proc/${1}/cmdline | tr '\0' '\n'); do + if [[ $i == *.cfg ]]; then + echo $i + return 0 + fi + done + + echo "Could not parse config file. Exiting" >&2 + exit 1 +} + +page_size=$(getconf -a | grep PAGE_SIZE | awk '{print $2}') + +echo > ${out} +echo >> ${out} +echo "Page size: " ${page_size} >> ${out} + +for pid in $pids[@]; do + printf "%-7d %s\n" ${pid} $(get_config ${pid}) >> ${out} + cmdline=$(tr '\0' ' ' < "/proc/${pid}/cmdline") + printf "%-7d %s\n" ${pid} ${cmdline} >> ${out} + exe=$(ls -l /proc/${pid}/exe) + printf "%-7d %s\n\n" ${pid} ${exe} >> ${out} +done + +echo "\npid time res_gb inner_node_counts treenode_cache_size" >> ${out} +while true; do + for pid in $pids[@]; do + if [[ ! -f /proc/${pid}/statm ]]; then + exit 1 + fi + config=$(get_config ${pid}) + # Set the vars in the to_set collection to each line returned by the command in turn. + to_set=(innerCount cacheSize) + for i in $(/proc/${pid}/exe --conf ${config} -- get_counts 2>/dev/null \ + | jq '.result | ."ripple::SHAMapInnerNode",.treenode_cache_size'); do + eval $to_set[1]=$i + shift to_set + done + + pages=$(cat /proc/${pid}/statm | awk '{print $2}') + gig=1073741824.0 + printf "%-7d %11s %8.3f %-.9d %-.9d\n" ${pid} $(date "+%s") $((pages*page_size/gig)) ${innerCount} ${cacheSize} >> ${out} + echo $(tail -1 ${out}) + done + sleep 30 +done diff --git a/draft_pr_remove_me/notes.md b/draft_pr_remove_me/notes.md new file mode 100644 index 00000000000..bbc9139e25e --- /dev/null +++ b/draft_pr_remove_me/notes.md @@ -0,0 +1,12 @@ +The lock-free part of this code needs to be carefully audited. But note the memory savings do not depend on the lock-free nature. We can re-add the locks and still get the memory savings. + +Making the inner node lock free may actually be _slower_ then using locks. There are operations that now use atomics that didn't use atomics before. This can be addressed by either re-adding the locks or potentially modifying this patch so only code that used to be under locks use atomic operations, and code that was not under locks do not use atomics. + +There is a total of 32-bits used for pointer accounting information: 16-bits for the strong count; 14-bits for the weak count, 1 bit if "partial delete" was started, and 1 bit if "partial delete" has finished. I need to audit the code to make sure these limits are reasonable. The total bits can easily be bumped up to 64 without _too_ much impact on the memory savings if needed. We can also reallocate more bits to the strong count and less bits to the weak count if an audit shows that is better (I suspect there are far fewer weak pointers than strong pointers). + +Much of the complication of the intrusive pointer comes from supporting weak pointers (needed for the tagged cache). If we removed weak pointers from the tagged cache this design gets much simpler. + +This code will need substantially more testing (the unit tests are minimal right now). Unit tests that exercise various threading scenarios are particular important. + +Note that this same technique can be used for other objects current kept in tagged caches. After we do this for inner nodes (this patch) we should see if it makes sense to do it for other objects as well. + diff --git a/draft_pr_remove_me/nov_5/data.raw b/draft_pr_remove_me/nov_5/data.raw new file mode 100644 index 00000000000..7b384f63542 --- /dev/null +++ b/draft_pr_remove_me/nov_5/data.raw @@ -0,0 +1,2639 @@ + + +Page size: 4096 +579123 /home/swd/data/rippled/main.no_shards.dog/rippled.cfg +579123 ./rippled --conf /home/swd/data/rippled/main.no_shards.dog/rippled.cfg +579123 lrwxrwxrwx 1 swd swd 0 Nov 5 18:00 /proc/579123/exe -> /home/swd/projs/ripple/mine/build/gcc.release.nounity/rippled + +579092 /home/swd/data/rippled/main.no_shards.one/rippled.cfg +579092 ./rippled --conf /home/swd/data/rippled/main.no_shards.one/rippled.cfg +579092 lrwxrwxrwx 1 swd swd 0 Nov 5 18:00 /proc/579092/exe -> /home/swd/projs/ripple/ours/build/gcc.release.nounity/rippled + + +pid time res_gb inner_node_counts treenode_cache_size +579123 1699225246 0.129 000000000 000000003 +579092 1699225246 0.128 000000000 000000003 +579123 1699225276 1.005 000369694 000798395 +579092 1699225276 0.890 000370062 000793697 +579123 1699225306 1.974 001200355 002837465 +579092 1699225306 1.993 001206324 002844277 +579123 1699225336 2.306 001834793 004478140 +579092 1699225336 2.631 001816259 004422934 +579123 1699225366 3.240 002414757 006106851 +579092 1699225367 3.683 002435892 006145222 +579123 1699225397 3.725 002989673 007898906 +579092 1699225397 4.283 003048206 008047545 +579123 1699225427 4.570 003487536 009674631 +579092 1699225427 4.836 003502064 009689392 +579123 1699225457 5.614 003853364 011158153 +579092 1699225457 5.539 003853431 011138108 +579123 1699225487 5.687 004127250 012392167 +579092 1699225487 6.128 004115628 012323120 +579123 1699225517 5.697 004259309 013045676 +579092 1699225517 6.309 004245852 012967226 +579123 1699225547 6.491 004466586 014127446 +579092 1699225547 6.945 004397121 013755956 +579123 1699225577 7.049 004685506 015347866 +579092 1699225577 7.745 004618906 014928529 +579123 1699225608 7.636 004894655 016601704 +579092 1699225608 8.186 004822767 016112762 +579123 1699225638 7.820 005046962 017550155 +579092 1699225638 8.421 004965136 016993407 +579123 1699225668 8.404 005173845 018320025 +579092 1699225668 9.042 005083796 017731750 +579123 1699225698 8.476 005344932 019249285 +579092 1699225698 9.145 005229886 018611784 +579123 1699225728 8.540 005424152 015213380 +579092 1699225728 9.588 005368932 015517789 +579123 1699225758 8.543 005432576 015224018 +579092 1699225758 9.622 005417924 015580777 +579123 1699225788 8.544 005439735 015231931 +579092 1699225788 9.631 005425113 015588103 +579123 1699225818 8.548 005448479 015241907 +579092 1699225818 9.643 005433835 015597039 +579123 1699225848 8.551 005453256 008447230 +579092 1699225848 9.657 005410866 008559317 +579123 1699225878 8.554 005463302 008460601 +579092 1699225878 9.660 005420934 008569264 +579123 1699225908 8.634 005616664 008713866 +579092 1699225908 9.891 005591076 008841119 +579123 1699225938 8.644 005624414 008724292 +579092 1699225938 9.913 005601094 008852384 +579123 1699225968 8.655 005625774 004429523 +579092 1699225968 9.931 005611760 004961101 +579123 1699225998 8.656 005636109 004442955 +579092 1699225998 9.933 005622102 004971485 +579123 1699226028 8.660 005646216 004455661 +579092 1699226028 9.937 005632114 004982044 +579123 1699226059 8.662 005655014 004468528 +579092 1699226059 9.938 005640874 004991198 +579123 1699226089 8.666 005664320 000580683 +579092 1699226089 9.941 005648848 001283344 +579123 1699226119 8.668 005672235 000591962 +579092 1699226119 9.942 005658081 001292859 +579123 1699226149 8.669 005681151 000605075 +579092 1699226149 9.947 005667080 001304283 +579123 1699226179 8.672 005690715 000619869 +579092 1699226179 9.951 005676607 001314413 +579123 1699226209 8.675 005699947 000440093 +579092 1699226209 9.952 005225923 000423773 +579123 1699226239 8.678 005708330 000464473 +579092 1699226239 9.953 005242340 000461575 +579123 1699226269 8.682 005718940 000483319 +579092 1699226269 9.954 005256918 000486700 +579123 1699226299 8.686 005729748 000514812 +579092 1699226299 9.955 005275225 000524882 +579123 1699226329 8.691 005582242 000244691 +579092 1699226329 9.977 004811041 000216094 +579123 1699226359 8.696 005594346 000355018 +579092 1699226359 9.979 004823517 000235063 +579123 1699226389 8.705 005607495 000539496 +579092 1699226389 9.981 004852994 000309094 +579123 1699226419 8.710 005617573 000614287 +579092 1699226419 9.983 004876939 000371195 +579123 1699226449 8.748 005073938 000652300 +579092 1699226449 10.003 004556001 000408432 +579123 1699226479 8.755 004194651 000758406 +579092 1699226479 10.007 003990406 000504147 +579123 1699226510 8.763 004227436 000899325 +579092 1699226510 10.012 004039084 000660086 +579123 1699226540 8.772 004260373 001039896 +579092 1699226540 10.015 004068234 000745364 +579123 1699226570 8.779 004291457 001146849 +579092 1699226570 10.018 004098470 000832798 +579123 1699226600 8.793 003906360 001342153 +579092 1699226600 10.039 003912833 000954786 +579123 1699226630 8.806 003954799 001537774 +579092 1699226630 10.044 003962644 001113435 +579123 1699226660 8.820 004005597 001730860 +579092 1699226660 10.052 004011993 001271472 +579123 1699226690 8.874 004131832 002288062 +579092 1699226690 10.169 004248834 002114216 +579123 1699226720 9.379 004647921 005677394 +579092 1699226720 10.636 004876567 005101239 +579123 1699226750 9.758 005411661 008868564 +579092 1699226754 11.620 005544181 007514157 +579123 1699226784 10.449 005586904 009579868 +579092 1699226784 11.620 005555155 007529617 +579123 1699226814 9.872 005597688 009591242 +579092 1699226814 11.130 005532360 007540656 +579123 1699226844 9.903 005548841 009078044 +579092 1699226844 11.133 005481903 007236173 +579123 1699226874 9.905 005559728 009089860 +579092 1699226874 11.135 005492812 007247496 +579123 1699226904 9.907 005571422 009102062 +579092 1699226904 11.136 005504402 007258957 +579123 1699226934 9.910 005582379 009113386 +579092 1699226934 11.137 005515466 007270463 +579123 1699226964 9.911 005524380 008637065 +579092 1699226964 11.138 005493278 006882104 +579123 1699226994 9.911 005534557 008651236 +579092 1699226994 11.141 005503503 006894055 +579123 1699227024 9.911 005544854 008665196 +579092 1699227024 11.141 005513788 006906517 +579123 1699227054 9.911 005555167 008679840 +579092 1699227054 11.141 005524121 006921341 +579123 1699227084 9.915 005531812 007929673 +579092 1699227084 11.141 005500737 006335310 +579123 1699227114 9.917 005540766 007943292 +579092 1699227114 11.141 005509650 006346862 +579123 1699227144 9.918 005550009 007958666 +579092 1699227144 11.141 005518991 006360164 +579123 1699227174 9.921 005558934 007971273 +579092 1699227174 11.141 005527853 006371298 +579123 1699227204 9.925 005534989 006936104 +579092 1699227205 11.143 005534165 003217295 +579123 1699227235 9.925 005543044 006947880 +579092 1699227235 11.143 005542258 003229137 +579123 1699227265 9.928 005551517 006959148 +579092 1699227265 11.146 005550783 003240592 +579123 1699227295 9.929 005561136 006973087 +579092 1699227295 11.146 005560376 003253606 +579123 1699227325 9.929 005568070 000218244 +579092 1699227325 11.147 005569259 000180907 +579123 1699227355 9.933 005578777 000234717 +579092 1699227355 11.147 005580030 000192807 +579123 1699227385 9.934 005587368 000251066 +579092 1699227385 11.147 005588652 000202602 +579123 1699227415 9.935 005596564 000267110 +579092 1699227415 11.147 005597808 000213018 +579123 1699227445 9.938 005607322 000274621 +579092 1699227445 11.148 005087870 000216295 +579123 1699227475 9.943 005616893 000368835 +579092 1699227475 11.151 005113210 000285649 +579123 1699227505 9.947 005626794 000447306 +579092 1699227505 11.152 005137017 000347771 +579123 1699227535 9.953 005635021 000581131 +579092 1699227535 11.154 005173078 000459003 +579123 1699227565 9.956 005120574 000649167 +579092 1699227565 11.158 004645604 000518602 +579123 1699227595 9.957 005150510 000823304 +579092 1699227595 11.160 004677948 000613158 +579123 1699227625 9.968 005182509 000991401 +579092 1699227625 11.165 004720110 000743141 +579123 1699227655 9.976 005208617 001134827 +579092 1699227656 11.169 004756434 000852957 +579123 1699227686 10.025 004614768 001448678 +579092 1699227686 11.199 004637732 001024955 +579123 1699227716 10.072 004754438 002004245 +579092 1699227716 11.301 004786531 001546960 +579123 1699227746 10.165 004869182 002489652 +579092 1699227746 11.495 004925608 002033228 +579123 1699227776 10.259 004964990 002862520 +579092 1699227776 11.652 005018190 002351200 +579123 1699227812 10.308 004958643 003342505 +579092 1699227812 11.749 005026359 002742312 +579123 1699227842 10.353 005080114 003801852 +579092 1699227842 11.847 005123740 003076945 +579123 1699227880 10.385 005168528 004158458 +579092 1699227880 11.951 005223882 003418072 +579123 1699227910 10.813 005262830 004552351 +579092 1699227910 12.152 005239268 003496000 +579123 1699227940 10.481 005244150 004509992 +579092 1699227944 11.913 005331297 003812845 +579123 1699227980 10.551 005336706 004882943 +579092 1699227980 11.949 005422531 004113816 +579123 1699228010 10.782 005443972 005294490 +579092 1699228010 12.214 005504297 004389268 +579123 1699228042 10.682 005525579 005607052 +579092 1699228042 12.281 005520694 004118785 +579123 1699228072 10.741 005571909 005291248 +579092 1699228072 12.683 005572137 004287278 +579123 1699228102 11.323 005612192 005470346 +579092 1699228102 12.683 005580735 004297692 +579123 1699228132 11.323 005621576 005482694 +579092 1699228132 12.144 005524324 004309157 +579123 1699228162 10.759 005629760 005493046 +579092 1699228163 12.145 005525656 003890185 +579123 1699228193 10.764 005536494 004667144 +579092 1699228193 12.145 005533839 003900626 +579123 1699228223 10.765 005546571 004678208 +579092 1699228223 12.147 005543961 003911709 +579123 1699228253 10.765 005556137 004689489 +579092 1699228253 12.148 005553560 003922434 +579123 1699228283 10.768 005566000 004700567 +579092 1699228283 12.148 005563449 003276265 +579123 1699228313 10.769 005569936 004058600 +579092 1699228313 12.148 005571979 003286243 +579123 1699228343 10.775 005582478 004075617 +579092 1699228343 12.148 005584530 003299832 +579123 1699228373 10.776 005591464 004090993 +579092 1699228373 12.149 005593476 003312470 +579123 1699228403 10.776 005601917 004105351 +579092 1699228403 12.150 005603909 002841656 +579123 1699228433 10.777 005611003 003559461 +579092 1699228433 12.150 005613051 002852515 +579123 1699228463 10.777 005620530 003571650 +579092 1699228463 12.150 005622601 002865613 +579123 1699228493 10.777 005627876 003584458 +579092 1699228493 12.151 005629919 002875437 +579123 1699228523 10.778 005635412 003596633 +579092 1699228523 12.151 005637473 000177495 +579123 1699228553 10.778 005641793 001895643 +579092 1699228553 12.151 005644153 000185523 +579123 1699228583 10.778 005649128 001907183 +579092 1699228583 12.151 005651222 000194503 +579123 1699228613 10.778 005656970 001923481 +579092 1699228613 12.151 005659118 000205859 +579123 1699228644 10.778 005666086 001936458 +579092 1699228644 12.152 005668190 000156778 +579123 1699228680 10.781 005672990 000196706 +579092 1699228680 12.246 005677929 000170907 +579123 1699228710 10.877 005683919 000217884 +579092 1699228710 12.427 005686005 000183279 +579123 1699228740 10.984 005692706 000233588 +579092 1699228740 12.388 005694730 000193946 +579123 1699228770 11.032 005700885 000201873 +579092 1699228770 12.465 005341294 000255988 +579123 1699228805 10.863 005706104 000335436 +579092 1699228805 12.445 005386273 000404292 +579123 1699228835 10.866 005717203 000483679 +579092 1699228835 12.454 005427381 000529923 +579123 1699228865 10.981 005727107 000592262 +579092 1699228865 12.451 005458982 000624310 +579123 1699228895 10.979 005449167 000666326 +579092 1699228895 12.443 005371924 000776178 +579123 1699228925 11.005 005488731 001057676 +579092 1699228925 12.463 005441295 001014474 +579123 1699228955 10.854 005531906 001447662 +579092 1699228956 12.467 005519427 001279981 +579123 1699228986 10.874 005582409 001809707 +579092 1699228986 12.669 005559877 001406805 +579123 1699229016 10.875 005560041 002117601 +579092 1699229016 12.913 005558469 001503560 +579123 1699229046 11.423 005600549 002283261 +579092 1699229046 12.913 005569868 001519934 +579123 1699229076 10.844 005609786 002298267 +579092 1699229076 12.331 005519326 001531354 +579123 1699229106 10.886 005619084 002309961 +579092 1699229106 12.331 005528462 001541727 +579123 1699229136 10.890 005533987 002265389 +579092 1699229136 12.332 005524853 001507829 +579123 1699229166 10.890 005544851 002280163 +579092 1699229166 12.333 005535746 001520835 +579123 1699229196 10.890 005555710 002295298 +579092 1699229196 12.333 005546591 001533604 +579123 1699229226 10.890 005564667 002308961 +579092 1699229226 12.333 005555551 001545740 +579123 1699229256 10.890 005563436 002144487 +579092 1699229256 12.333 005565237 001422546 +579123 1699229286 10.890 005573886 002162766 +579092 1699229286 12.333 005575701 001434545 +579123 1699229316 10.890 005584590 002181012 +579092 1699229317 12.333 005586429 001448889 +579123 1699229347 10.890 005594998 002196124 +579092 1699229347 12.334 005596866 001462380 +579123 1699229377 10.890 005604857 001871941 +579092 1699229377 12.334 005606538 001219178 +579123 1699229407 10.890 005612821 001885729 +579092 1699229407 12.334 005614660 001232204 +579123 1699229437 10.892 005621091 001898870 +579092 1699229437 12.334 005622916 001242212 +579123 1699229467 10.894 005630881 001914331 +579092 1699229467 12.334 005632736 001255244 +579123 1699229497 10.894 005640125 000209424 +579092 1699229497 12.334 005641820 000175176 +579123 1699229527 10.894 005648388 000222355 +579092 1699229527 12.334 005650302 000186327 +579123 1699229557 10.894 005658756 000239748 +579092 1699229557 12.334 005660697 000198355 +579123 1699229587 10.894 005666929 000251468 +579092 1699229587 12.334 005668587 000209788 +579123 1699229617 10.894 005674759 000209901 +579092 1699229617 12.334 005676648 000171856 +579123 1699229647 10.906 005683315 000224483 +579092 1699229647 12.334 005685079 000182871 +579123 1699229677 10.915 005691668 000239872 +579092 1699229677 12.334 005692681 000195613 +579123 1699229707 10.915 005699578 000255277 +579092 1699229714 12.392 005700881 000206666 +579123 1699229744 11.160 005710085 000319068 +579092 1699229744 12.506 005190032 000428751 +579123 1699229774 11.168 005719173 000700002 +579092 1699229774 12.551 005247598 000621571 +579123 1699229809 10.993 005726455 000987069 +579092 1699229809 12.552 005341354 000939221 +579123 1699229839 10.999 005736806 001396329 +579092 1699229839 12.581 005405407 001164558 +579123 1699229869 11.002 005373110 001748021 +579092 1699229869 12.606 005309574 001388020 +579123 1699229899 11.240 005472530 002318520 +579092 1699229899 12.638 005436476 001825457 +579123 1699229930 11.008 005530860 002704986 +579092 1699229930 12.990 005518487 002100942 +579123 1699229961 11.008 005601166 003087803 +579092 1699229961 12.678 005587690 002335514 +579123 1699229991 11.595 005600791 003270369 +579092 1699229991 13.100 005562781 002335674 +579123 1699230021 11.595 005607202 003281079 +579092 1699230021 12.516 005503022 002343990 +579123 1699230051 11.013 005614650 003291236 +579092 1699230051 12.538 005510449 002353906 +579123 1699230081 11.013 005623810 003302837 +579092 1699230081 12.539 005519677 002364933 +579123 1699230111 11.014 005524461 003260434 +579092 1699230111 12.539 005511419 002332451 +579123 1699230141 11.014 005536499 003277590 +579092 1699230141 12.539 005523389 002346376 +579123 1699230171 11.014 005543718 003289219 +579092 1699230171 12.539 005530570 002355243 +579123 1699230201 11.014 005551880 003302886 +579092 1699230201 12.539 005538758 002366303 +579123 1699230231 11.014 005547021 002933525 +579092 1699230231 12.539 005548107 002128490 +579123 1699230261 11.014 005555855 002949138 +579092 1699230261 12.539 005556915 002139774 +579123 1699230291 11.014 005564706 002961992 +579092 1699230291 12.539 005565778 002149750 +579123 1699230321 11.014 005572706 002973953 +579092 1699230321 12.539 005573793 002160406 +579123 1699230351 11.014 005581956 002469104 +579092 1699230351 12.539 005582734 001910747 +579123 1699230381 11.014 005592930 002484484 +579092 1699230381 12.539 005594003 001922179 +579123 1699230411 11.014 005606230 002501930 +579092 1699230411 12.539 005607259 001935030 +579123 1699230442 11.014 005621072 002523747 +579092 1699230442 12.539 005622062 001949339 +579123 1699230472 11.014 005638771 000224659 +579092 1699230472 12.540 005639747 000172025 +579123 1699230502 11.014 005654279 000239872 +579092 1699230502 12.542 005654809 000191813 +579123 1699230532 11.014 005666098 000257805 +579092 1699230532 12.542 005667210 000207503 +579123 1699230562 11.014 005680729 000278275 +579092 1699230562 12.542 005681723 000221740 +579123 1699230592 11.016 005692978 000237037 +579092 1699230592 12.542 005694168 000187162 +579123 1699230622 11.018 005706676 000249630 +579092 1699230622 12.542 005707770 000199433 +579123 1699230652 11.020 005721932 000268448 +579092 1699230655 12.543 005721833 000213778 +579123 1699230685 11.026 005735662 000287248 +579092 1699230685 12.684 005739314 000236621 +579123 1699230715 11.028 005750850 000484702 +579092 1699230716 13.031 005363766 000476044 +579123 1699230754 11.034 005766889 000791846 +579092 1699230755 12.750 005431166 000681196 +579123 1699230785 11.476 005787298 001048295 +579092 1699230786 12.755 005495693 000881444 +579123 1699230816 11.056 005805712 001336480 +579092 1699230817 12.715 005547332 001020424 +579123 1699230848 11.062 005507300 001505766 +579092 1699230848 12.931 005551010 001210586 +579123 1699230878 11.204 005585835 001869706 +579092 1699230878 13.009 005602069 001353555 +579123 1699230911 11.066 005651029 002157595 +579092 1699230911 12.753 005653139 001486999 +579123 1699230947 11.066 005706943 002362488 +579092 1699230948 12.959 005659182 001538786 +579123 1699230987 11.132 005720877 002464342 +579092 1699230987 13.338 005690855 001610351 +579123 1699231017 11.719 005740958 002495075 +579092 1699231017 12.754 005625864 001626201 +579123 1699231047 11.134 005759479 002514841 +579092 1699231047 12.754 005644522 001642657 +579123 1699231077 11.135 005657527 002468710 +579092 1699231077 12.754 005633523 001597166 +579123 1699231107 11.135 005670006 002481552 +579092 1699231107 12.754 005645987 001609194 +579123 1699231137 11.136 005684595 002496464 +579092 1699231137 12.754 005660507 001624050 +579123 1699231167 11.136 005696774 002508811 +579092 1699231167 12.754 005672718 001636092 +579123 1699231197 11.140 005686993 002152833 +579092 1699231197 12.754 005688960 001315150 +579123 1699231227 11.141 005700057 002168186 +579092 1699231227 12.754 005702003 001327466 +579123 1699231257 11.141 005716566 002190030 +579092 1699231258 12.754 005718573 001343533 +579123 1699231288 11.142 005729995 002205177 +579092 1699231288 12.754 005732002 001356744 +579123 1699231318 11.147 005742584 001672631 +579092 1699231318 12.756 005744676 000951938 +579123 1699231348 11.147 005756969 001690531 +579092 1699231348 12.760 005759124 000966427 +579123 1699231378 11.147 005769400 001706363 +579092 1699231378 12.761 005771605 000978274 +579123 1699231408 11.202 005782689 001724458 +579092 1699231408 12.762 005784851 000991040 +579123 1699231438 11.203 005795609 001162065 +579092 1699231438 12.763 005797672 000183054 +579123 1699231468 11.204 005808898 001182663 +579092 1699231468 12.763 005810993 000196120 +579123 1699231498 11.204 005822966 001203950 +579092 1699231498 12.764 005824976 000213952 +579123 1699231528 11.204 005834009 001217482 +579092 1699231528 12.764 005835870 000229778 +579123 1699231558 11.204 005847597 000246106 +579092 1699231558 12.765 005849901 000187442 +579123 1699231588 11.204 005860163 000262056 +579092 1699231588 12.766 005861696 000200741 +579123 1699231618 11.204 005872034 000276280 +579092 1699231618 12.767 005874157 000213529 +579123 1699231648 11.204 005885336 000290495 +579092 1699231648 12.778 005887397 000226543 +579123 1699231678 11.230 005898855 000246033 +579092 1699231678 12.781 005643964 000209965 +579123 1699231709 11.240 005910645 000371322 +579092 1699231709 13.040 005669381 000272354 +579123 1699231739 11.280 005922209 000469709 +579092 1699231739 13.082 005696489 000346551 +579123 1699231769 11.464 005934986 000562075 +579092 1699231769 13.097 005721537 000408183 +579123 1699231799 11.432 005690771 000517997 +579092 1699231816 12.911 005669931 000384357 +579123 1699231873 11.292 005731396 000764327 +579092 1699231873 13.058 005711122 000483110 +579123 1699231903 11.582 005764363 000935280 +579092 1699231919 12.879 005734631 000547681 +579123 1699231953 11.292 005695881 001080968 +579092 1699231953 12.882 005708505 000575601 +579123 1699231983 11.444 005737264 001223176 +579092 1699231983 13.078 005729343 000623502 +579123 1699232016 11.292 005776986 001384407 +579092 1699232016 13.131 005750058 000668654 +579123 1699232046 11.883 005767126 001449374 +579092 1699232046 13.471 005716362 000645005 +579123 1699232076 11.886 005777361 001463490 +579092 1699232077 13.474 005726578 000656308 +579123 1699232107 11.304 005789076 001477841 +579092 1699232107 12.890 005668797 000678054 +579123 1699232137 11.300 005801024 001490312 +579092 1699232137 12.890 005680487 000689653 +579123 1699232167 11.304 005690682 001348604 +579092 1699232167 12.892 005691709 000555696 +579123 1699232197 11.306 005701182 001365734 +579092 1699232197 12.859 005702766 000567843 +579123 1699232227 11.306 005711897 001383666 +579092 1699232227 12.863 005713044 000581689 +579123 1699232257 11.306 005720491 001399379 +579092 1699232257 12.864 005721578 000593981 +579123 1699232287 11.306 005725448 001162542 +579092 1699232287 12.864 005726670 000449116 +579123 1699232317 11.308 005731345 001176765 +579092 1699232317 12.864 005732494 000459332 +579123 1699232347 11.308 005739309 001192315 +579092 1699232347 12.864 005740404 000471278 +579123 1699232377 11.309 005747681 001209118 +579092 1699232377 12.865 005748756 000484596 +579123 1699232407 11.313 005755599 000990520 +579092 1699232407 12.868 005756787 000344584 +579123 1699232437 11.314 005763677 001009494 +579092 1699232437 12.869 005764807 000357179 +579123 1699232467 11.315 005772101 001025552 +579092 1699232467 12.872 005773147 000369594 +579123 1699232497 11.315 005780271 001040762 +579092 1699232497 12.874 005781383 000380253 +579123 1699232527 11.315 005788275 000231449 +579092 1699232528 12.876 005789421 000161776 +579123 1699232558 11.315 005796308 000247274 +579092 1699232558 12.876 005797439 000173781 +579123 1699232588 11.315 005800616 000259974 +579092 1699232588 12.876 005803760 000184323 +579123 1699232618 11.315 005805486 000275503 +579092 1699232618 12.876 005807752 000199971 +579123 1699232648 11.315 005811546 000224987 +579092 1699232648 12.877 005811244 000159820 +579123 1699232678 11.324 005815273 000241659 +579092 1699232678 12.880 005815093 000174768 +579123 1699232708 11.417 005820567 000258793 +579092 1699232708 12.895 005821093 000186698 +579123 1699232738 11.359 005824668 000275111 +579092 1699232738 12.895 005826093 000208139 +579123 1699232768 11.361 005833552 000345267 +579092 1699232771 12.903 005831655 000218335 +579123 1699232801 11.364 005840538 000903660 +579092 1699232801 13.241 005474215 000387253 +579123 1699232831 11.366 005844967 001243197 +579092 1699232831 13.073 005507179 000502348 +579123 1699232861 11.532 005850276 001602588 +579092 1699232861 13.100 005554378 000666662 +579123 1699232891 11.551 005476479 001824477 +579092 1699232892 12.925 005598017 000821432 +579123 1699232931 11.371 005571990 002474640 +579092 1699232937 12.926 005592027 000986053 +579123 1699232967 11.371 005647724 003004560 +579092 1699232969 12.926 005638540 001143881 +579123 1699232999 11.648 005708417 003445755 +579092 1699233000 13.511 005666835 001238744 +579123 1699233030 11.955 005678849 003428708 +579092 1699233030 13.511 005631942 001203205 +579123 1699233060 11.371 005682813 003440348 +579092 1699233060 12.928 005567370 001212936 +579123 1699233090 11.371 005690353 003451688 +579092 1699233090 12.928 005574915 001223970 +579123 1699233120 11.371 005695194 003460469 +579092 1699233120 12.928 005579686 001232419 +579123 1699233150 11.372 005593455 003413212 +579092 1699233151 12.928 005584855 001187761 +579123 1699233181 11.372 005599317 003425738 +579092 1699233181 12.928 005590827 001199028 +579123 1699233211 11.372 005604713 003440602 +579092 1699233211 12.928 005596143 001210305 +579123 1699233241 11.372 005609850 003453497 +579092 1699233241 12.928 005601324 001222250 +579123 1699233271 11.372 005605494 003164959 +579092 1699233271 12.928 005606056 001044827 +579123 1699233301 11.372 005610441 003176557 +579092 1699233301 12.928 005611045 001054421 +579123 1699233331 11.372 005617049 003189385 +579092 1699233331 12.928 005617643 001064385 +579123 1699233361 11.372 005625513 003204995 +579092 1699233361 12.928 005626162 001076709 +579123 1699233391 11.372 005631617 002866852 +579092 1699233391 12.928 005631475 000866867 +579123 1699233421 11.372 005635704 002877947 +579092 1699233421 12.928 005636274 000876116 +579123 1699233451 11.372 005640796 002890554 +579092 1699233451 12.930 005641393 000885517 +579123 1699233481 11.372 005644665 002902844 +579092 1699233481 12.930 005645433 000895179 +579123 1699233511 11.372 005650102 000200066 +579092 1699233511 12.930 005650841 000147973 +579123 1699233541 11.372 005656963 000215564 +579092 1699233541 12.930 005657578 000160805 +579123 1699233571 11.372 005661997 000229269 +579092 1699233571 12.930 005662484 000169697 +579123 1699233602 11.372 005669322 000244298 +579092 1699233602 12.930 005671038 000180816 +579123 1699233632 11.372 005674409 000207744 +579092 1699233632 12.930 005675481 000144316 +579123 1699233662 11.426 005681097 000221480 +579092 1699233662 12.932 005681905 000159086 +579123 1699233692 11.514 005687631 000237142 +579092 1699233692 12.932 005688676 000172722 +579123 1699233722 11.695 005695438 000255433 +579092 1699233722 12.981 005696827 000187251 +579123 1699233752 11.834 005704004 000617292 +579092 1699233752 12.984 005364877 000155665 +579123 1699233783 11.419 005708010 001067604 +579092 1699233783 13.161 005208247 000439198 +579123 1699233813 11.595 005718038 001615149 +579092 1699233814 12.997 005285672 000708760 +579123 1699233853 11.427 005723467 002060832 +579092 1699233856 12.999 005382205 001044689 +579123 1699233886 11.578 005423698 002635975 +579092 1699233886 13.155 005392249 001293085 +579123 1699233923 11.392 005498625 002979040 +579092 1699233923 13.204 005475619 001583199 +579123 1699233953 11.571 005579378 003336713 +579092 1699233958 13.004 005545469 001827798 +579123 1699233988 11.976 005583832 003444193 +579092 1699233988 13.590 005578370 001935958 +579123 1699234018 11.394 005589042 003455458 +579092 1699234018 13.009 005491346 001905790 +579123 1699234048 11.398 005595965 003466881 +579092 1699234048 13.042 005498369 001916399 +579123 1699234078 11.398 005603454 003476289 +579092 1699234078 13.043 005505896 001925356 +579123 1699234109 11.398 005519883 003427980 +579092 1699234109 13.046 005514933 001935689 +579123 1699234139 11.398 005529208 003441861 +579092 1699234139 13.047 005520129 001896989 +579123 1699234169 11.398 005538379 003456138 +579092 1699234169 13.047 005529314 001909395 +579123 1699234199 11.398 005544642 003468038 +579092 1699234199 13.049 005535541 001917538 +579123 1699234229 11.398 005541517 003017143 +579092 1699234229 13.049 005542814 001927340 +579123 1699234259 11.398 005548509 003028620 +579092 1699234259 13.051 005549776 001644728 +579123 1699234289 11.398 005556893 003042796 +579092 1699234289 13.053 005558001 001655322 +579123 1699234319 11.398 005564292 003054764 +579092 1699234319 13.053 005565442 001664891 +579123 1699234349 11.398 005574336 002569932 +579092 1699234349 13.053 005575569 001676071 +579123 1699234379 11.398 005585501 002585546 +579092 1699234379 13.053 005586734 001417096 +579123 1699234409 11.398 005595379 002601445 +579092 1699234409 13.053 005596606 001430163 +579123 1699234439 11.398 005606643 002617369 +579092 1699234439 13.053 005607910 001442644 +579123 1699234469 11.398 005614647 000197639 +579092 1699234469 13.053 005615895 001452081 +579123 1699234499 11.398 005625426 000213360 +579092 1699234499 13.053 005626615 000158454 +579123 1699234529 11.399 005635756 000228450 +579092 1699234529 13.055 005637031 000170911 +579123 1699234560 11.400 005645680 000243422 +579092 1699234560 13.055 005646359 000182549 +579123 1699234590 11.400 005654744 000208049 +579092 1699234590 13.055 005656467 000194337 +579123 1699234620 11.400 005663725 000224423 +579092 1699234630 13.057 005664163 000159701 +579123 1699234660 11.506 005674279 000244996 +579092 1699234660 13.089 005675651 000177089 +579123 1699234690 11.595 005683602 000259974 +579092 1699234690 13.146 005684940 000191837 +579123 1699234720 11.623 005692820 000473746 +579092 1699234720 13.149 005693994 000203007 +579123 1699234750 11.644 005701210 000904713 +579092 1699234750 13.150 005319193 000310531 +579123 1699234780 11.655 005710034 001324929 +579092 1699234780 13.150 005384943 000536678 +579123 1699234810 11.664 005718969 001614278 +579092 1699234810 13.150 005437698 000705456 +579123 1699234840 11.727 005351352 001850634 +579092 1699234840 13.449 005484621 000860372 +579123 1699234870 11.513 005391895 002020634 +579092 1699234870 13.176 005475099 001022767 +579123 1699234900 11.656 005469837 002381041 +579092 1699234900 13.332 005519882 001167549 +579123 1699234930 11.822 005539834 002663031 +579092 1699234942 13.180 005580302 001362655 +579123 1699234972 11.519 005596190 003031338 +579092 1699234972 13.717 005558823 001342385 +579123 1699235002 12.101 005606200 003055505 +579092 1699235002 13.134 005494338 001352507 +579123 1699235032 11.565 005615217 003068341 +579092 1699235032 13.135 005503371 001362809 +579123 1699235062 11.579 005625266 003079751 +579092 1699235062 13.135 005513407 001374031 +579123 1699235092 11.579 005529019 003028381 +579092 1699235092 13.135 005513389 001337184 +579123 1699235122 11.580 005537052 003038268 +579092 1699235122 13.135 005521388 001346306 +579123 1699235152 11.581 005544652 003047561 +579092 1699235152 13.135 005528985 001355338 +579123 1699235182 11.581 005554049 003059586 +579092 1699235182 13.135 005538330 001366251 +579123 1699235213 11.582 005546493 002744756 +579092 1699235213 13.135 005547614 001170511 +579123 1699235243 11.582 005553667 002757196 +579092 1699235243 13.135 005554772 001178663 +579123 1699235273 11.582 005561641 002770392 +579092 1699235273 13.135 005562758 001187641 +579123 1699235303 11.582 005571077 002785143 +579092 1699235303 13.135 005572160 001199859 +579123 1699235333 11.582 005580025 002299548 +579092 1699235333 13.154 005581085 000997458 +579123 1699235363 11.582 005589532 002314297 +579092 1699235363 13.181 005590592 001010357 +579123 1699235393 11.582 005597606 002329262 +579092 1699235393 13.201 005598679 001020158 +579123 1699235423 11.582 005607888 002343143 +579092 1699235423 13.209 005608971 001035319 +579123 1699235453 11.582 005616673 000192052 +579092 1699235453 13.246 005617717 000149513 +579123 1699235483 11.582 005624189 000203962 +579092 1699235483 13.253 005625215 000158366 +579123 1699235513 11.582 005631574 000219406 +579092 1699235513 13.292 005632660 000167494 +579123 1699235543 11.582 005640857 000233216 +579092 1699235543 13.292 005641914 000179594 +579123 1699235573 11.582 005650623 000208625 +579092 1699235573 13.312 005651616 000154603 +579123 1699235603 11.582 005658724 000221641 +579092 1699235603 13.318 005659729 000165248 +579123 1699235633 11.586 005666684 000234941 +579092 1699235633 13.363 005667718 000178593 +579123 1699235664 11.586 005674486 000249937 +579092 1699235664 13.399 005675440 000191765 +579123 1699235698 11.540 005682049 000434491 +579092 1699235698 13.456 005239381 000197902 +579123 1699235737 11.576 005692946 000765832 +579092 1699235740 13.473 005306670 000420284 +579123 1699235774 11.750 005705691 001063083 +579092 1699235774 13.662 005392365 000706947 +579123 1699235804 11.718 005358412 001225658 +579092 1699235809 13.518 005429029 000826899 +579123 1699235839 11.974 005417475 001617954 +579092 1699235839 13.945 005420386 000953658 +579123 1699235869 11.800 005469927 001946631 +579092 1699235869 13.748 005473506 001133213 +579123 1699235899 11.789 005532073 002325787 +579092 1699235899 13.760 005519924 001283024 +579123 1699235929 11.898 005486779 002597558 +579092 1699235929 13.899 005572025 001455667 +579123 1699235959 11.625 005572424 002935280 +579092 1699235959 14.197 005551025 001443762 +579123 1699235989 12.165 005594259 003015882 +579092 1699235989 13.615 005496605 001453489 +579123 1699236019 11.583 005602057 003026378 +579092 1699236019 13.641 005504438 001462528 +579123 1699236049 11.583 005516675 002980525 +579092 1699236049 13.646 005511071 001470695 +579123 1699236079 11.583 005525048 002991074 +579092 1699236079 13.648 005505772 001431920 +579123 1699236109 11.583 005533375 003001610 +579092 1699236110 13.650 005514119 001442814 +579123 1699236140 11.583 005541611 003012268 +579092 1699236140 13.650 005522401 001452905 +579123 1699236170 11.583 005527752 002718589 +579092 1699236170 13.650 005528938 001462019 +579123 1699236200 11.583 005534057 002730517 +579092 1699236200 13.650 005535319 001293775 +579123 1699236230 11.583 005542347 002744439 +579092 1699236230 13.650 005543565 001304872 +579123 1699236260 11.583 005550264 002757798 +579092 1699236260 13.650 005551542 001315280 +579123 1699236290 11.583 005558559 002312186 +579092 1699236290 13.650 005559895 001326408 +579123 1699236320 11.583 005565757 002324120 +579092 1699236320 13.650 005567056 001106868 +579123 1699236350 11.583 005573141 002335056 +579092 1699236350 13.650 005574426 001115129 +579123 1699236380 11.583 005580663 002349687 +579092 1699236380 13.650 005581942 001124532 +579123 1699236410 11.583 005587981 000774985 +579092 1699236410 13.650 005589241 001134838 +579123 1699236440 11.583 005596304 000786983 +579092 1699236440 13.650 005597533 000143294 +579123 1699236470 11.583 005605192 000800481 +579092 1699236470 13.652 005606588 000153563 +579123 1699236500 11.583 005612710 000810955 +579092 1699236500 13.654 005613858 000162121 +579123 1699236530 11.583 005622506 000174357 +579092 1699236530 13.654 005623960 000173176 +579123 1699236561 11.583 005630824 000187070 +579092 1699236561 13.654 005632521 000140217 +579123 1699236591 11.583 005639053 000201159 +579092 1699236591 13.654 005640334 000150243 +579123 1699236622 11.583 005647067 000215266 +579092 1699236622 13.654 005649460 000160987 +579123 1699236657 11.673 005655739 000209715 +579092 1699236657 13.837 005634277 000136306 +579123 1699236687 11.787 005664042 000473781 +579092 1699236691 13.689 005271617 000322933 +579123 1699236721 11.673 005672590 000723818 +579092 1699236721 13.731 005330980 000523445 +579123 1699236758 11.673 005679795 001007171 +579092 1699236759 13.689 005405146 000769411 +579123 1699236789 11.676 005386939 001204291 +579092 1699236794 13.689 005353613 000862150 +579123 1699236824 11.868 005427967 001473568 +579092 1699236824 13.874 005409284 001047023 +579123 1699236854 11.827 005483839 001813564 +579092 1699236854 13.689 005461009 001218293 +579123 1699236885 11.880 005528401 002096402 +579092 1699236887 13.689 005499511 001347954 +579123 1699236930 11.629 005541222 002644743 +579092 1699236930 13.848 005503413 001509798 +579123 1699236960 12.267 005582238 002853191 +579092 1699236960 14.276 005546688 001651171 +579123 1699236991 11.685 005590016 002865263 +579092 1699236991 14.276 005554577 001661354 +579123 1699237021 11.727 005508072 002822159 +579092 1699237021 13.695 005485793 001628452 +579123 1699237051 11.729 005517300 002834075 +579092 1699237051 13.696 005495058 001639341 +579123 1699237081 11.729 005524970 002844017 +579092 1699237081 13.696 005502706 001648675 +579123 1699237111 11.730 005532797 002853900 +579092 1699237111 13.696 005510725 001658311 +579123 1699237141 11.730 005515862 002607646 +579092 1699237141 13.696 005517385 001408972 +579123 1699237171 11.730 005523829 002623315 +579092 1699237171 13.696 005525276 001418478 +579123 1699237201 11.730 005532576 002639395 +579092 1699237202 13.696 005534083 001433600 +579123 1699237232 11.730 005539823 002652468 +579092 1699237232 13.696 005541372 001443119 +579123 1699237262 11.730 005547759 002320815 +579092 1699237262 13.696 005549368 001236186 +579123 1699237292 11.730 005556120 002332954 +579092 1699237292 13.696 005557757 001249359 +579123 1699237322 11.730 005564211 002348570 +579092 1699237322 13.696 005565848 001260740 +579123 1699237352 11.730 005572130 002361871 +579092 1699237352 13.696 005573787 001271689 +579123 1699237382 11.730 005578646 002066788 +579092 1699237382 13.696 005580309 001097870 +579123 1699237412 11.730 005587160 002077999 +579092 1699237412 13.696 005589097 001108600 +579123 1699237442 11.730 005593475 002088416 +579092 1699237442 13.696 005595099 001116750 +579123 1699237472 11.730 005599964 002098423 +579092 1699237472 13.698 005601615 001125198 +579123 1699237502 11.730 005607082 000187544 +579092 1699237502 13.700 005608651 000139301 +579123 1699237532 11.730 005613964 000199496 +579092 1699237532 13.700 005615437 000147704 +579123 1699237562 11.730 005620652 000210752 +579092 1699237562 13.700 005621935 000156703 +579123 1699237592 11.742 005628258 000225413 +579092 1699237592 13.700 005629983 000169044 +579123 1699237629 11.749 005634360 000249002 +579092 1699237629 13.703 005254602 000198293 +579123 1699237662 11.749 005643681 000374302 +579092 1699237663 13.955 005328772 000451558 +579123 1699237693 11.772 005652944 000495459 +579092 1699237693 13.810 005387689 000654659 +579123 1699237735 11.783 005660087 000602617 +579092 1699237740 13.811 005441775 000830559 +579123 1699237770 11.919 005450203 001083910 +579092 1699237770 13.954 005335358 001071868 +579123 1699237806 11.781 005486223 001486745 +579092 1699237806 13.886 005407944 001310410 +579123 1699237836 11.851 005521155 001823055 +579092 1699237836 13.986 005466559 001511584 +579123 1699237866 11.928 005442077 002142524 +579092 1699237875 13.827 005528547 001724536 +579123 1699237905 11.930 005549966 002597164 +579092 1699237912 14.351 005523501 001818387 +579123 1699237942 12.363 005571690 002662477 +579092 1699237942 14.366 005534090 001832921 +579123 1699237972 11.782 005580801 002674432 +579092 1699237972 13.790 005486477 001843642 +579123 1699238002 11.782 005499739 002639211 +579092 1699238002 13.792 005477546 001817475 +579123 1699238032 11.782 005509277 002651236 +579092 1699238032 13.792 005487119 001828766 +579123 1699238062 11.782 005517811 002661652 +579092 1699238062 13.792 005495621 001838942 +579123 1699238093 11.782 005526047 002671635 +579092 1699238093 13.792 005503717 001848129 +579123 1699238123 11.782 005511304 002513619 +579092 1699238123 13.792 005512879 001681336 +579123 1699238153 11.782 005519719 002527038 +579092 1699238153 13.792 005521239 001691637 +579123 1699238183 11.782 005527687 002538841 +579092 1699238183 13.792 005529216 001702053 +579123 1699238213 11.782 005536040 002551144 +579092 1699238213 13.792 005537788 001712872 +579123 1699238243 11.782 005544279 002156811 +579092 1699238243 13.792 005545812 001475794 +579123 1699238273 11.782 005552023 002169129 +579092 1699238273 13.792 005553538 001485457 +579123 1699238303 11.782 005560147 002181478 +579092 1699238303 13.792 005561587 001495202 +579123 1699238333 11.782 005567144 002192396 +579092 1699238333 13.792 005568547 001504327 +579123 1699238363 11.782 005574400 001566841 +579092 1699238363 13.792 005575792 000248273 +579123 1699238393 11.782 005582134 001581690 +579092 1699238393 13.792 005583528 000260531 +579123 1699238423 11.782 005589494 001597068 +579092 1699238423 13.792 005590689 000269757 +579123 1699238453 11.782 005596348 001609137 +579092 1699238453 13.792 005597642 000279711 +579123 1699238483 11.782 005603930 000187802 +579092 1699238483 13.792 005605034 000140824 +579123 1699238513 11.782 005610798 000200306 +579092 1699238513 13.792 005612186 000150637 +579123 1699238544 11.782 005617700 000213150 +579092 1699238544 13.792 005619121 000159563 +579123 1699238579 11.782 005624053 000226961 +579092 1699238579 13.875 005627681 000174570 +579123 1699238611 11.782 005632390 000435160 +579092 1699238611 13.828 005167328 000200144 +579123 1699238641 11.741 005642491 000751099 +579092 1699238641 13.828 005223797 000386037 +579123 1699238679 11.746 005649425 001004058 +579092 1699238679 14.021 005311293 000686594 +579123 1699238709 11.972 005302394 001334277 +579092 1699238717 13.828 005365510 000872752 +579123 1699238747 11.774 005380431 001918474 +579092 1699238748 14.113 005311852 001133447 +579123 1699238778 11.749 005433039 002408394 +579092 1699238784 13.828 005402539 001448555 +579123 1699238826 11.779 005495953 003016559 +579092 1699238830 13.839 005511769 001819190 +579123 1699238860 11.976 005370089 003441184 +579092 1699238860 14.420 005510719 001902802 +579123 1699238890 12.180 005519893 004047198 +579092 1699238890 14.420 005519915 001916076 +579123 1699238920 12.353 005566021 004213276 +579092 1699238920 13.839 005471370 001925277 +579123 1699238950 11.773 005483226 004171004 +579092 1699238950 13.839 005479399 001934781 +579123 1699238980 11.780 005491739 004570666 +579092 1699238981 13.839 005466558 001904040 +579123 1699239011 11.781 005499260 004580011 +579092 1699239011 13.839 005474053 001913261 +579123 1699239041 11.781 005506732 004588549 +579092 1699239041 13.839 005481123 001921003 +579123 1699239071 11.781 005488320 004283201 +579092 1699239071 13.839 005489187 001930580 +579123 1699239101 11.781 005498012 004297463 +579092 1699239101 13.839 005498873 001821736 +579123 1699239131 11.781 005506620 004311628 +579092 1699239131 13.839 005507475 001832527 +579123 1699239161 11.782 005515147 004324128 +579092 1699239161 13.839 005515991 001842800 +579123 1699239191 11.782 005524315 003842765 +579092 1699239191 13.839 005525159 001852975 +579123 1699239221 11.782 005533175 003856263 +579092 1699239221 13.839 005534037 001504284 +579123 1699239251 11.783 005541184 003868309 +579092 1699239251 13.839 005542015 001514582 +579123 1699239281 11.783 005548751 003879922 +579092 1699239281 13.839 005549552 001523979 +579123 1699239311 11.783 005556905 003364247 +579092 1699239311 13.839 005557646 001533725 +579123 1699239341 11.783 005564402 003375751 +579092 1699239341 13.839 005565170 000138802 +579123 1699239371 11.783 005572995 003388033 +579092 1699239371 13.839 005573755 000148508 +579123 1699239401 11.783 005580785 003400471 +579092 1699239401 13.839 005581493 000158311 +579123 1699239432 11.783 005588584 000182367 +579092 1699239432 13.839 005588959 000166713 +579123 1699239462 11.783 005597012 000195565 +579092 1699239462 13.839 005597558 000140282 +579123 1699239492 11.783 005604319 000208739 +579092 1699239492 13.839 005605016 000150074 +579123 1699239522 11.783 005612558 000223756 +579092 1699239522 13.839 005613218 000161869 +579123 1699239556 11.785 005618994 000235813 +579092 1699239556 13.917 005621449 000178720 +579123 1699239586 11.785 005629097 000199978 +579092 1699239586 14.084 005067002 000330484 +579123 1699239616 11.852 005637491 000214665 +579092 1699239617 13.945 005146050 000603068 +579123 1699239647 11.852 005647392 000230246 +579092 1699239647 14.115 005233821 000894386 +579123 1699239677 11.852 005552319 000187221 +579092 1699239684 13.953 005296987 001108909 +579123 1699239721 11.856 005563269 000839594 +579092 1699239721 13.953 005234600 001417497 +579123 1699239751 12.319 005575942 001318452 +579092 1699239752 13.953 005336071 001757787 +579123 1699239782 12.198 005584844 001840300 +579092 1699239782 13.953 005414613 002022855 +579123 1699239821 11.860 005441018 002241410 +579092 1699239829 13.953 005490674 002425886 +579123 1699239860 12.605 005571589 002763754 +579092 1699239860 14.490 005534960 002562563 +579123 1699239890 12.462 005580986 002778601 +579092 1699239890 13.908 005487283 002572410 +579123 1699239920 11.880 005589742 002790878 +579092 1699239920 13.908 005496092 002582727 +579123 1699239950 11.864 005508121 002753655 +579092 1699239950 13.908 005480947 002553000 +579123 1699239980 11.864 005517898 002765203 +579092 1699239980 13.908 005490698 002564083 +579123 1699240010 11.864 005526238 002775665 +579092 1699240010 13.908 005499008 002573546 +579123 1699240040 11.864 005535568 002787011 +579092 1699240040 13.908 005508303 002584520 +579123 1699240070 11.864 005515674 002738445 +579092 1699240071 13.908 005516383 002304319 +579123 1699240101 11.864 005524895 002753545 +579092 1699240101 13.908 005525585 002315105 +579123 1699240131 11.864 005532994 002767619 +579092 1699240131 13.908 005533358 002324031 +579123 1699240161 11.864 005542695 002785146 +579092 1699240161 13.908 005543344 002335502 +579123 1699240191 11.864 005549891 002388728 +579092 1699240191 13.908 005550512 001959281 +579123 1699240221 11.864 005556446 002399414 +579092 1699240221 13.908 005556997 001967313 +579123 1699240251 11.864 005565810 002412989 +579092 1699240251 13.908 005566377 001977479 +579123 1699240281 11.864 005578327 002429336 +579092 1699240281 13.908 005578809 001990752 +579123 1699240311 11.864 005586880 002067990 +579092 1699240311 13.908 005587402 000143141 +579123 1699240341 11.864 005597822 002084086 +579092 1699240341 13.908 005597286 000154849 +579123 1699240371 11.864 005605804 002096712 +579092 1699240371 13.908 005606270 000168539 +579123 1699240401 11.864 005614848 002113752 +579092 1699240401 13.908 005615400 000187708 +579123 1699240431 11.864 005624325 000217535 +579092 1699240431 13.909 005624864 000157234 +579123 1699240461 11.864 005636301 000232960 +579092 1699240461 13.909 005636786 000171113 +579123 1699240491 11.864 005648033 000250887 +579092 1699240491 13.909 005647253 000182564 +579123 1699240527 11.872 005655470 000264582 +579092 1699240531 13.909 005656724 000195622 +579123 1699240561 11.825 005668248 000508642 +579092 1699240561 13.909 005140469 000446917 +579123 1699240591 11.870 005676193 000793262 +579092 1699240591 13.988 005232840 000764139 +579123 1699240621 11.871 005685876 000951732 +579092 1699240621 13.988 005284512 000932062 +579123 1699240651 11.871 005388811 001043918 +579092 1699240651 14.141 005350995 001152546 +579123 1699240681 11.871 005425188 001539945 +579092 1699240681 14.131 005268343 001423990 +579123 1699240711 11.871 005486470 002065587 +579092 1699240711 13.958 005369805 001763475 +579123 1699240741 11.871 005538321 002484949 +579092 1699240747 13.958 005465179 002086581 +579123 1699240783 11.832 005450370 002912172 +579092 1699240783 14.158 005486464 002263825 +579123 1699240814 11.844 005533162 003240926 +579092 1699240817 14.540 005555603 002501380 +579123 1699240847 12.439 005605384 003538724 +579092 1699240847 14.488 005567200 002517071 +579123 1699240877 11.856 005612672 003552254 +579092 1699240877 13.906 005511509 002527150 +579123 1699240907 11.871 005523100 003501831 +579092 1699240907 13.906 005485716 002488928 +579123 1699240937 11.873 005530508 003511460 +579092 1699240937 13.906 005493152 002498615 +579123 1699240967 11.874 005540091 003522477 +579092 1699240967 13.906 005502768 002508937 +579123 1699240997 11.877 005549452 003533756 +579092 1699240998 13.906 005512164 002519391 +579123 1699241028 11.877 005520602 003294115 +579092 1699241028 13.906 005521342 002253486 +579123 1699241058 11.877 005530306 003308563 +579092 1699241058 13.906 005531052 002263653 +579123 1699241088 11.877 005539805 003321972 +579092 1699241088 13.906 005540572 002273785 +579123 1699241118 11.877 005548867 003333984 +579092 1699241118 13.906 005549688 002283569 +579123 1699241148 11.877 005556903 002735428 +579092 1699241148 13.906 005557701 001899450 +579123 1699241178 11.877 005564657 002749261 +579092 1699241178 13.906 005565477 001909368 +579123 1699241208 11.877 005573253 002762726 +579092 1699241208 13.906 005574051 001920303 +579123 1699241238 11.877 005582600 002777303 +579092 1699241238 13.906 005583379 001930906 +579123 1699241268 11.877 005591331 002346105 +579092 1699241268 13.906 005592176 000304803 +579123 1699241298 11.877 005600167 002358217 +579092 1699241298 13.906 005600915 000313744 +579123 1699241328 11.877 005611560 002375590 +579092 1699241328 13.906 005612469 000325730 +579123 1699241358 11.877 005620141 002390389 +579092 1699241358 13.906 005621098 000336543 +579123 1699241388 11.877 005628369 000201362 +579092 1699241388 13.908 005629355 000139850 +579123 1699241418 11.877 005637109 000218434 +579092 1699241418 13.908 005638042 000149993 +579123 1699241449 11.877 005644617 000233481 +579092 1699241449 13.908 005644951 000158401 +579123 1699241479 11.877 005651708 000246492 +579092 1699241479 13.908 005652774 000167884 +579123 1699241513 11.877 005658823 000207732 +579092 1699241521 13.917 005057563 000216928 +579123 1699241551 11.885 005671566 000229862 +579092 1699241551 13.945 005143076 000502602 +579123 1699241581 11.897 005678964 000244968 +579092 1699241581 13.953 005199884 000695996 +579123 1699241611 11.897 005686153 000256947 +579092 1699241611 13.953 005263309 000911160 +579123 1699241641 11.899 005589262 000701912 +579092 1699241643 13.954 005041141 001234191 +579123 1699241676 11.899 005596021 001080601 +579092 1699241676 14.137 005147711 001593910 +579123 1699241706 11.899 005607996 001319664 +579092 1699241706 14.114 005245531 001928338 +579123 1699241736 11.900 005351431 001663870 +579092 1699241741 13.988 005345005 002275980 +579123 1699241780 11.900 005362173 002185253 +579092 1699241780 14.091 005427446 002689625 +579123 1699241810 11.900 005457478 002577898 +579092 1699241819 14.011 005513704 002983648 +579123 1699241854 11.900 005568790 002992349 +579092 1699241855 14.566 005578612 003191118 +579123 1699241885 12.490 005591088 003168899 +579092 1699241885 14.566 005556222 003160356 +579123 1699241915 11.908 005598506 003180196 +579092 1699241915 13.983 005504062 003168975 +579123 1699241945 11.913 005606332 003189606 +579092 1699241945 13.983 005511925 003177939 +579123 1699241975 11.918 005614042 003198937 +579092 1699241975 13.983 005519593 003187029 +579123 1699242005 11.918 005530768 003158121 +579092 1699242005 13.983 005527558 003031995 +579123 1699242035 11.918 005537882 003167838 +579092 1699242035 13.983 005534640 003041259 +579123 1699242065 11.918 005544803 003180463 +579092 1699242065 13.983 005541615 003052471 +579123 1699242095 11.918 005552231 003189733 +579092 1699242095 13.983 005549021 003061441 +579123 1699242125 11.918 005555256 002855574 +579092 1699242125 13.983 005556124 002606197 +579123 1699242155 11.918 005562866 002867527 +579092 1699242155 13.983 005563819 002615564 +579123 1699242185 11.918 005569273 002875136 +579092 1699242185 13.983 005570154 002623142 +579123 1699242215 11.918 005577291 002885752 +579092 1699242215 13.983 005578235 002634185 +579123 1699242245 11.918 005584728 002423389 +579092 1699242245 13.983 005585715 002180407 +579123 1699242275 11.918 005591765 002437193 +579092 1699242275 13.983 005592789 002191782 +579123 1699242306 11.918 005598318 002449283 +579092 1699242306 13.983 005599333 002201187 +579123 1699242336 11.918 005606609 002462042 +579092 1699242336 13.985 005607623 002211562 +579123 1699242366 11.918 005616259 000176726 +579092 1699242366 13.985 005617263 000145158 +579123 1699242396 11.918 005623420 000190259 +579092 1699242396 13.985 005624641 000154687 +579123 1699242426 11.918 005631975 000205978 +579092 1699242426 13.985 005633182 000165568 +579123 1699242456 11.918 005640213 000220395 +579092 1699242456 13.985 005641374 000177233 +579123 1699242486 11.918 005644060 000184869 +579092 1699242486 14.017 005648146 000150496 +579123 1699242519 11.931 005653150 000203530 +579092 1699242519 14.038 005656604 000165769 +579123 1699242549 11.932 005662462 000224273 +579092 1699242549 14.097 005663466 000177765 +579123 1699242579 12.087 005670216 000239992 +579092 1699242579 14.099 005671005 000190119 +579123 1699242609 11.933 005677350 000612308 +579092 1699242609 14.099 005051028 000304805 +579123 1699242640 12.108 005685730 001109946 +579092 1699242647 14.104 005179288 000757839 +579123 1699242677 12.106 005695325 001524358 +579092 1699242677 14.104 005274408 001080584 +579123 1699242707 12.114 005360942 001809041 +579092 1699242712 14.104 005374578 001431369 +579123 1699242742 12.136 005492180 002477663 +579092 1699242742 14.105 005388130 001695089 +579123 1699242772 12.127 005557469 002857932 +579092 1699242772 14.105 005481815 002013423 +579123 1699242803 12.527 005596281 003185393 +579092 1699242803 14.161 005537364 002198404 +579123 1699242833 12.527 005575002 003139901 +579092 1699242833 14.689 005572357 002307017 +579123 1699242863 11.945 005584608 003151285 +579092 1699242863 14.107 005491219 002274827 +579123 1699242893 11.945 005593549 003160954 +579092 1699242893 14.110 005500235 002284484 +579123 1699242923 11.945 005602960 003173310 +579092 1699242923 14.110 005509609 002295485 +579123 1699242953 11.945 005520526 003126471 +579092 1699242953 14.113 005518017 002305552 +579123 1699242983 11.945 005530627 003141411 +579092 1699242983 14.113 005524343 002265358 +579123 1699243013 11.945 005539264 003155896 +579092 1699243013 14.113 005533390 002277383 +579123 1699243043 11.945 005548070 003169776 +579092 1699243043 14.113 005542177 002287876 +579123 1699243073 11.945 005550005 002809887 +579092 1699243073 14.113 005551030 002299862 +579123 1699243103 11.945 005559410 002825751 +579092 1699243103 14.113 005560568 002038672 +579123 1699243133 11.945 005567042 002838858 +579092 1699243133 14.113 005568203 002049134 +579123 1699243163 11.945 005575289 002851973 +579092 1699243163 14.113 005576406 002058929 +579123 1699243193 11.945 005582715 002408708 +579092 1699243193 14.113 005583803 002069667 +579123 1699243223 11.945 005593009 002424607 +579092 1699243224 14.113 005594179 001716413 +579123 1699243254 11.945 005603897 002437103 +579092 1699243254 14.113 005605050 001728951 +579123 1699243284 11.945 005615136 002453935 +579092 1699243284 14.113 005616328 001742337 +579123 1699243314 11.945 005626056 000214876 +579092 1699243314 14.113 005626782 001754953 +579123 1699243344 11.945 005635803 000229089 +579092 1699243344 14.113 005636868 000165700 +579123 1699243374 11.945 005644751 000240042 +579092 1699243374 14.114 005645938 000175694 +579123 1699243404 11.945 005655139 000255307 +579092 1699243404 14.114 005656182 000186454 +579123 1699243434 11.945 005664067 000213974 +579092 1699243434 14.114 005664670 000198733 +579123 1699243464 11.968 005672214 000229110 +579092 1699243464 14.114 005673341 000164489 +579123 1699243495 11.968 005679000 000241469 +579092 1699243495 14.209 005682727 000180924 +579123 1699243525 11.968 005690318 000262198 +579092 1699243525 14.297 005691598 000194960 +579123 1699243555 11.929 005697253 000441502 +579092 1699243555 14.313 005698799 000203988 +579123 1699243585 11.929 005706963 000753755 +579092 1699243585 14.354 005075394 000357664 +579123 1699243615 11.930 005716207 001017318 +579092 1699243616 14.192 005176389 000709641 +579123 1699243649 11.930 005724582 001339037 +579092 1699243655 14.192 005283345 001072882 +579123 1699243689 11.945 005335195 001747249 +579092 1699243689 14.192 005359543 001319591 +579123 1699243719 12.129 005443378 002184213 +579092 1699243719 14.369 005412192 001729470 +579123 1699243759 11.965 005545462 002634401 +579092 1699243763 14.192 005513587 002074889 +579123 1699243793 11.966 005547947 002797151 +579092 1699243793 14.776 005600199 002368344 +579123 1699243823 12.548 005618584 003075889 +579092 1699243823 14.777 005578601 002338560 +579123 1699243853 11.966 005626586 003088705 +579092 1699243853 14.194 005518054 002347849 +579123 1699243883 11.966 005636112 003099422 +579092 1699243883 14.194 005527663 002358398 +579123 1699243913 11.966 005546246 003046651 +579092 1699243913 14.194 005538408 002369947 +579123 1699243943 11.966 005554316 003056139 +579092 1699243943 14.194 005538435 002332405 +579123 1699243973 11.966 005564163 003067821 +579092 1699243973 14.194 005548273 002344294 +579123 1699244003 11.966 005574406 003081100 +579092 1699244003 14.194 005558540 002356273 +579123 1699244033 11.966 005567913 002832356 +579092 1699244033 14.194 005569142 002368881 +579123 1699244063 11.966 005576584 002844343 +579092 1699244063 14.194 005577865 002136272 +579123 1699244093 11.966 005586728 002859075 +579092 1699244093 14.194 005587963 002149511 +579123 1699244123 11.966 005597166 002871837 +579092 1699244123 14.194 005598480 002162588 +579123 1699244153 11.966 005606264 002435729 +579092 1699244154 14.194 005607563 002173100 +579123 1699244184 11.966 005616415 002452848 +579092 1699244184 14.194 005617663 001942138 +579123 1699244214 11.966 005627409 002469497 +579092 1699244214 14.194 005628676 001953890 +579123 1699244244 11.966 005637395 002485786 +579092 1699244244 14.194 005638644 001967529 +579123 1699244274 11.966 005646847 000190189 +579092 1699244274 14.194 005648122 001977621 +579123 1699244304 11.966 005656493 000203133 +579092 1699244304 14.194 005657786 000161969 +579123 1699244334 11.966 005665448 000220350 +579092 1699244334 14.194 005666628 000174112 +579123 1699244364 11.966 005674129 000234136 +579092 1699244364 14.194 005675389 000184135 +579123 1699244394 11.966 005685208 000205766 +579092 1699244394 14.194 005685649 000195143 +579123 1699244424 11.966 005692762 000220161 +579092 1699244424 14.194 005694332 000164522 +579123 1699244454 11.990 005700966 000233228 +579092 1699244454 14.194 005702254 000174474 +579123 1699244487 11.966 005708735 000245388 +579092 1699244487 14.194 005712418 000189246 +579123 1699244518 11.966 005719027 000251872 +579092 1699244518 14.194 005721229 000201037 +579123 1699244553 12.033 005728074 000634500 +579092 1699244554 14.194 005103185 000295061 +579123 1699244587 12.037 005736905 000945802 +579092 1699244587 14.366 005218113 000686265 +579123 1699244617 12.037 005746226 001195924 +579092 1699244623 14.194 005305455 000990904 +579123 1699244653 12.352 005415329 001504851 +579092 1699244653 14.202 005380079 001240081 +579123 1699244686 12.037 005453851 001741915 +579092 1699244686 14.388 005379169 001481349 +579123 1699244716 12.220 005510071 002016406 +579092 1699244716 14.413 005478863 001831083 +579123 1699244746 12.245 005567844 002379702 +579092 1699244748 14.203 005553681 002091682 +579123 1699244789 12.037 005576719 002716370 +579092 1699244789 14.822 005577527 002242987 +579123 1699244819 12.620 005631271 002910905 +579092 1699244819 14.822 005585863 002253656 +579123 1699244849 12.037 005639331 002923658 +579092 1699244849 14.239 005527019 002263474 +579123 1699244879 12.037 005648143 002934552 +579092 1699244879 14.239 005535837 002274407 +579123 1699244909 12.037 005554895 002881959 +579092 1699244909 14.239 005532733 002242049 +579123 1699244939 12.037 005565311 002895952 +579092 1699244939 14.239 005543173 002255629 +579123 1699244969 12.037 005574357 002907223 +579092 1699244969 14.239 005552244 002266283 +579123 1699244999 12.037 005583142 002918038 +579092 1699244999 14.239 005561027 002277975 +579123 1699245029 12.037 005567729 002606362 +579092 1699245030 14.239 005567946 002063787 +579123 1699245060 12.037 005574974 002619932 +579092 1699245060 14.239 005575184 002073671 +579123 1699245090 12.037 005581840 002633683 +579092 1699245090 14.239 005582020 002084739 +579123 1699245120 12.038 005588470 002643498 +579092 1699245120 14.239 005588684 002093907 +579123 1699245150 12.038 005596690 002282800 +579092 1699245150 14.239 005596990 001883286 +579123 1699245180 12.038 005604082 002297619 +579092 1699245180 14.239 005604331 001894949 +579123 1699245210 12.038 005611851 002309828 +579092 1699245210 14.239 005612123 001907533 +579123 1699245240 12.038 005619457 002322095 +579092 1699245240 14.239 005619636 001917411 +579123 1699245270 12.038 005627849 001252101 +579092 1699245270 14.239 005628101 000150480 +579123 1699245300 12.038 005635989 001264078 +579092 1699245300 14.239 005636307 000162345 +579123 1699245330 12.038 005647135 001279641 +579092 1699245330 14.239 005647359 000174216 +579123 1699245360 12.038 005655302 001293970 +579092 1699245360 14.239 005655567 000184306 +579123 1699245390 12.038 005663636 000199699 +579092 1699245390 14.239 005663977 000149976 +579123 1699245420 12.038 005672181 000214396 +579092 1699245420 14.239 005672552 000162354 +579123 1699245458 12.038 005677202 000224825 +579092 1699245458 14.239 005679867 000172818 +579123 1699245488 12.031 005685187 000193249 +579092 1699245488 14.239 005685434 000185301 +579123 1699245518 12.126 005692655 000219562 +579092 1699245518 14.444 005140169 000275550 +579123 1699245553 12.047 005700196 000235871 +579092 1699245553 14.482 005249754 000652369 +579123 1699245583 12.047 005711048 000265267 +579092 1699245583 14.472 005318636 000885130 +579123 1699245613 12.047 005580476 000344317 +579092 1699245613 14.549 005368830 001054172 +579123 1699245643 12.047 005593044 000825236 +579092 1699245644 14.318 005302900 001197545 +579123 1699245679 12.047 005601172 001295203 +579092 1699245679 14.528 005422586 001611908 +579123 1699245709 12.047 005612292 001653422 +579092 1699245719 14.318 005525683 001970659 +579123 1699245749 12.212 005394234 002098331 +579092 1699245749 14.687 005577236 002139967 +579123 1699245779 12.288 005512391 002652641 +579092 1699245779 14.901 005556242 002134507 +579123 1699245811 12.640 005604729 003072058 +579092 1699245811 14.318 005501169 002143567 +579123 1699245842 12.640 005613740 003085234 +579092 1699245842 14.293 005508705 002153157 +579123 1699245872 12.065 005522807 003533586 +579092 1699245872 14.293 005515712 002162411 +579123 1699245902 12.065 005530547 003543658 +579092 1699245902 14.293 005505927 002128598 +579123 1699245932 12.065 005536138 003552560 +579092 1699245932 14.293 005511349 002137778 +579123 1699245962 12.065 005543157 003561903 +579092 1699245962 14.293 005518514 002147967 +579123 1699245992 12.065 005525517 003508036 +579092 1699245992 14.293 005526095 002158063 +579123 1699246022 12.065 005532818 003520502 +579092 1699246022 14.293 005533379 001906620 +579123 1699246052 12.065 005541139 003538128 +579092 1699246052 14.293 005541675 001918485 +579123 1699246082 12.065 005548263 003552514 +579092 1699246082 14.293 005548753 001929498 +579123 1699246112 12.065 005554868 003249999 +579092 1699246112 14.293 005555399 001941079 +579123 1699246142 12.065 005561962 003261384 +579092 1699246142 14.293 005562475 001726855 +579123 1699246172 12.065 005570502 003275708 +579092 1699246172 14.293 005571011 001737638 +579123 1699246202 12.065 005579137 003287736 +579092 1699246202 14.293 005579615 001748049 +579123 1699246232 12.065 005587693 002973977 +579092 1699246232 14.293 005588123 001760417 +579123 1699246262 12.065 005594539 002986902 +579092 1699246262 14.293 005594944 000150825 +579123 1699246293 12.065 005602348 002999760 +579092 1699246293 14.293 005602827 000160953 +579123 1699246323 12.065 005611251 003014147 +579092 1699246323 14.293 005611791 000171563 +579123 1699246353 12.065 005619785 000202300 +579092 1699246353 14.293 005620467 000181665 +579123 1699246383 12.065 005627463 000217528 +579092 1699246383 14.293 005627938 000151340 +579123 1699246413 12.065 005634969 000229802 +579092 1699246413 14.293 005635607 000162082 +579123 1699246443 12.065 005639484 000239278 +579092 1699246443 14.293 005640400 000172733 +579123 1699246473 12.158 005647029 000198760 +579092 1699246473 14.293 005647709 000182245 +579123 1699246503 12.194 005655361 000214856 +579092 1699246509 14.276 005152743 000426910 +579123 1699246539 12.065 005665513 000232877 +579092 1699246540 14.413 005231705 000695310 +579123 1699246575 12.065 005671530 000247492 +579092 1699246575 14.596 005314596 000978474 +579123 1699246605 12.226 005573563 000274655 +579092 1699246616 14.301 005382052 001212880 +579123 1699246646 12.220 005584562 000391116 +579092 1699246646 14.463 005348293 001384149 +579123 1699246683 12.065 005591250 000479924 +579092 1699246683 14.505 005451232 001738791 +579123 1699246714 12.207 005524409 000524098 +579092 1699246714 14.453 005502965 001916007 +579123 1699246744 12.273 005544763 000591186 +579092 1699246754 14.302 005524423 002081385 +579123 1699246784 12.340 005576552 000701071 +579092 1699246784 14.883 005538696 002113536 +579123 1699246814 12.647 005587564 000727644 +579092 1699246814 14.302 005484677 002121885 +579123 1699246844 12.066 005561804 000692867 +579092 1699246844 14.302 005492819 002131628 +579123 1699246874 12.072 005569217 000976979 +579092 1699246874 14.302 005482494 002099829 +579123 1699246904 12.072 005577998 000988043 +579092 1699246904 14.302 005491263 002110273 +579123 1699246934 12.072 005585620 000997950 +579092 1699246934 14.302 005498952 002119659 +579123 1699246964 12.072 005505211 000947586 +579092 1699246964 14.302 005505760 002127820 +579123 1699246994 12.072 005512556 000962768 +579092 1699246994 14.302 005513082 001930503 +579123 1699247024 12.072 005520083 000976861 +579092 1699247024 14.302 005520619 001940734 +579123 1699247054 12.072 005527710 000988231 +579092 1699247054 14.302 005528191 001951384 +579123 1699247084 12.072 005535194 000934878 +579092 1699247084 14.302 005535717 001964756 +579123 1699247114 12.072 005542007 000950347 +579092 1699247114 14.302 005542426 001637495 +579123 1699247144 12.072 005548694 000965242 +579092 1699247144 14.302 005549161 001647674 +579123 1699247174 12.072 005555344 000974667 +579092 1699247174 14.302 005555848 001655437 +579123 1699247205 12.072 005563158 000899503 +579092 1699247205 14.302 005563652 001665829 +579123 1699247235 12.072 005571134 000911632 +579092 1699247235 14.302 005571643 000144773 +579123 1699247265 12.072 005579525 000926634 +579092 1699247265 14.302 005580227 000154639 +579123 1699247295 12.072 005586583 000938901 +579092 1699247295 14.302 005587193 000163230 +579123 1699247325 12.072 005593039 000190640 +579092 1699247325 14.302 005593737 000171564 +579123 1699247355 12.072 005598948 000201577 +579092 1699247355 14.302 005599672 000145108 +579123 1699247385 12.072 005605519 000215439 +579092 1699247385 14.302 005606344 000156341 +579123 1699247421 12.072 005611414 000227997 +579092 1699247421 14.302 005614357 000172018 +579123 1699247451 12.072 005619949 000189159 +579092 1699247451 14.395 005620756 000183475 +579123 1699247481 12.072 005627361 000203137 +579092 1699247481 14.471 005041224 000373357 +579123 1699247511 12.076 005634418 000217580 +579092 1699247511 14.524 005140109 000716549 +579123 1699247541 12.076 005642017 000233597 +579092 1699247545 14.324 005217137 000984051 +579123 1699247575 12.181 005545200 000593807 +579092 1699247575 14.506 005310907 001298459 +579123 1699247605 12.035 005553040 001019737 +579092 1699247605 14.326 005152695 001614810 +579123 1699247635 12.290 005559345 001478712 +579092 1699247635 14.603 005295705 002120211 +579123 1699247676 12.089 005566822 001857889 +579092 1699247680 14.334 005413940 002529665 +579123 1699247710 12.343 005446609 002297247 +579092 1699247710 14.674 005471537 002814525 +579123 1699247740 12.268 005531945 002650449 +579092 1699247740 14.923 005513923 002952512 +579123 1699247770 12.665 005561410 002755979 +579092 1699247770 14.342 005466608 002961307 +579123 1699247800 12.084 005481356 002718532 +579092 1699247800 14.342 005474381 002970777 +579123 1699247830 12.084 005490069 002729777 +579092 1699247830 14.342 005464156 002934686 +579123 1699247860 12.084 005499429 002740585 +579092 1699247860 14.342 005473529 002945110 +579123 1699247890 12.084 005507371 002750658 +579092 1699247890 14.342 005481468 002954529 +579123 1699247920 12.084 005488990 002708816 +579092 1699247920 14.342 005489735 002964582 +579123 1699247950 12.084 005497801 002727382 +579092 1699247950 14.342 005498543 002723684 +579123 1699247980 12.084 005506961 002742833 +579092 1699247980 14.342 005507643 002735921 +579123 1699248010 12.084 005516569 002754821 +579092 1699248010 14.342 005517331 002747088 +579123 1699248040 12.084 005524367 002446344 +579092 1699248040 14.342 005525209 002756208 +579123 1699248070 12.084 005533999 002460630 +579092 1699248070 14.342 005534848 002495177 +579123 1699248100 12.084 005543173 002474634 +579092 1699248101 14.342 005544024 002507963 +579123 1699248131 12.084 005551029 002486350 +579092 1699248131 14.342 005551913 002520576 +579123 1699248161 12.084 005559357 002063082 +579092 1699248161 14.342 005560213 002533964 +579123 1699248191 12.084 005567410 002076254 +579092 1699248191 14.342 005568252 000157071 +579123 1699248221 12.084 005577614 002091097 +579092 1699248221 14.342 005578449 000170403 +579123 1699248251 12.084 005587068 002105206 +579092 1699248251 14.342 005587801 000182966 +579123 1699248281 12.084 005598069 000195589 +579092 1699248281 14.342 005598666 000196671 +579123 1699248311 12.084 005607197 000210495 +579092 1699248311 14.342 005608214 000166839 +579123 1699248341 12.084 005617418 000228437 +579092 1699248341 14.342 005617941 000179942 +579123 1699248371 12.084 005625658 000244419 +579092 1699248371 14.342 005626189 000193728 +579123 1699248407 12.086 005631989 000256222 +579092 1699248407 14.342 005635223 000209501 +579123 1699248437 12.175 005643657 000216110 +579092 1699248437 14.342 005091593 000264757 +579123 1699248467 12.224 005651431 000231052 +579092 1699248469 14.382 005169418 000533959 +579123 1699248499 12.084 005660489 000250130 +579092 1699248499 14.793 005264214 000855828 +579123 1699248529 12.084 005572112 000206810 +579092 1699248529 14.581 005293949 000951539 +579123 1699248560 12.157 005575356 000511911 +579092 1699248560 14.453 005193017 001104402 +579123 1699248595 12.085 005588114 000976210 +579092 1699248595 14.384 005291516 001445229 +579123 1699248625 12.334 005598836 001344118 +579092 1699248628 14.386 005374888 001731468 +579123 1699248659 12.128 005366234 001805613 +579092 1699248659 14.542 005439881 001942608 +579123 1699248689 12.280 005438507 002081258 +579092 1699248689 14.386 005441155 002083496 +579123 1699248719 12.085 005517828 002380999 +579092 1699248720 14.595 005532764 002382704 +579123 1699248750 12.667 005594678 002680061 +579092 1699248750 14.969 005550503 002432575 +579123 1699248780 12.667 005567921 002630662 +579092 1699248780 14.386 005496524 002442479 +579123 1699248810 12.085 005575616 002641460 +579092 1699248810 14.386 005474120 002536323 +579123 1699248840 12.085 005583256 002651459 +579092 1699248840 14.386 005481810 002545776 +579123 1699248870 12.085 005591284 002661747 +579092 1699248870 14.386 005489860 002555478 +579123 1699248900 12.085 005497157 002616719 +579092 1699248900 14.386 005497482 002564645 +579123 1699248930 12.085 005504836 002630568 +579092 1699248930 14.386 005505176 002343366 +579123 1699248960 12.085 005511832 002644119 +579092 1699248960 14.386 005512159 002353884 +579123 1699248990 12.085 005519432 002658320 +579092 1699248990 14.386 005519750 002363296 +579123 1699249020 12.085 005526263 002294477 +579092 1699249020 14.386 005527090 002373133 +579123 1699249050 12.085 005533796 002306752 +579092 1699249050 14.386 005534628 002001234 +579123 1699249080 12.085 005541310 002319962 +579092 1699249080 14.387 005542101 002010246 +579123 1699249110 12.085 005549742 002333124 +579092 1699249110 14.387 005550577 002021283 +579123 1699249140 12.085 005556712 001977948 +579092 1699249140 14.387 005557625 002029539 +579123 1699249170 12.085 005564869 001990856 +579092 1699249171 14.387 005565797 001752225 +579123 1699249201 12.085 005573627 002003948 +579092 1699249201 14.387 005574504 001762843 +579123 1699249231 12.085 005582726 002018386 +579092 1699249231 14.387 005583587 001775123 +579123 1699249261 12.085 005591106 000192889 +579092 1699249261 14.387 005592020 001784990 +579123 1699249291 12.085 005600436 000208686 +579092 1699249291 14.387 005601516 000143220 +579123 1699249321 12.085 005609122 000222888 +579092 1699249321 14.387 005610209 000152550 +579123 1699249351 12.085 005618299 000238572 +579092 1699249351 14.387 005619328 000162829 +579123 1699249383 12.085 005624992 000252874 +579092 1699249383 14.387 005628701 000177858 +579123 1699249413 12.176 005636937 000215061 +579092 1699249419 14.389 005530700 000150916 +579123 1699249449 12.113 005649711 000237505 +579092 1699249451 14.389 005540470 000165728 +579123 1699249481 12.392 005658399 000252920 +579092 1699249481 14.390 005550912 000180519 +579123 1699249511 12.439 005560929 000274916 +579092 1699249512 14.390 005559754 000193781 +579123 1699249542 12.113 005570473 000748015 +579092 1699249551 14.390 005163920 000414902 +579123 1699249581 12.353 005581799 001286847 +579092 1699249581 14.390 005240045 000672504 +579123 1699249611 12.114 005593411 001656377 +579092 1699249612 14.390 005325354 000948724 +579123 1699249642 12.356 005397286 002078285 +579092 1699249642 14.624 005338221 001196589 +579123 1699249672 12.115 005479528 002421312 +579092 1699249672 14.631 005438667 001540187 +579123 1699249702 12.115 005571843 002795792 +579092 1699249707 14.414 005532118 001861768 +579123 1699249737 12.697 005602801 002885210 +579092 1699249737 14.997 005567593 001963024 +579123 1699249767 12.115 005581574 002837279 +579092 1699249767 14.414 005447338 001934597 +579123 1699249797 12.115 005592312 002849851 +579092 1699249797 14.418 005500733 002103503 +579123 1699249827 12.115 005603220 002862730 +579092 1699249827 14.419 005511651 002115605 +579123 1699249857 12.115 005611974 002872789 +579092 1699249857 14.419 005520439 002124972 +579123 1699249887 12.115 005531396 002822625 +579092 1699249887 14.420 005531303 002081102 +579123 1699249917 12.115 005544934 002841248 +579092 1699249917 14.420 005544854 002094499 +579123 1699249947 12.115 005556717 002858522 +579092 1699249947 14.420 005556587 002106763 +579123 1699249977 12.115 005566519 002871611 +579092 1699249977 14.420 005566382 002116874 +579123 1699250007 12.115 005576965 002580227 +579092 1699250007 14.420 005578288 001937124 +579123 1699250038 12.115 005588155 002596455 +579092 1699250038 14.420 005589461 001948798 +579123 1699250068 12.115 005599027 002612268 +579092 1699250068 14.420 005600380 001961391 +579123 1699250098 12.115 005608658 002627618 +579092 1699250098 14.420 005610051 001972410 +579123 1699250128 12.115 005617007 002153772 +579092 1699250128 14.420 005618426 001569099 +579123 1699250158 12.115 005626736 002169488 +579092 1699250158 14.420 005628131 001580055 +579123 1699250188 12.115 005638055 002187501 +579092 1699250188 14.423 005639387 001592097 +579123 1699250218 12.115 005649371 002201775 +579092 1699250218 14.423 005650775 001603853 +579123 1699250248 12.115 005658788 000226045 +579092 1699250248 14.423 005660026 000160975 +579123 1699250278 12.115 005668540 000241567 +579092 1699250278 14.423 005670054 000172132 +579123 1699250308 12.115 005677862 000259330 +579092 1699250308 14.423 005679441 000185237 +579123 1699250338 12.115 005687244 000275303 +579092 1699250338 14.423 005688808 000196157 +579123 1699250372 12.115 005693462 000223903 +579092 1699250372 14.504 005579910 000160842 +579123 1699250402 12.282 005703991 000243142 +579092 1699250402 14.630 005588221 000172215 +579123 1699250432 12.398 005712628 000257797 +579092 1699250432 14.756 005596788 000183671 +579123 1699250462 12.236 005721006 000272283 +579092 1699250462 14.597 005605103 000194009 +579123 1699250492 12.502 005616855 000386694 +579092 1699250492 14.618 005400011 000157929 +579123 1699250522 12.317 005625669 000876853 +579092 1699250528 14.450 005311696 000495106 +579123 1699250558 12.582 005638016 001318676 +579092 1699250559 14.673 005391793 000762147 +579123 1699250591 12.124 005646732 001597354 +579092 1699250591 14.656 005469676 001019446 +579123 1699250621 12.303 005492009 002029143 +579092 1699250622 14.655 005481188 001234797 +579123 1699250658 12.121 005570105 002378846 +579092 1699250663 14.469 005580310 001570407 +579123 1699250693 12.708 005644064 002684292 +579092 1699250693 15.052 005595866 001601908 +579123 1699250723 12.125 005614157 002639492 +579092 1699250723 14.469 005535831 001612686 +579123 1699250753 12.125 005623324 002650495 +579092 1699250753 14.469 005512116 001578849 +579123 1699250783 12.125 005634301 002662592 +579092 1699250783 14.469 005523119 001590866 +579123 1699250813 12.125 005643813 002673078 +579092 1699250813 14.469 005532641 001600625 +579123 1699250843 12.125 005548290 002631532 +579092 1699250843 14.469 005543885 001612326 +579123 1699250873 12.125 005558620 002648024 +579092 1699250873 14.469 005554167 001580874 +579123 1699250903 12.125 005570223 002663324 +579092 1699250903 14.469 005565805 001593372 +579123 1699250933 12.125 005581184 002678952 +579092 1699250933 14.469 005576744 001606871 +579123 1699250964 12.125 005583408 002386497 +579092 1699250964 14.469 005584573 001615561 +579123 1699250994 12.125 005592264 002401129 +579092 1699250994 14.469 005593478 001428093 +579123 1699251024 12.125 005601233 002416806 +579092 1699251024 14.469 005602492 001440128 +579123 1699251054 12.125 005609892 002430757 +579092 1699251054 14.469 005611154 001450690 +579123 1699251084 12.125 005618865 002124212 +579092 1699251084 14.469 005620114 001462959 +579123 1699251114 12.125 005626611 002138217 +579092 1699251114 14.469 005627863 000779943 +579123 1699251144 12.125 005636363 002154023 +579092 1699251144 14.469 005637616 000791352 +579123 1699251174 12.128 005646637 002167408 +579092 1699251174 14.469 005647903 000802396 +579123 1699251204 12.128 005657659 000220518 +579092 1699251204 14.469 005658550 000814703 +579123 1699251234 12.128 005666981 000238648 +579092 1699251234 14.469 005668162 000160913 +579123 1699251264 12.129 005676175 000255177 +579092 1699251264 14.469 005677350 000172320 +579123 1699251294 12.129 005683414 000268251 +579092 1699251294 14.469 005684455 000183452 +579123 1699251324 12.129 005689626 000216580 +579092 1699251324 14.469 005690278 000191206 +579123 1699251359 12.133 005697569 000229988 +579092 1699251359 14.540 005218288 000209785 +579123 1699251389 12.133 005708929 000248395 +579092 1699251389 14.677 005284731 000430304 +579123 1699251419 12.144 005716562 000268156 +579092 1699251419 14.781 005354790 000671048 +579123 1699251449 12.170 005726006 000260875 +579092 1699251449 14.662 005393198 000792489 +579123 1699251479 12.352 005734770 000701062 +579092 1699251479 14.649 005154481 001050550 +579123 1699251509 12.145 005743799 001064430 +579092 1699251509 14.497 005224549 001289507 +579123 1699251539 12.171 005752730 001357486 +579092 1699251539 14.561 005289005 001507015 +579123 1699251571 12.148 005758594 001619141 +579092 1699251573 14.497 005366807 001778663 +579123 1699251603 12.149 005389835 001960350 +579092 1699251603 14.500 005377147 002062841 +579123 1699251633 12.299 005485203 002415438 +579092 1699251640 14.500 005483366 002433824 +579123 1699251670 12.346 005575039 002827239 +579092 1699251670 14.500 005561983 002704938 +579123 1699251700 12.270 005599842 003052302 +579092 1699251700 15.084 005607635 002851199 +579123 1699251730 12.731 005617303 003104550 +579092 1699251730 15.084 005579122 002820947 +579123 1699251760 12.149 005627101 003118932 +579092 1699251760 14.500 005523033 002832947 +579123 1699251790 12.149 005634919 003128863 +579092 1699251790 14.500 005530902 002842268 +579123 1699251820 12.149 005540617 003081602 +579092 1699251820 14.500 005537677 002852043 +579123 1699251850 12.149 005548391 003094586 +579092 1699251850 14.500 005541017 002656265 +579123 1699251880 12.149 005556479 003107030 +579092 1699251880 14.500 005549140 002668092 +579123 1699251910 12.149 005565334 003119686 +579092 1699251910 14.500 005558076 002678282 +579123 1699251940 12.149 005564977 002800671 +579092 1699251940 14.500 005566111 002688994 +579123 1699251970 12.149 005573683 002814308 +579092 1699251970 14.500 005574863 002378669 +579123 1699252000 12.149 005582489 002829960 +579092 1699252001 14.500 005583717 002390605 +579123 1699252031 12.149 005589677 002844998 +579092 1699252031 14.500 005590890 002402029 +579123 1699252061 12.149 005597075 002373998 +579092 1699252061 14.500 005598412 002414819 +579123 1699252091 12.149 005604527 002388713 +579092 1699252091 14.500 005605703 001992415 +579123 1699252121 12.151 005611834 002402272 +579092 1699252121 14.500 005612969 002002309 +579123 1699252151 12.151 005619970 002416198 +579092 1699252151 14.500 005621064 002012467 +579123 1699252181 12.151 005627479 000196505 +579092 1699252181 14.500 005628571 002023133 +579123 1699252211 12.151 005635495 000212029 +579092 1699252211 14.500 005636618 000162907 +579123 1699252241 12.151 005642584 000226128 +579092 1699252241 14.500 005643634 000171998 +579123 1699252271 12.151 005650553 000240911 +579092 1699252271 14.500 005651759 000182811 +579123 1699252301 12.151 005658655 000206615 +579092 1699252301 14.500 005659795 000193125 +579123 1699252331 12.151 005667179 000225966 +579092 1699252331 14.500 005668261 000163296 +579123 1699252361 12.151 005673912 000237914 +579092 1699252361 14.500 005674873 000172367 +579123 1699252391 12.151 005681588 000253142 +579092 1699252391 14.503 005681792 000185417 +579123 1699252421 12.151 005690114 000293607 +579092 1699252421 14.505 005691094 000199430 +579123 1699252452 12.151 005698990 000812681 +579092 1699252452 14.523 005172906 000355979 +579123 1699252482 12.321 005707431 001139731 +579092 1699252482 14.545 005276950 000712493 +579123 1699252512 12.330 005715605 001477709 +579092 1699252513 14.531 005327438 000881070 +579123 1699252555 12.136 005269527 001827949 +579092 1699252555 14.734 005387607 001305686 +579123 1699252585 12.137 005345199 002220264 +579092 1699252585 14.551 005462390 001564547 +579123 1699252615 12.137 005445342 002733819 +579092 1699252615 14.550 005534409 001816199 +579123 1699252645 12.183 005542542 003224263 +579092 1699252645 15.141 005569823 001922658 +579123 1699252675 12.137 005577105 003572326 +579092 1699252675 14.559 005484901 001892274 +579123 1699252705 12.718 005591170 003602770 +579092 1699252705 14.559 005493661 001903491 +579123 1699252735 12.137 005600874 003616115 +579092 1699252735 14.559 005503412 001914795 +579123 1699252765 12.137 005610042 003626265 +579092 1699252765 14.559 005512653 001924746 +579123 1699252795 12.137 005527424 003577457 +579092 1699252795 14.560 005512583 001893172 +579123 1699252825 12.137 005536907 003588762 +579092 1699252826 14.560 005522038 001905292 +579123 1699252856 12.137 005548855 003601694 +579092 1699252856 14.561 005533908 001917477 +579123 1699252886 12.137 005559560 003613611 +579092 1699252886 14.561 005544659 001927973 +579123 1699252916 12.137 005554343 003164118 +579092 1699252916 14.561 005555458 001693970 +579123 1699252946 12.137 005564440 003179321 +579092 1699252946 14.561 005565589 001705335 +579123 1699252976 12.137 005572904 003192541 +579092 1699252976 14.562 005574034 001717611 +579123 1699253006 12.137 005584455 003213091 +579092 1699253006 14.563 005585680 001731100 +579123 1699253036 12.137 005594151 002759688 +579092 1699253036 14.563 005595403 001485069 +579123 1699253066 12.137 005604553 002776926 +579092 1699253066 14.563 005605777 001496680 +579123 1699253096 12.137 005614167 002790386 +579092 1699253096 14.563 005615157 001506097 +579123 1699253126 12.137 005625307 002804651 +579092 1699253126 14.563 005626715 001517606 +579123 1699253156 12.137 005635544 000215336 +579092 1699253156 14.563 005636690 000156314 +579123 1699253186 12.137 005645690 000230078 +579092 1699253186 14.563 005647091 000169166 +579123 1699253216 12.137 005655881 000246368 +579092 1699253216 14.564 005657366 000179761 +579123 1699253246 12.137 005666579 000260608 +579092 1699253246 14.565 005667869 000191112 +579123 1699253277 12.137 005675591 000226886 +579092 1699253277 14.565 005578406 000153375 +579123 1699253307 12.137 005685629 000241539 +579092 1699253307 14.565 005588355 000164220 +579123 1699253337 12.137 005695952 000254230 +579092 1699253342 14.566 005596216 000173670 +579123 1699253372 12.214 005707728 000273466 +579092 1699253372 14.566 005610490 000195247 +579123 1699253402 12.347 005718234 000388770 +579092 1699253402 14.582 005275222 000207946 +579123 1699253432 12.453 005729246 000582257 +579092 1699253433 14.582 005327980 000372592 +579123 1699253463 12.461 005739908 000810009 +579092 1699253463 14.582 005378192 000534670 +579123 1699253493 12.306 005747567 000992755 +579092 1699253493 14.582 005428818 000702763 +579123 1699253530 12.137 005445069 001197431 +579092 1699253530 14.937 005420497 000852621 +579123 1699253560 12.313 005505771 001508443 +579092 1699253560 14.960 005480524 001055123 +579123 1699253590 12.372 005561160 001858132 +579092 1699253590 14.952 005540017 001260880 +579123 1699253623 12.139 005613252 002122385 +579092 1699253623 14.824 005589300 001423701 +579123 1699253653 12.285 005602076 002292422 +579092 1699253653 14.819 005573612 001456339 +579123 1699253683 12.721 005626827 002387067 +579092 1699253683 15.203 005582671 001469129 +579123 1699253713 12.139 005635976 002400943 +579092 1699253713 14.621 005524002 001479603 +579123 1699253743 12.139 005644943 002412299 +579092 1699253743 14.621 005533025 001490219 +579123 1699253773 12.139 005546989 002362039 +579092 1699253773 14.621 005524523 001451513 +579123 1699253803 12.139 005555405 002372571 +579092 1699253803 14.621 005532976 001461271 +579123 1699253833 12.139 005562914 002382144 +579092 1699253833 14.621 005540482 001470778 +579123 1699253863 12.139 005571248 002392018 +579092 1699253863 14.621 005548829 001480413 +579123 1699253893 12.139 005554633 002153485 +579092 1699253893 14.621 005556089 001334983 +579123 1699253923 12.139 005562759 002167771 +579092 1699253923 14.621 005564282 001345236 +579123 1699253953 12.139 005571768 002183657 +579092 1699253953 14.621 005573323 001355425 +579123 1699253983 12.139 005580619 002195141 +579092 1699253983 14.621 005582122 001366159 +579123 1699254013 12.139 005588950 001854420 +579092 1699254013 14.621 005590483 001127714 +579123 1699254043 12.139 005597289 001867306 +579092 1699254044 14.621 005598872 001139451 +579123 1699254074 12.139 005605219 001879839 +579092 1699254074 14.621 005606765 001149381 +579123 1699254104 12.139 005613861 001893348 +579092 1699254104 14.621 005615407 001160171 +579123 1699254134 12.139 005621228 000655382 +579092 1699254134 14.621 005622743 000139249 +579123 1699254164 12.139 005629092 000670946 +579092 1699254164 14.621 005630733 000149695 +579123 1699254194 12.139 005636817 000684541 +579092 1699254194 14.621 005638468 000159842 +579123 1699254224 12.139 005643695 000697091 +579092 1699254224 14.621 005645372 000171420 +579123 1699254254 12.139 005651536 000196286 +579092 1699254254 14.590 005653319 000142297 +579123 1699254284 12.139 005657248 000207484 +579092 1699254284 14.590 005659918 000150422 +579123 1699254314 12.139 005665091 000219310 +579092 1699254314 14.590 005667095 000160340 +579123 1699254349 12.139 005672236 000233264 +579092 1699254349 14.590 005676020 000174399 +579123 1699254379 12.139 005683875 000569410 +579092 1699254379 14.741 004990209 000256533 +579123 1699254409 12.139 005691697 000846460 +579092 1699254409 14.855 005088262 000602700 +579123 1699254439 12.139 005698692 001093382 +579092 1699254440 15.042 005164652 000868591 +579123 1699254470 12.347 005706951 001390463 +579092 1699254470 14.922 005285306 001295353 +579123 1699254500 12.306 005327037 001806747 +579092 1699254500 14.628 005251783 001563699 +579123 1699254530 12.355 005398456 002292359 +579092 1699254530 14.763 005331440 001842450 +579123 1699254560 12.143 005478481 002830216 +579092 1699254568 14.628 005460625 002293813 +579123 1699254598 12.339 005500042 003322622 +579092 1699254598 14.628 005582264 002715461 +579123 1699254628 12.143 005584623 003657437 +579092 1699254628 15.210 005557021 002685277 +579123 1699254658 12.724 005603653 003717830 +579092 1699254658 14.628 005507748 002696265 +579123 1699254688 12.143 005610369 003730203 +579092 1699254688 14.628 005514425 002705691 +579123 1699254718 12.143 005529593 003690994 +579092 1699254718 14.628 005523421 002716870 +579123 1699254748 12.143 005537107 003701199 +579092 1699254748 14.628 005514385 002686411 +579123 1699254778 12.143 005544116 003711438 +579092 1699254778 14.628 005521374 002697222 +579123 1699254808 12.143 005550087 003720803 +579092 1699254809 14.628 005527312 002708960 +579123 1699254839 12.143 005531959 003365168 +579092 1699254839 14.628 005533758 002719301 +579123 1699254869 12.143 005537323 003377204 +579092 1699254869 14.628 005539169 002376922 +579123 1699254899 12.143 005543886 003389797 +579092 1699254899 14.628 005545712 002387215 +579123 1699254929 12.143 005550775 003403947 +579092 1699254929 14.628 005552502 002400110 +579123 1699254959 12.143 005557297 002795613 +579092 1699254959 14.628 005559077 002410337 +579123 1699254989 12.143 005564606 002810526 +579092 1699254989 14.628 005566382 001998252 +579123 1699255019 12.143 005571184 002822343 +579092 1699255019 14.628 005572908 002009205 +579123 1699255049 12.143 005578410 002836281 +579092 1699255049 14.628 005580092 002020301 +579123 1699255079 12.143 005586324 001312264 +579092 1699255079 14.628 005587962 002031792 +579123 1699255109 12.143 005595438 001328963 +579092 1699255109 14.628 005597021 000158951 +579123 1699255139 12.143 005604362 001344968 +579092 1699255139 14.628 005606174 000170880 +579123 1699255169 12.143 005611209 001358219 +579092 1699255169 14.628 005612692 000180710 +579123 1699255199 12.143 005619208 000196001 +579092 1699255199 14.628 005620635 000191314 +579123 1699255229 12.143 005628586 000211598 +579092 1699255229 14.629 005629935 000157245 +579123 1699255260 12.143 005637813 000228373 +579092 1699255260 14.629 005639198 000169320 +579123 1699255300 12.145 005645920 000245506 +579092 1699255300 14.630 005649650 000186715 +579123 1699255330 12.147 005655774 000319866 +579092 1699255330 14.630 005656907 000198113 +579123 1699255360 12.169 005663906 000683974 +579092 1699255360 14.637 004898222 000470188 +579123 1699255390 12.170 005672864 000994517 +579092 1699255390 14.637 005042532 000972676 +579123 1699255431 12.174 005682215 001277656 +579092 1699255431 14.637 005145881 001322008 +579123 1699255461 12.176 005289840 001651430 +579092 1699255461 14.637 005244592 001664266 +579123 1699255491 12.303 005364553 002184391 +579092 1699255491 14.785 005227168 001906991 +579123 1699255525 12.176 005399001 002474884 +579092 1699255525 14.834 005337788 002291785 +579123 1699255555 12.306 005445824 002746988 +579092 1699255555 14.824 005407256 002528866 +579123 1699255585 12.294 005381394 003076959 +579092 1699255585 14.989 005492436 002822412 +579123 1699255615 12.304 005463487 003403636 +579092 1699255619 14.639 005535380 003095707 +579123 1699255649 12.597 005577161 003898189 +579092 1699255649 15.221 005551595 003130725 +579123 1699255679 12.723 005598740 003963418 +579092 1699255679 14.639 005497712 003141662 +579123 1699255709 12.141 005575361 003907772 +579092 1699255709 14.639 005480736 003104938 +579123 1699255739 12.141 005583839 004209839 +579092 1699255739 14.639 005489207 003114738 +579123 1699255769 12.141 005592676 004220617 +579092 1699255769 14.639 005498102 003125122 +579123 1699255799 12.141 005601458 004231091 +579092 1699255799 14.639 005506786 003135308 +579123 1699255829 12.141 005516003 003858559 +579092 1699255829 14.639 005516671 002779240 +579123 1699255859 12.141 005524762 003871088 +579092 1699255859 14.639 005525680 002789735 +579123 1699255890 12.141 005535527 003887302 +579092 1699255890 14.639 005536175 002802330 +579123 1699255920 12.141 005545970 003905024 +579092 1699255920 14.639 005546652 002815278 +579123 1699255950 12.141 005556511 003410114 +579092 1699255950 14.639 005557199 002375558 +579123 1699255980 12.141 005567557 003428359 +579092 1699255980 14.639 005568220 002387240 +579123 1699256010 12.141 005576759 003442644 +579092 1699256010 14.639 005577334 002396801 +579123 1699256040 12.141 005587355 003457140 +579092 1699256040 14.639 005587938 002408098 +579123 1699256070 12.141 005597689 002991325 +579092 1699256070 14.639 005598193 000793867 +579123 1699256100 12.141 005607729 003007040 +579092 1699256100 14.639 005608252 000805144 +579123 1699256130 12.141 005616813 003021029 +579092 1699256130 14.639 005617339 000817430 +579123 1699256160 12.141 005625971 003036256 +579092 1699256160 14.639 005626490 000828211 +579123 1699256190 12.141 005633429 000229892 +579092 1699256190 14.639 005633973 000152826 +579123 1699256220 12.141 005643488 000243740 +579092 1699256220 14.639 005643798 000164920 +579123 1699256250 12.141 005653197 000257705 +579092 1699256250 14.639 005653559 000177144 +579123 1699256280 12.141 005663145 000273417 +579092 1699256280 14.639 005663483 000188694 +579123 1699256310 12.141 005672691 000223007 +579092 1699256310 14.639 005296424 000154285 +579123 1699256341 12.141 005683782 000239597 +579092 1699256345 14.639 005190949 000398496 +579123 1699256376 12.144 005696371 000262175 +579092 1699256376 14.639 005263726 000636890 +579123 1699256409 12.144 005704176 000275702 +579092 1699256409 14.802 005325719 000841359 +579123 1699256439 12.147 005602556 000415906 +579092 1699256442 14.639 005370871 000995156 +579123 1699256472 12.483 005611247 000770053 +579092 1699256488 14.639 005197672 001336016 +579123 1699256518 12.337 005624342 001193101 +579092 1699256518 14.806 005285794 001628333 +579123 1699256557 12.166 005390435 001612037 +579092 1699256561 14.639 005414726 002075346 +579123 1699256591 12.312 005511189 002103999 +579092 1699256591 14.797 005471957 002489945 +579123 1699256621 12.166 005558876 002284121 +579092 1699256621 14.640 005533919 002691560 +579123 1699256654 12.749 005629642 002614301 +579092 1699256654 15.222 005584386 002848291 +579123 1699256684 12.749 005601854 002571671 +579092 1699256684 14.640 005529295 002860282 +579123 1699256715 12.166 005610584 002588675 +579092 1699256715 14.640 005512582 003252400 +579123 1699256745 12.166 005618368 002827903 +579092 1699256745 14.640 005520329 003261241 +579123 1699256775 12.166 005627384 002838504 +579092 1699256775 14.640 005529410 003270947 +579123 1699256805 12.166 005537970 002788722 +579092 1699256805 14.642 005538022 003280968 +579123 1699256835 12.166 005545816 002800251 +579092 1699256835 14.640 005545903 003000459 +579123 1699256865 12.166 005553892 002813455 +579092 1699256865 14.640 005553906 003010012 +579123 1699256895 12.166 005562094 002828490 +579092 1699256895 14.640 005562213 003020665 +579123 1699256925 12.166 005569528 002511220 +579092 1699256925 14.640 005569820 003032386 +579123 1699256955 12.166 005579955 002529259 +579092 1699256955 14.640 005580234 002648992 +579123 1699256985 12.166 005589362 002544503 +579092 1699256985 14.640 005589643 002661282 +579123 1699257015 12.166 005598510 002559200 +579092 1699257015 14.640 005598756 002672810 +579123 1699257045 12.166 005608496 002164575 +579092 1699257045 14.640 005608770 002685787 +579123 1699257075 12.166 005617441 002179740 +579092 1699257075 14.640 005617649 002347282 +579123 1699257105 12.166 005625953 002192579 +579092 1699257105 14.640 005626192 002357607 +579123 1699257135 12.166 005634594 002207987 +579092 1699257135 14.640 005635035 002367275 +579123 1699257166 12.166 005644365 000215620 +579092 1699257166 14.640 005644704 002378226 +579123 1699257196 12.166 005653450 000230766 +579092 1699257196 14.640 005653888 000163596 +579123 1699257226 12.166 005661539 000241865 +579092 1699257226 14.640 005661808 000172590 +579123 1699257256 12.166 005670313 000256955 +579092 1699257264 14.640 005670448 000182659 +579123 1699257294 12.166 005683152 000225749 +579092 1699257294 14.640 005560092 000165350 +579123 1699257324 12.296 005693115 000242478 +579092 1699257324 14.778 005569393 000179400 +579123 1699257354 12.337 005700593 000257731 +579092 1699257354 14.767 005577349 000192571 +579123 1699257386 12.168 005707406 000270506 +579092 1699257389 14.600 005585481 000204852 +579123 1699257422 12.168 005717350 000930580 +579092 1699257422 14.631 005199976 000276056 +579123 1699257453 12.171 005727357 001297675 +579092 1699257453 14.631 005268307 000513186 +579123 1699257483 12.173 005735434 001618763 +579092 1699257487 14.631 005361604 000840528 +579123 1699257517 12.288 005315843 002142914 +579092 1699257520 14.797 005456903 001161912 +579123 1699257550 12.147 005424951 002580967 +579092 1699257552 14.644 005467471 001328982 +579123 1699257591 12.147 005560556 003134127 +579092 1699257591 15.092 005569580 001667092 +579123 1699257621 12.747 005622729 003367252 +579092 1699257621 15.227 005582130 001693666 +579123 1699257651 12.703 005596868 003327447 +579092 1699257651 14.644 005523400 001705105 +579123 1699257681 12.120 005604969 003339078 +579092 1699257681 14.644 005504486 001670238 +579123 1699257712 12.120 005611964 003348663 +579092 1699257712 14.644 005511498 001679196 +579123 1699257742 12.120 005621878 003361318 +579092 1699257742 14.644 005521396 001691356 +579123 1699257772 12.120 005529448 003313158 +579092 1699257772 14.644 005529722 001701904 +579123 1699257802 12.120 005536401 003323199 +579092 1699257802 14.644 005536713 001662444 +579123 1699257832 12.120 005544788 003336824 +579092 1699257832 14.644 005545152 001673938 +579123 1699257862 12.120 005552847 003349645 +579092 1699257862 14.644 005553193 001683961 +579123 1699257892 12.120 005562498 002903613 +579092 1699257892 14.644 005562874 001694946 +579123 1699257922 12.120 005570398 002914648 +579092 1699257922 14.644 005570813 001429469 +579123 1699257952 12.120 005578432 002927027 +579092 1699257952 14.644 005578893 001443422 +579123 1699257982 12.120 005588777 002942778 +579092 1699257982 14.644 005589298 001456477 +579123 1699258012 12.120 005597397 002530582 +579092 1699258012 14.644 005597997 001469541 +579123 1699258042 12.120 005607763 002547216 +579092 1699258042 14.644 005608408 001274341 +579123 1699258072 12.120 005615545 002559550 +579092 1699258072 14.644 005616141 001283795 +579123 1699258102 12.120 005621596 002571241 +579092 1699258102 14.644 005622821 001294940 +579123 1699258132 12.120 005628049 000196677 +579092 1699258133 14.644 005628831 001307573 +579123 1699258163 12.120 005635641 000211189 +579092 1699258163 14.644 005636124 000166931 +579123 1699258193 12.120 005642019 000223354 +579092 1699258193 14.644 005642956 000176771 +579123 1699258223 12.120 005649237 000235598 +579092 1699258223 14.644 005650552 000185528 +579123 1699258253 12.120 005658496 000200238 +579092 1699258253 14.644 005657206 000197200 +579123 1699258283 12.120 005668256 000216428 +579092 1699258283 14.644 005343588 000265214 +579123 1699258313 12.122 005678111 000235540 +579092 1699258313 14.644 005388952 000415792 +579123 1699258348 12.124 005685320 000248996 +579092 1699258348 14.645 005429237 000535393 +579123 1699258378 12.211 005697789 000646133 +579092 1699258380 14.662 005466751 000654254 +579123 1699258410 12.273 005706482 001108168 +579092 1699258415 14.645 005094757 001039313 +579123 1699258445 12.127 005714008 001636938 +579092 1699258446 14.645 005217988 001477769 +579123 1699258476 12.312 005623756 002049471 +579092 1699258476 14.645 005343168 001910848 +579123 1699258506 12.340 005388340 002496993 +579092 1699258506 14.645 005378052 002161493 +579123 1699258536 12.336 005482658 002905922 +579092 1699258536 14.645 005487559 002539291 +579123 1699258566 12.412 005587438 003317402 +579092 1699258566 15.227 005560289 002784767 +579123 1699258596 12.735 005612704 003407741 +579092 1699258596 14.645 005504744 002794352 +579123 1699258626 12.152 005584301 003368710 +579092 1699258626 14.645 005482254 002761776 +579123 1699258656 12.152 005593596 003379451 +579092 1699258656 14.645 005491529 002772342 +579123 1699258686 12.152 005602594 003389828 +579092 1699258686 14.645 005500544 002782765 +579123 1699258716 12.152 005613530 003402427 +579092 1699258716 14.645 005511423 002794724 +579123 1699258746 12.152 005524499 003357478 +579092 1699258746 14.645 005519950 002688834 +579123 1699258776 12.152 005535417 003372423 +579092 1699258776 14.645 005530914 002700636 +579123 1699258806 12.152 005546135 003385691 +579092 1699258806 14.645 005541670 002713550 +579123 1699258836 12.152 005556223 003400367 +579092 1699258836 14.645 005551729 002724574 +579123 1699258866 12.152 005561127 003037657 +579092 1699258867 14.645 005561752 002388888 +579123 1699258897 12.152 005569980 003051094 +579092 1699258897 14.645 005570322 002399710 +579123 1699258927 12.152 005578622 003064074 +579092 1699258927 14.645 005579244 002409663 +579123 1699258957 12.152 005587805 003078292 +579092 1699258957 14.645 005588363 002421184 +579123 1699258987 12.152 005596734 002601601 +579092 1699258987 14.645 005597281 000341418 +579123 1699259017 12.152 005605436 002616946 +579092 1699259017 14.645 005605995 000353180 +579123 1699259047 12.152 005613911 002630415 +579092 1699259047 14.645 005614476 000365183 +579123 1699259077 12.152 005623093 002648811 +579092 1699259077 14.645 005623618 000377791 +579123 1699259107 12.152 005631011 000229412 +579092 1699259107 14.645 005631612 000157047 +579123 1699259137 12.152 005639240 000241706 +579092 1699259137 14.645 005640017 000165687 +579123 1699259167 12.152 005648881 000260740 +579092 1699259167 14.645 005649530 000181925 +579123 1699259197 12.152 005656310 000272443 +579092 1699259197 14.645 005656765 000190906 +579123 1699259232 12.178 005665538 000228161 +579092 1699259232 14.645 005125490 000195683 +579123 1699259262 12.178 005677071 000248480 +579092 1699259262 14.645 005196342 000435336 +579123 1699259292 12.178 005685688 000265714 +579092 1699259292 14.646 005254870 000632455 +579123 1699259322 12.325 005693458 000241865 +579092 1699259322 14.646 005318382 000847412 +579123 1699259352 12.195 005701390 000648742 +579092 1699259352 14.646 005085349 000985432 +579123 1699259382 12.352 005709767 001345411 +579092 1699259382 14.807 005140706 001491957 +579123 1699259412 12.358 005717716 001718622 +579092 1699259412 14.820 005214744 001742411 +579123 1699259442 12.418 005528400 002013201 +579092 1699259443 14.872 005327817 002137466 +579123 1699259473 12.339 005380465 002438394 +579092 1699259473 14.915 005406589 002405029 +579123 1699259503 12.369 005496918 002900935 +579092 1699259503 15.002 005414185 002687498 +579123 1699259533 12.358 005555627 003188455 +579092 1699259533 14.825 005514270 003034772 +579123 1699259563 12.779 005617121 003443843 +579092 1699259563 15.229 005570130 003218229 +579123 1699259593 12.779 005587182 003394396 +579092 1699259593 14.646 005517709 003228874 +579123 1699259623 12.196 005595282 003405326 +579092 1699259623 14.646 005491983 003191353 +579123 1699259653 12.196 005603635 003415804 +579092 1699259653 14.646 005500351 003201323 +579123 1699259683 12.196 005611277 003426221 +579092 1699259683 14.646 005507992 003211237 +579123 1699259713 12.196 005523182 003378779 +579092 1699259713 14.646 005515729 003219935 +579123 1699259743 12.196 005532625 003393445 +579092 1699259743 14.646 005525158 003027429 +579123 1699259773 12.196 005541679 003408049 +579092 1699259773 14.646 005534127 003039145 +579123 1699259803 12.196 005549482 003420825 +579092 1699259803 14.646 005541953 003049531 +579123 1699259833 12.196 005547811 003051437 +579092 1699259833 14.646 005548540 003059768 +579123 1699259863 12.196 005556546 003066383 +579092 1699259863 14.646 005557303 002694228 +579123 1699259893 12.196 005564798 003081968 +579092 1699259894 14.646 005565586 002704295 +579123 1699259924 12.196 005573085 003093653 +579092 1699259924 14.646 005573843 002713246 +579123 1699259954 12.196 005583184 002595378 +579092 1699259954 14.646 005584021 002725467 +579123 1699259984 12.196 005592560 002610294 +579092 1699259984 14.646 005593403 002274801 +579123 1699260014 12.196 005601034 002624321 +579092 1699260014 14.646 005601908 002284716 +579123 1699260044 12.196 005609002 002638194 +579092 1699260044 14.646 005609803 002295271 +579123 1699260074 12.196 005616181 000210445 +579092 1699260074 14.646 005617112 002303837 +579123 1699260104 12.196 005624562 000226264 +579092 1699260104 14.646 005625743 000151653 +579123 1699260134 12.196 005632750 000241339 +579092 1699260134 14.646 005633899 000163081 +579123 1699260164 12.196 005640828 000257852 +579092 1699260164 14.646 005642045 000174088 +579123 1699260194 12.196 005648869 000217469 +579092 1699260194 14.646 005649771 000183971 +579123 1699260230 12.198 005655217 000228676 +579092 1699260230 14.646 005555074 000154056 +579123 1699260260 12.320 005666812 000248085 +579092 1699260260 14.646 005564224 000168866 +579123 1699260290 12.356 005674954 000314987 +579092 1699260290 14.689 005572318 000177984 +579123 1699260320 12.407 005684944 000890927 +579092 1699260320 14.689 005143356 000149467 +579123 1699260361 12.200 005694030 001433992 +579092 1699260361 14.861 005086518 000724513 +579123 1699260391 12.598 005704630 001926209 +579092 1699260391 15.084 005200237 001122918 +579123 1699260426 12.208 005305030 002247410 +579092 1699260428 14.689 005332760 001589536 +579123 1699260467 12.214 005435335 002846176 +579092 1699260470 14.689 005399669 002047354 +579123 1699260502 12.214 005522481 003256944 +579092 1699260502 14.689 005527341 002487120 +579123 1699260533 12.981 005612786 003629936 +579092 1699260533 15.271 005569878 002616038 +579123 1699260563 12.797 005585076 003584060 +579092 1699260563 14.689 005483650 002582341 +579123 1699260593 12.214 005592181 003594315 +579092 1699260593 14.689 005489928 002591761 +579123 1699260623 12.214 005600087 003605018 +579092 1699260623 14.689 005497763 002601741 +579123 1699260654 12.215 005607898 003562703 +579092 1699260654 14.689 005505719 002611446 +579123 1699260684 12.215 005521924 003576290 +579092 1699260684 14.689 005510069 002578900 +579123 1699260714 12.215 005529315 003589541 +579092 1699260714 14.689 005517432 002589384 +579123 1699260744 12.215 005536743 003602756 +579092 1699260744 14.689 005524879 002599363 +579123 1699260774 12.215 005544690 003616103 +579092 1699260774 14.689 005532847 002609713 +579123 1699260804 12.215 005539801 003147579 +579092 1699260804 14.689 005540856 002620767 +579123 1699260834 12.215 005547509 003159754 +579092 1699260834 14.689 005548573 002350198 +579123 1699260864 12.215 005554914 003172373 +579092 1699260864 14.689 005555977 002361549 +579123 1699260894 12.215 005562503 003186980 +579092 1699260894 14.689 005563540 002372584 +579123 1699260924 12.215 005570220 002721107 +579092 1699260924 14.689 005571274 002382666 +579123 1699260954 12.215 005578276 002734154 +579092 1699260954 14.689 005579388 001966604 +579123 1699260984 12.215 005584372 002744664 +579092 1699260984 14.689 005585150 001974993 +579123 1699261014 12.215 005589992 002756322 +579092 1699261014 14.689 005591123 001984040 +579123 1699261044 12.215 005596716 000206308 +579092 1699261044 14.689 005598496 001995008 +579123 1699261074 12.215 005603527 000217119 +579092 1699261074 14.689 005604779 000151315 +579123 1699261105 12.215 005611276 000230041 +579092 1699261105 14.689 005612299 000160696 +579123 1699261135 12.215 005619461 000242133 +579092 1699261135 14.689 005620251 000170382 +579123 1699261165 12.215 005628284 000206795 +579092 1699261165 14.689 005629544 000181729 +579123 1699261202 12.214 005635174 000220446 +579092 1699261202 14.689 005548002 000155160 +579123 1699261232 12.214 005645389 000241134 +579092 1699261233 14.796 005554857 000163845 +579123 1699261263 12.214 005653885 000330327 +579092 1699261273 14.689 005562551 000180449 +579123 1699261303 12.574 005665505 001038215 +579092 1699261303 14.902 004980082 000309783 +579123 1699261333 12.280 005673800 001463140 +579092 1699261333 14.901 005088492 000686856 +579123 1699261370 12.229 005680924 001834078 +579092 1699261375 14.689 005222777 001154434 +579123 1699261405 12.434 005184795 002312388 +579092 1699261405 14.691 005336854 001551709 +579123 1699261435 12.394 005285558 002741842 +579092 1699261435 14.865 005365073 001857042 +579123 1699261465 12.458 005376865 003108873 +579092 1699261465 14.925 005474538 002228895 +579123 1699261495 12.428 005489642 003565712 +579092 1699261497 15.227 005553549 002499573 +579123 1699261527 12.231 005545798 004035625 +579092 1699261527 15.227 005562790 002512386 +579123 1699261557 12.813 005584944 004174613 +579092 1699261557 14.645 005485095 002485706 +579123 1699261587 12.232 005593457 004186398 +579092 1699261587 14.645 005493658 002495522 +579123 1699261618 12.232 005602772 004197253 +579092 1699261618 14.645 005501996 002504442 +579123 1699261648 12.232 005530753 004148005 +579092 1699261648 14.645 005517055 002519132 +579123 1699261678 12.232 005545824 004163407 +579092 1699261678 14.645 005526511 002484703 +579123 1699261708 12.232 005557557 004177229 +579092 1699261708 14.645 005538295 002499190 +579123 1699261738 12.232 005567698 004188625 +579092 1699261738 14.645 005548478 002510124 +579123 1699261768 12.232 005557661 003665820 +579092 1699261768 14.645 005558424 002522055 +579123 1699261798 12.232 005568311 003682828 +579092 1699261798 14.645 005569061 002170204 +579123 1699261828 12.232 005578494 003698327 +579092 1699261828 14.645 005579232 002182002 +579123 1699261858 12.232 005590758 003715531 +579092 1699261858 14.645 005591377 002195086 +579123 1699261888 12.232 005602505 003221309 +579092 1699261888 14.645 005603157 002208236 +579123 1699261918 12.232 005612898 003237340 +579092 1699261918 14.645 005613943 001905420 +579123 1699261948 12.232 005623426 003251021 +579092 1699261948 14.645 005624084 001917119 +579123 1699261978 12.232 005635080 003267096 +579092 1699261978 14.645 005635728 001931119 +579123 1699262008 12.232 005646335 000891365 +579092 1699262008 14.645 005646778 001942574 +579123 1699262038 12.232 005657260 000907983 +579092 1699262038 14.645 005657822 000174076 +579123 1699262069 12.232 005668352 000926434 +579092 1699262069 14.645 005668531 000187346 +579123 1699262099 12.232 005679875 000943577 +579092 1699262099 14.645 005680464 000202702 +579123 1699262129 12.232 005691424 000241847 +579092 1699262129 14.645 005692240 000214702 +579123 1699262159 12.232 005704129 000264564 +579092 1699262159 14.645 005609186 000174089 +579123 1699262189 12.232 005716505 000280582 +579092 1699262189 14.645 005622529 000189444 +579123 1699262219 12.232 005727604 000297265 +579092 1699262219 14.661 005633159 000202846 +579123 1699262249 12.232 005737940 000448403 +579092 1699262257 14.660 005643047 000214990 +579123 1699262289 12.201 005749769 000848920 +579092 1699262289 14.660 005160743 000330916 +579123 1699262319 12.364 005764140 001146849 +579092 1699262319 14.660 005255614 000647519 +579123 1699262349 12.629 005444586 001364354 +579092 1699262349 14.670 005332578 000902137 +579123 1699262379 12.494 005495801 001670137 +579092 1699262379 14.827 005407193 001149898 +579123 1699262409 12.370 005536761 001926312 +579092 1699262409 14.840 005388616 001310807 +579123 1699262439 12.220 005574814 002207882 +579092 1699262439 14.671 005462952 001567309 +579123 1699262469 12.220 005568534 002458468 +579092 1699262469 14.856 005533549 001805151 +579123 1699262499 12.443 005526802 002709681 +579092 1699262499 14.671 005563604 001890860 +579123 1699262541 12.220 005646568 003178262 +579092 1699262547 15.261 005606850 002156777 +579123 1699262577 12.805 005681604 003295609 +579092 1699262577 15.228 005618772 002175109 +579123 1699262607 12.222 005577379 003245486 +579092 1699262607 14.647 005554841 002185933 +579123 1699262637 12.225 005585291 003710371 +579092 1699262637 14.648 005562625 002195240 +579123 1699262667 12.226 005593396 003720472 +579092 1699262667 14.648 005559640 002149823 +579123 1699262697 12.227 005600884 003729841 +579092 1699262697 14.648 005567172 002159135 +579123 1699262727 12.229 005575445 003327862 +579092 1699262727 14.648 005575841 002170060 +579123 1699262758 12.229 005582949 003340533 +579092 1699262758 14.648 005582290 002178671 +579123 1699262788 12.229 005590680 003351633 +579092 1699262788 14.648 005590927 001891375 +579123 1699262818 12.229 005600083 003368391 +579092 1699262818 14.648 005600315 001905588 +579123 1699262848 12.230 005607785 002874052 +579092 1699262848 14.648 005608060 001914938 +579123 1699262878 12.231 005618088 002891162 +579092 1699262878 14.648 005618328 001927275 +579123 1699262908 12.231 005626244 002904359 +579092 1699262908 14.648 005626513 001495252 +579123 1699262938 12.231 005634724 002917390 +579092 1699262938 14.648 005635004 001506766 +579123 1699262968 12.232 005642144 002557764 +579092 1699262968 14.648 005642435 001517621 +579123 1699262998 12.232 005649925 002571636 +579092 1699262998 14.648 005650354 001527184 +579123 1699263028 12.232 005657008 002585498 +579092 1699263028 14.648 005657375 000151278 +579123 1699263058 12.232 005665383 002601657 +579092 1699263058 14.648 005665822 000161658 +579123 1699263088 12.232 005674198 000212296 +579092 1699263088 14.648 005674869 000173545 +579123 1699263118 12.232 005683578 000231444 +579092 1699263118 14.648 005684032 000186550 +579123 1699263148 12.236 005696281 000251802 +579092 1699263148 14.648 005696699 000160833 +579123 1699263178 12.236 005709109 000272778 +579092 1699263179 14.648 005709564 000175408 +579123 1699263209 12.236 005717517 000233106 +579092 1699263209 14.797 005717796 000185920 +579123 1699263240 12.236 005723600 000245441 +579092 1699263240 14.699 005724868 000197756 +579123 1699263270 12.404 005736424 000268599 +579092 1699263270 14.699 005220208 000424618 +579123 1699263300 12.263 005746370 000284917 +579092 1699263300 14.843 005288033 000650823 +579123 1699263330 12.281 005627250 000421955 +579092 1699263330 14.699 005345203 000846128 +579123 1699263360 12.299 005636329 000819656 +579092 1699263360 14.861 005415522 001082831 +579123 1699263396 12.281 005645788 001304978 +579092 1699263396 15.085 005357209 001397848 +579123 1699263426 12.281 005658316 001662337 +579092 1699263426 14.851 005468909 001776007 +579123 1699263456 12.479 005514089 001868150 +579092 1699263456 14.868 005547148 002055245 +579123 1699263496 12.281 005595656 002252053 +579092 1699263496 15.282 005593074 002281809 +579123 1699263526 12.863 005655611 002473052 +579092 1699263526 14.699 005542903 002296099 +579123 1699263556 12.281 005574140 002419135 +579092 1699263556 14.699 005554882 002309152 +579123 1699263586 12.281 005584855 002633376 +579092 1699263586 14.699 005565025 002320882 +579123 1699263617 12.281 005594293 002644974 +579092 1699263617 14.701 005558592 002282397 +579123 1699263647 12.281 005605036 002659213 +579092 1699263647 14.699 005569340 002295804 +579123 1699263677 12.281 005576851 002608117 +579092 1699263677 14.699 005577625 002307457 +579123 1699263707 12.281 005585715 002626659 +579092 1699263707 14.699 005586478 002321390 +579123 1699263737 12.281 005594913 002641804 +579092 1699263737 14.699 005595655 002069115 +579123 1699263767 12.281 005601750 002654811 +579092 1699263767 14.699 005602493 002079614 +579123 1699263797 12.281 005609173 002325929 +579092 1699263797 14.699 005609916 002091616 +579123 1699263827 12.281 005617164 002342314 +579092 1699263827 14.699 005617889 002106029 +579123 1699263857 12.281 005625822 002359078 +579092 1699263857 14.699 005626361 001737788 +579123 1699263887 12.281 005633199 002377067 +579092 1699263887 14.699 005633953 001749862 +579123 1699263917 12.281 005638975 001973721 +579092 1699263918 14.699 005639750 001761431 +579123 1699263948 12.281 005646140 001990355 +579092 1699263948 14.699 005647042 001772670 +579123 1699263978 12.281 005652057 002004046 +579092 1699263978 14.699 005652933 000164674 +579123 1699264008 12.281 005661027 002019744 +579092 1699264008 14.699 005661490 000176092 +579123 1699264038 12.281 005669941 000216994 +579092 1699264038 14.699 005670830 000189076 +579123 1699264068 12.281 005677746 000231667 +579092 1699264068 14.699 005678604 000202194 +579123 1699264098 12.281 005686010 000246833 +579092 1699264098 14.699 005686887 000162323 +579123 1699264128 12.281 005693121 000263792 +579092 1699264128 14.699 005694551 000173443 +579123 1699264162 12.285 005698539 000274727 +579092 1699264162 14.699 005701322 000185087 +579123 1699264192 12.281 005709508 000231139 +579092 1699264192 14.699 005710420 000196251 +579123 1699264222 12.281 005717515 000247722 +579092 1699264222 14.708 005127031 000229356 +579123 1699264260 12.281 005725667 000265873 +579092 1699264270 14.708 005226804 000745928 +579123 1699264302 12.281 005615440 000445862 +579092 1699264302 14.888 005343001 001148330 +579123 1699264341 12.281 005626448 001093420 +579092 1699264341 14.897 005434196 001457841 +579123 1699264380 12.282 005634292 001642054 +579092 1699264380 14.908 005426022 001740825 +579123 1699264410 12.455 005291583 001994249 +579092 1699264410 14.937 005529050 002098866 +579123 1699264440 12.486 005400849 002471483 +579092 1699264440 15.341 005587433 002298920 +579123 1699264470 12.485 005530277 003018556 +579092 1699264470 15.341 005559052 002261453 +579123 1699264500 12.865 005622195 003381118 +579092 1699264500 14.758 005501088 002271802 +579123 1699264531 12.282 005593427 003334088 +579092 1699264531 14.758 005510003 002282047 +579123 1699264561 12.282 005601703 003343684 +579092 1699264561 14.758 005518195 002290817 +579123 1699264591 12.282 005611815 003355872 +579092 1699264591 14.758 005510512 002259516 +579123 1699264621 12.282 005620463 003366729 +579092 1699264621 14.758 005519170 002270661 +579123 1699264651 12.282 005526650 003313005 +579092 1699264651 14.758 005527170 002280378 +579123 1699264681 12.282 005536176 003327608 +579092 1699264681 14.758 005536770 002291360 +579123 1699264711 12.282 005543545 003340713 +579092 1699264711 14.758 005544139 002007594 +579123 1699264741 12.284 005551095 003353349 +579092 1699264741 14.758 005551639 002016792 +579123 1699264771 12.284 005559663 003000272 +579092 1699264771 14.758 005560209 002027980 +579123 1699264801 12.284 005567063 003013010 +579092 1699264801 14.758 005567597 002040273 +579123 1699264831 12.284 005576029 003027619 +579092 1699264831 14.758 005576612 001700578 +579123 1699264861 12.284 005584397 003042084 +579092 1699264861 14.758 005585052 001711253 +579123 1699264891 12.285 005591840 002645306 +579092 1699264891 14.758 005592541 001722671 +579123 1699264921 12.285 005601480 002660343 +579092 1699264921 14.758 005602206 001734485 +579123 1699264951 12.285 005607682 002672318 +579092 1699264952 14.759 005608387 000152950 +579123 1699264982 12.285 005615973 002688838 +579092 1699264982 14.759 005616771 000166717 +579123 1699265012 12.285 005623850 000200903 +579092 1699265012 14.759 005624735 000177922 +579123 1699265042 12.285 005631603 000214127 +579092 1699265042 14.759 005632453 000187963 +579123 1699265072 12.285 005638266 000226144 +579092 1699265072 14.759 005639103 000155233 +579123 1699265102 12.285 005646650 000240581 +579092 1699265102 14.759 005647468 000167347 +579123 1699265142 12.285 005655449 000200700 +579092 1699265142 14.759 005658290 000183344 +579123 1699265172 12.285 005668540 000224061 +579092 1699265172 14.759 005669326 000195737 +579123 1699265202 12.285 005677725 000242401 +579092 1699265202 14.759 004929773 000296905 +579123 1699265243 12.285 005686690 000255936 +579092 1699265243 14.759 005059645 000749602 +579123 1699265273 12.285 005588539 000336069 +579092 1699265274 14.713 005150986 001070220 +579123 1699265304 12.682 005597062 000778094 +579092 1699265304 14.921 005264857 001457950 +579123 1699265334 12.417 005604497 001132976 +579092 1699265334 14.853 005273478 001741013 +579123 1699265370 12.265 005612269 001353021 +579092 1699265371 14.713 005371644 002070928 +579123 1699265401 12.416 005421597 001683325 +579092 1699265401 14.898 005472852 002415661 +579123 1699265431 12.425 005501416 002022912 +579092 1699265438 14.758 005538956 002642295 +579123 1699265468 12.534 005577409 002366329 +579092 1699265468 15.039 005559717 002785219 +579123 1699265498 12.868 005588202 002481029 +579092 1699265498 15.340 005570554 002798389 +579123 1699265528 12.285 005599335 002498382 +579092 1699265528 14.758 005520385 002811030 +579123 1699265558 12.285 005608704 002508907 +579092 1699265558 14.758 005529786 002821010 +579123 1699265588 12.285 005617187 002519093 +579092 1699265588 14.758 005519924 002784849 +579123 1699265618 12.285 005532753 002474508 +579092 1699265618 14.758 005530373 002796624 +579123 1699265648 12.285 005540393 002489576 +579092 1699265648 14.758 005538049 002806146 +579123 1699265678 12.285 005549114 002505716 +579092 1699265678 14.758 005546807 002816438 +579123 1699265709 12.285 005557184 002520493 +579092 1699265709 14.758 005554880 002553110 +579123 1699265739 12.285 005561270 002273127 +579092 1699265739 14.758 005561950 002565150 +579123 1699265769 12.282 000000000 000000000 +579092 1699265769 14.754 000000000 000000000 +579123 1699265799 12.282 000000000 000000000 +579092 1699265799 14.651 000000000 000000000 diff --git a/draft_pr_remove_me/nov_5/mem_diff_intrusive_ptr.png b/draft_pr_remove_me/nov_5/mem_diff_intrusive_ptr.png new file mode 100644 index 0000000000000000000000000000000000000000..945106dfed1ab3f75b5de67d455ce6ccc2a26ebe GIT binary patch literal 20068 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 literal 0 HcmV?d00001 diff --git a/draft_pr_remove_me/nov_5/mem_percent_change.png b/draft_pr_remove_me/nov_5/mem_percent_change.png new file mode 100644 index 0000000000000000000000000000000000000000..43c89481b6673ddaeeca02b7c6b7ba2c4d8c583a GIT binary patch literal 22775 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 Q!xv03)HBh&rDK2QpX)}Pw*UYD literal 0 HcmV?d00001 diff --git a/draft_pr_remove_me/oct_25/mem_usage_intrusive_ptr.png b/draft_pr_remove_me/oct_25/mem_usage_intrusive_ptr.png new file mode 100644 index 0000000000000000000000000000000000000000..354ef9ee779585886747029b1efa81e99ec2e0c0 GIT binary patch literal 24850 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 literal 0 HcmV?d00001 diff --git a/draft_pr_remove_me/oct_30/mem_diff_intrusive_ptr.png b/draft_pr_remove_me/oct_30/mem_diff_intrusive_ptr.png new file mode 100644 index 0000000000000000000000000000000000000000..4a7a68c96c06c04edd6b864839f1fc56dbdfbb15 GIT binary patch literal 23700 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 PPT9H5d~2zZOXR-+FK5WU literal 0 HcmV?d00001 diff --git a/draft_pr_remove_me/oct_30/mem_percent_change.png b/draft_pr_remove_me/oct_30/mem_percent_change.png new file mode 100644 index 0000000000000000000000000000000000000000..137dba0e6b54b2db01e05877b714fc2e2f0a4835 GIT binary patch literal 24496 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

DBbyKZyr|S9<_{$42PmsrX1k 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 HbL@Ws5ZIdU literal 0 HcmV?d00001 diff --git a/draft_pr_remove_me/oct_30/mem_usage_intrusive_ptr.png b/draft_pr_remove_me/oct_30/mem_usage_intrusive_ptr.png new file mode 100644 index 0000000000000000000000000000000000000000..ddd197e5d42bffdb986933a6186ecd08bd09236f GIT binary patch literal 26289 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 {&b6l&%GmQ=dBB8Hq( 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 LzDB8p7&^@!; literal 0 HcmV?d00001 From da2ba2f2f83593dbfcc9c8b1d35dbe84e02737b5 Mon Sep 17 00:00:00 2001 From: Scott Determan Date: Sat, 14 Oct 2023 14:04:56 -0400 Subject: [PATCH 3/7] Replace SHAMapTreeNodes with intrusive pointers: This branch has a long history. About two years ago I wrote a patch to remove the mutex from shamap inner nodes (ref: https://github.com/seelabs/rippled/tree/lockfree-tagged-cache). At the time I measured a large memory savings of about 2 gig. Unfortunately, the code required using the `folly` library, and I was hesitant to introduce such a large dependency into rippled (especially one that was so hard to build). This branch resurrects that old work and removes the `folly` dependency. The old branch used a lockless atomic shared pointer. This new branch introduces a intrusive pointer type. Unlike boost's intrusive pointer, this intrusive pointer can handle both strong and weak pointers (needed for the tagged cache). Since this is an intrusive pointer type, in order to support weak pointers, the object is not destroyed when the strong count goes to zero. Instead, it is "partially destroyed" (for example, inner nodes will reset their children). This intrusive pointer takes 16-bits for the strong count and 14-bits for the weak count, and takes one 64-bit pointer to point at the object. This is much smaller than a std::shared_pointer, which needs a control block to hold the strong and weak counts (and potentially other objects), as well as an extra pointer to point at the control block. The intrusive shared pointer can be modified to support for atomic operations (there is a branch that adds this support). These atomic operations can be used instead of the lock when changing inner node pointers in the shamap. Note: The space savings is independent from removing the locks from shamap inner node. Therefor this work is divided into two phases. In the first phase a non-atomic intrusive pointer is introduced and the locks are kept. In a second phases the atomic intrusive pointer could be introduced and the locks will be removed. Some of the code in this patch is written with the upcoming atomic work in mind (for example, using exchange in places). The atomic intrusive pointer also requires the C++ library to support `atomic_ref`. Both gcc and msvc support this, but at the time of this writing clang's library does not. Note: Intrusive pointer will be 12 bytes. The shared_ptr will be around 40 bytes, depending on implementation. When measuring memory usage on a validator, this patch resulted in between a 10 and 15% memory savings. --- Builds/CMake/RippledCore.cmake | 1138 +++++++++++++++++ include/xrpl/basics/IntrusivePointer.h | 494 +++++++ include/xrpl/basics/IntrusivePointer.ipp | 720 +++++++++++ include/xrpl/basics/IntrusiveRefCounts.h | 466 +++++++ include/xrpl/basics/SharedWeakCachePointer.h | 132 ++ .../xrpl/basics/SharedWeakCachePointer.ipp | 190 +++ include/xrpl/basics/TaggedCache.h | 42 +- include/xrpl/basics/TaggedCache.ipp | 496 +++++-- include/xrpl/protocol/AccountID.h | 2 +- src/test/basics/IntrusiveShared_test.cpp | 787 ++++++++++++ src/xrpld/app/main/Application.h | 2 + .../nodestore/detail/DatabaseNodeImp.cpp | 1 + src/xrpld/shamap/SHAMap.h | 68 +- src/xrpld/shamap/SHAMapAccountStateLeafNode.h | 4 +- src/xrpld/shamap/SHAMapInnerNode.h | 26 +- src/xrpld/shamap/SHAMapTreeNode.h | 36 +- src/xrpld/shamap/SHAMapTxLeafNode.h | 4 +- src/xrpld/shamap/SHAMapTxPlusMetaLeafNode.h | 5 +- src/xrpld/shamap/TreeNodeCache.h | 9 +- src/xrpld/shamap/detail/NodeFamily.cpp | 1 + src/xrpld/shamap/detail/SHAMap.cpp | 158 +-- src/xrpld/shamap/detail/SHAMapDelta.cpp | 34 +- src/xrpld/shamap/detail/SHAMapInnerNode.cpp | 63 +- src/xrpld/shamap/detail/SHAMapLeafNode.cpp | 1 + src/xrpld/shamap/detail/SHAMapSync.cpp | 26 +- src/xrpld/shamap/detail/SHAMapTreeNode.cpp | 25 +- src/xrpld/shamap/detail/TaggedPointer.h | 6 +- src/xrpld/shamap/detail/TaggedPointer.ipp | 40 +- 28 files changed, 4636 insertions(+), 340 deletions(-) create mode 100644 Builds/CMake/RippledCore.cmake create mode 100644 include/xrpl/basics/IntrusivePointer.h create mode 100644 include/xrpl/basics/IntrusivePointer.ipp create mode 100644 include/xrpl/basics/IntrusiveRefCounts.h create mode 100644 include/xrpl/basics/SharedWeakCachePointer.h create mode 100644 include/xrpl/basics/SharedWeakCachePointer.ipp create mode 100644 src/test/basics/IntrusiveShared_test.cpp diff --git a/Builds/CMake/RippledCore.cmake b/Builds/CMake/RippledCore.cmake new file mode 100644 index 00000000000..2a3743c3ee3 --- /dev/null +++ b/Builds/CMake/RippledCore.cmake @@ -0,0 +1,1138 @@ +#[===================================================================[ + xrpl_core + core functionality, useable by some client software perhaps +#]===================================================================] + +file (GLOB_RECURSE rb_headers + src/ripple/beast/*.h + src/ripple/beast/*.hpp) + +add_library (xrpl_core + ${rb_headers}) ## headers added here for benefit of IDEs +if (unity) + set_target_properties(xrpl_core PROPERTIES UNITY_BUILD ON) +endif () + +add_library(libxrpl INTERFACE) +target_link_libraries(libxrpl INTERFACE xrpl_core) +add_library(xrpl::libxrpl ALIAS libxrpl) + + +#[===============================[ + beast/legacy FILES: + TODO: review these sources for removal or replacement +#]===============================] +target_sources (xrpl_core PRIVATE + src/ripple/beast/clock/basic_seconds_clock.cpp + src/ripple/beast/core/CurrentThreadName.cpp + src/ripple/beast/core/SemanticVersion.cpp + src/ripple/beast/hash/impl/xxhash.cpp + src/ripple/beast/insight/impl/Collector.cpp + src/ripple/beast/insight/impl/Groups.cpp + src/ripple/beast/insight/impl/Hook.cpp + src/ripple/beast/insight/impl/Metric.cpp + src/ripple/beast/insight/impl/NullCollector.cpp + src/ripple/beast/insight/impl/StatsDCollector.cpp + src/ripple/beast/net/impl/IPAddressConversion.cpp + src/ripple/beast/net/impl/IPAddressV4.cpp + src/ripple/beast/net/impl/IPAddressV6.cpp + src/ripple/beast/net/impl/IPEndpoint.cpp + src/ripple/beast/utility/src/beast_Journal.cpp + src/ripple/beast/utility/src/beast_PropertyStream.cpp) + +#[===============================[ + core sources +#]===============================] +target_sources (xrpl_core PRIVATE + #[===============================[ + main sources: + subdir: basics (partial) + #]===============================] + src/ripple/basics/impl/base64.cpp + src/ripple/basics/impl/contract.cpp + src/ripple/basics/impl/CountedObject.cpp + src/ripple/basics/impl/FileUtilities.cpp + src/ripple/basics/impl/IOUAmount.cpp + src/ripple/basics/impl/Log.cpp + src/ripple/basics/impl/Number.cpp + src/ripple/basics/impl/StringUtilities.cpp + #[===============================[ + main sources: + subdir: json + #]===============================] + src/ripple/json/impl/JsonPropertyStream.cpp + src/ripple/json/impl/Object.cpp + src/ripple/json/impl/Output.cpp + src/ripple/json/impl/Writer.cpp + src/ripple/json/impl/json_reader.cpp + src/ripple/json/impl/json_value.cpp + src/ripple/json/impl/json_valueiterator.cpp + src/ripple/json/impl/json_writer.cpp + src/ripple/json/impl/to_string.cpp + #[===============================[ + main sources: + subdir: protocol + #]===============================] + src/ripple/protocol/impl/AccountID.cpp + src/ripple/protocol/impl/AMMCore.cpp + src/ripple/protocol/impl/Book.cpp + src/ripple/protocol/impl/BuildInfo.cpp + src/ripple/protocol/impl/ErrorCodes.cpp + src/ripple/protocol/impl/Feature.cpp + src/ripple/protocol/impl/Indexes.cpp + src/ripple/protocol/impl/InnerObjectFormats.cpp + src/ripple/protocol/impl/Issue.cpp + src/ripple/protocol/impl/STIssue.cpp + src/ripple/protocol/impl/Keylet.cpp + src/ripple/protocol/impl/LedgerFormats.cpp + src/ripple/protocol/impl/LedgerHeader.cpp + src/ripple/protocol/impl/PublicKey.cpp + src/ripple/protocol/impl/Quality.cpp + src/ripple/protocol/impl/QualityFunction.cpp + src/ripple/protocol/impl/Rate2.cpp + src/ripple/protocol/impl/Rules.cpp + src/ripple/protocol/impl/SField.cpp + src/ripple/protocol/impl/SOTemplate.cpp + src/ripple/protocol/impl/STAccount.cpp + src/ripple/protocol/impl/STAmount.cpp + src/ripple/protocol/impl/STArray.cpp + src/ripple/protocol/impl/STBase.cpp + src/ripple/protocol/impl/STBlob.cpp + src/ripple/protocol/impl/STInteger.cpp + src/ripple/protocol/impl/STLedgerEntry.cpp + src/ripple/protocol/impl/STObject.cpp + src/ripple/protocol/impl/STParsedJSON.cpp + src/ripple/protocol/impl/STPathSet.cpp + src/ripple/protocol/impl/STXChainBridge.cpp + src/ripple/protocol/impl/STTx.cpp + src/ripple/protocol/impl/XChainAttestations.cpp + src/ripple/protocol/impl/STValidation.cpp + src/ripple/protocol/impl/STVar.cpp + src/ripple/protocol/impl/STVector256.cpp + src/ripple/protocol/impl/SecretKey.cpp + src/ripple/protocol/impl/Seed.cpp + src/ripple/protocol/impl/Serializer.cpp + src/ripple/protocol/impl/Sign.cpp + src/ripple/protocol/impl/TER.cpp + src/ripple/protocol/impl/TxFormats.cpp + src/ripple/protocol/impl/TxMeta.cpp + src/ripple/protocol/impl/UintTypes.cpp + src/ripple/protocol/impl/digest.cpp + src/ripple/protocol/impl/tokens.cpp + src/ripple/protocol/impl/NFTSyntheticSerializer.cpp + src/ripple/protocol/impl/NFTokenID.cpp + src/ripple/protocol/impl/NFTokenOfferID.cpp + #[===============================[ + main sources: + subdir: crypto + #]===============================] + src/ripple/crypto/impl/RFC1751.cpp + src/ripple/crypto/impl/csprng.cpp + src/ripple/crypto/impl/secure_erase.cpp) + +add_library (Ripple::xrpl_core ALIAS xrpl_core) +target_include_directories (xrpl_core + PUBLIC + $ + $) + +target_compile_definitions(xrpl_core + PUBLIC + BOOST_ASIO_USE_TS_EXECUTOR_AS_DEFAULT + BOOST_CONTAINER_FWD_BAD_DEQUE + HAS_UNCAUGHT_EXCEPTIONS=1) +target_compile_options (xrpl_core + PUBLIC + $<$:-Wno-maybe-uninitialized>) +target_link_libraries (xrpl_core + PUBLIC + OpenSSL::Crypto + Ripple::boost + Ripple::syslibs + secp256k1::secp256k1 + ed25519::ed25519 + date::date + Ripple::opts) +#[=================================[ + main/core headers installation +#]=================================] +install ( + FILES + src/ripple/basics/algorithm.h + src/ripple/basics/Archive.h + src/ripple/basics/base64.h + src/ripple/basics/base_uint.h + src/ripple/basics/BasicConfig.h + src/ripple/basics/Blob.h + src/ripple/basics/Buffer.h + src/ripple/basics/ByteUtilities.h + src/ripple/basics/chrono.h + src/ripple/basics/comparators.h + src/ripple/basics/CompressionAlgorithms.h + src/ripple/basics/contract.h + src/ripple/basics/CountedObject.h + src/ripple/basics/DecayingSample.h + src/ripple/basics/Expected.h + src/ripple/basics/FeeUnits.h + src/ripple/basics/FileUtilities.h + src/ripple/basics/hardened_hash.h + src/ripple/basics/IOUAmount.h + src/ripple/basics/join.h + src/ripple/basics/KeyCache.h + src/ripple/basics/LocalValue.h + src/ripple/basics/Log.h + src/ripple/basics/make_SSLContext.h + src/ripple/basics/MathUtilities.h + src/ripple/basics/mulDiv.h + src/ripple/basics/Number.h + src/ripple/basics/partitioned_unordered_map.h + src/ripple/basics/PerfLog.h + src/ripple/basics/random.h + src/ripple/basics/RangeSet.h + src/ripple/basics/README.md + src/ripple/basics/ResolverAsio.h + src/ripple/basics/Resolver.h + src/ripple/basics/safe_cast.h + src/ripple/basics/scope.h + src/ripple/basics/SHAMapHash.h + src/ripple/basics/Slice.h + src/ripple/basics/spinlock.h + src/ripple/basics/strHex.h + src/ripple/basics/StringUtilities.h + src/ripple/basics/TaggedCache.h + src/ripple/basics/tagged_integer.h + src/ripple/basics/SubmitSync.h + src/ripple/basics/ThreadSafetyAnalysis.h + src/ripple/basics/ToString.h + src/ripple/basics/UnorderedContainers.h + src/ripple/basics/UptimeClock.h + src/ripple/basics/XRPAmount.h + DESTINATION include/ripple/basics) +install ( + FILES + src/ripple/crypto/RFC1751.h + src/ripple/crypto/csprng.h + src/ripple/crypto/secure_erase.h + DESTINATION include/ripple/crypto) +install ( + FILES + src/ripple/json/JsonPropertyStream.h + src/ripple/json/MultivarJson.h + src/ripple/json/Object.h + src/ripple/json/Output.h + src/ripple/json/Writer.h + src/ripple/json/json_forwards.h + src/ripple/json/json_reader.h + src/ripple/json/json_value.h + src/ripple/json/json_writer.h + src/ripple/json/to_string.h + DESTINATION include/ripple/json) +install ( + FILES + src/ripple/json/impl/json_assert.h + DESTINATION include/ripple/json/impl) +install ( + FILES + src/ripple/protocol/AccountID.h + src/ripple/protocol/AMMCore.h + src/ripple/protocol/AmountConversions.h + src/ripple/protocol/Book.h + src/ripple/protocol/BuildInfo.h + src/ripple/protocol/ErrorCodes.h + src/ripple/protocol/Feature.h + src/ripple/protocol/Fees.h + src/ripple/protocol/HashPrefix.h + src/ripple/protocol/Indexes.h + src/ripple/protocol/InnerObjectFormats.h + src/ripple/protocol/Issue.h + src/ripple/protocol/json_get_or_throw.h + src/ripple/protocol/KeyType.h + src/ripple/protocol/Keylet.h + src/ripple/protocol/KnownFormats.h + src/ripple/protocol/LedgerFormats.h + src/ripple/protocol/LedgerHeader.h + src/ripple/protocol/NFTSyntheticSerializer.h + src/ripple/protocol/NFTokenID.h + src/ripple/protocol/NFTokenOfferID.h + src/ripple/protocol/Protocol.h + src/ripple/protocol/PublicKey.h + src/ripple/protocol/Quality.h + src/ripple/protocol/QualityFunction.h + src/ripple/protocol/Rate.h + src/ripple/protocol/Rules.h + src/ripple/protocol/SField.h + src/ripple/protocol/SOTemplate.h + src/ripple/protocol/STAccount.h + src/ripple/protocol/STAmount.h + src/ripple/protocol/STIssue.h + src/ripple/protocol/STArray.h + src/ripple/protocol/STBase.h + src/ripple/protocol/STBitString.h + src/ripple/protocol/STBlob.h + src/ripple/protocol/STExchange.h + src/ripple/protocol/STInteger.h + src/ripple/protocol/STLedgerEntry.h + src/ripple/protocol/STObject.h + src/ripple/protocol/STParsedJSON.h + src/ripple/protocol/STPathSet.h + src/ripple/protocol/STTx.h + src/ripple/protocol/XChainAttestations.h + src/ripple/protocol/STXChainBridge.h + src/ripple/protocol/STValidation.h + src/ripple/protocol/STVector256.h + src/ripple/protocol/SecretKey.h + src/ripple/protocol/Seed.h + src/ripple/protocol/SeqProxy.h + src/ripple/protocol/Serializer.h + src/ripple/protocol/Sign.h + src/ripple/protocol/SystemParameters.h + src/ripple/protocol/TER.h + src/ripple/protocol/TxFlags.h + src/ripple/protocol/TxFormats.h + src/ripple/protocol/TxMeta.h + src/ripple/protocol/UintTypes.h + src/ripple/protocol/digest.h + src/ripple/protocol/jss.h + src/ripple/protocol/serialize.h + src/ripple/protocol/nft.h + src/ripple/protocol/nftPageMask.h + src/ripple/protocol/tokens.h + DESTINATION include/ripple/protocol) +install ( + FILES + src/ripple/protocol/impl/STVar.h + src/ripple/protocol/impl/secp256k1.h + DESTINATION include/ripple/protocol/impl) + +#[===================================[ + beast/legacy headers installation +#]===================================] +install ( + FILES + src/ripple/beast/clock/abstract_clock.h + src/ripple/beast/clock/basic_seconds_clock.h + src/ripple/beast/clock/manual_clock.h + DESTINATION include/ripple/beast/clock) +install ( + FILES + src/ripple/beast/core/CurrentThreadName.h + src/ripple/beast/core/LexicalCast.h + src/ripple/beast/core/List.h + src/ripple/beast/core/SemanticVersion.h + DESTINATION include/ripple/beast/core) +install ( + FILES + src/ripple/beast/hash/hash_append.h + src/ripple/beast/hash/uhash.h + src/ripple/beast/hash/xxhasher.h + DESTINATION include/ripple/beast/hash) +install ( + FILES src/ripple/beast/hash/impl/xxhash.h + DESTINATION include/ripple/beast/hash/impl) +install ( + FILES + src/ripple/beast/net/IPAddress.h + src/ripple/beast/net/IPAddressConversion.h + src/ripple/beast/net/IPAddressV4.h + src/ripple/beast/net/IPAddressV6.h + src/ripple/beast/net/IPEndpoint.h + DESTINATION include/ripple/beast/net) +install ( + FILES + src/ripple/beast/rfc2616.h + src/ripple/beast/type_name.h + src/ripple/beast/unit_test.h + src/ripple/beast/xor_shift_engine.h + DESTINATION include/ripple/beast) +install ( + FILES + src/ripple/beast/unit_test/amount.hpp + src/ripple/beast/unit_test/dstream.hpp + src/ripple/beast/unit_test/global_suites.hpp + src/ripple/beast/unit_test/main.cpp + src/ripple/beast/unit_test/match.hpp + src/ripple/beast/unit_test/recorder.hpp + src/ripple/beast/unit_test/reporter.hpp + src/ripple/beast/unit_test/results.hpp + src/ripple/beast/unit_test/runner.hpp + src/ripple/beast/unit_test/suite.hpp + src/ripple/beast/unit_test/suite_info.hpp + src/ripple/beast/unit_test/suite_list.hpp + src/ripple/beast/unit_test/thread.hpp + DESTINATION include/ripple/beast/unit_test) +install ( + FILES + src/ripple/beast/unit_test/detail/const_container.hpp + DESTINATION include/ripple/beast/unit_test/detail) +install ( + FILES + src/ripple/beast/utility/Journal.h + src/ripple/beast/utility/PropertyStream.h + src/ripple/beast/utility/Zero.h + src/ripple/beast/utility/rngfill.h + DESTINATION include/ripple/beast/utility) +# WARNING!! -- horrible levelization ahead +# (these files should be isolated or moved...but +# unfortunately unit_test.h above creates this dependency) +if (tests) + install ( + FILES + src/ripple/beast/unit_test/amount.hpp + src/ripple/beast/unit_test/dstream.hpp + src/ripple/beast/unit_test/global_suites.hpp + src/ripple/beast/unit_test/match.hpp + src/ripple/beast/unit_test/recorder.hpp + src/ripple/beast/unit_test/reporter.hpp + src/ripple/beast/unit_test/results.hpp + src/ripple/beast/unit_test/runner.hpp + src/ripple/beast/unit_test/suite.hpp + src/ripple/beast/unit_test/suite_info.hpp + src/ripple/beast/unit_test/suite_list.hpp + src/ripple/beast/unit_test/thread.hpp + DESTINATION include/ripple/beast/extras/unit_test) + install ( + FILES + src/ripple/beast/unit_test/detail/const_container.hpp + DESTINATION include/ripple/beast/unit_test/detail) +endif () #tests +#[===================================================================[ + rippled executable +#]===================================================================] + +#[=========================================================[ + this one header is added as source just to keep older + versions of cmake happy. cmake 3.10+ allows + add_executable with no sources +#]=========================================================] +add_executable (rippled src/ripple/app/main/Application.h) +if (unity) + set_target_properties(rippled PROPERTIES UNITY_BUILD ON) +endif () +if (tests) + target_compile_definitions(rippled PUBLIC ENABLE_TESTS) +endif() +target_sources (rippled PRIVATE + #[===============================[ + main sources: + subdir: app + #]===============================] + src/ripple/app/consensus/RCLConsensus.cpp + src/ripple/app/consensus/RCLCxPeerPos.cpp + src/ripple/app/consensus/RCLValidations.cpp + src/ripple/app/ledger/AcceptedLedger.cpp + src/ripple/app/ledger/AcceptedLedgerTx.cpp + src/ripple/app/ledger/AccountStateSF.cpp + src/ripple/app/ledger/BookListeners.cpp + src/ripple/app/ledger/ConsensusTransSetSF.cpp + src/ripple/app/ledger/Ledger.cpp + src/ripple/app/ledger/LedgerHistory.cpp + src/ripple/app/ledger/OrderBookDB.cpp + src/ripple/app/ledger/TransactionStateSF.cpp + src/ripple/app/ledger/impl/BuildLedger.cpp + src/ripple/app/ledger/impl/InboundLedger.cpp + src/ripple/app/ledger/impl/InboundLedgers.cpp + src/ripple/app/ledger/impl/InboundTransactions.cpp + src/ripple/app/ledger/impl/LedgerCleaner.cpp + src/ripple/app/ledger/impl/LedgerDeltaAcquire.cpp + src/ripple/app/ledger/impl/LedgerMaster.cpp + src/ripple/app/ledger/impl/LedgerReplay.cpp + src/ripple/app/ledger/impl/LedgerReplayer.cpp + src/ripple/app/ledger/impl/LedgerReplayMsgHandler.cpp + src/ripple/app/ledger/impl/LedgerReplayTask.cpp + src/ripple/app/ledger/impl/LedgerToJson.cpp + src/ripple/app/ledger/impl/LocalTxs.cpp + src/ripple/app/ledger/impl/OpenLedger.cpp + src/ripple/app/ledger/impl/SkipListAcquire.cpp + src/ripple/app/ledger/impl/TimeoutCounter.cpp + src/ripple/app/ledger/impl/TransactionAcquire.cpp + src/ripple/app/ledger/impl/TransactionMaster.cpp + src/ripple/app/main/Application.cpp + src/ripple/app/main/BasicApp.cpp + src/ripple/app/main/CollectorManager.cpp + src/ripple/app/main/GRPCServer.cpp + src/ripple/app/main/LoadManager.cpp + src/ripple/app/main/Main.cpp + src/ripple/app/main/NodeIdentity.cpp + src/ripple/app/main/NodeStoreScheduler.cpp + src/ripple/app/reporting/ReportingETL.cpp + src/ripple/app/reporting/ETLSource.cpp + src/ripple/app/reporting/P2pProxy.cpp + src/ripple/app/misc/impl/AMMHelpers.cpp + src/ripple/app/misc/impl/AMMUtils.cpp + src/ripple/app/misc/CanonicalTXSet.cpp + src/ripple/app/misc/FeeVoteImpl.cpp + src/ripple/app/misc/HashRouter.cpp + src/ripple/app/misc/NegativeUNLVote.cpp + src/ripple/app/misc/NetworkOPs.cpp + src/ripple/app/misc/SHAMapStoreImp.cpp + src/ripple/app/misc/detail/impl/WorkSSL.cpp + src/ripple/app/misc/impl/AccountTxPaging.cpp + src/ripple/app/misc/impl/AmendmentTable.cpp + src/ripple/app/misc/impl/DeliverMax.cpp + src/ripple/app/misc/impl/LoadFeeTrack.cpp + src/ripple/app/misc/impl/Manifest.cpp + src/ripple/app/misc/impl/Transaction.cpp + src/ripple/app/misc/impl/TxQ.cpp + src/ripple/app/misc/impl/ValidatorKeys.cpp + src/ripple/app/misc/impl/ValidatorList.cpp + src/ripple/app/misc/impl/ValidatorSite.cpp + src/ripple/app/paths/AccountCurrencies.cpp + src/ripple/app/paths/Credit.cpp + src/ripple/app/paths/Flow.cpp + src/ripple/app/paths/PathRequest.cpp + src/ripple/app/paths/PathRequests.cpp + src/ripple/app/paths/Pathfinder.cpp + src/ripple/app/paths/RippleCalc.cpp + src/ripple/app/paths/RippleLineCache.cpp + src/ripple/app/paths/TrustLine.cpp + src/ripple/app/paths/impl/AMMLiquidity.cpp + src/ripple/app/paths/impl/AMMOffer.cpp + src/ripple/app/paths/impl/BookStep.cpp + src/ripple/app/paths/impl/DirectStep.cpp + src/ripple/app/paths/impl/PaySteps.cpp + src/ripple/app/paths/impl/XRPEndpointStep.cpp + src/ripple/app/rdb/backend/detail/impl/Node.cpp + src/ripple/app/rdb/backend/detail/impl/Shard.cpp + src/ripple/app/rdb/backend/impl/PostgresDatabase.cpp + src/ripple/app/rdb/backend/impl/SQLiteDatabase.cpp + src/ripple/app/rdb/impl/Download.cpp + src/ripple/app/rdb/impl/PeerFinder.cpp + src/ripple/app/rdb/impl/RelationalDatabase.cpp + src/ripple/app/rdb/impl/ShardArchive.cpp + src/ripple/app/rdb/impl/State.cpp + src/ripple/app/rdb/impl/UnitaryShard.cpp + src/ripple/app/rdb/impl/Vacuum.cpp + src/ripple/app/rdb/impl/Wallet.cpp + src/ripple/app/tx/impl/AMMBid.cpp + src/ripple/app/tx/impl/AMMCreate.cpp + src/ripple/app/tx/impl/AMMDelete.cpp + src/ripple/app/tx/impl/AMMDeposit.cpp + src/ripple/app/tx/impl/AMMVote.cpp + src/ripple/app/tx/impl/AMMWithdraw.cpp + src/ripple/app/tx/impl/ApplyContext.cpp + src/ripple/app/tx/impl/BookTip.cpp + src/ripple/app/tx/impl/CancelCheck.cpp + src/ripple/app/tx/impl/CancelOffer.cpp + src/ripple/app/tx/impl/CashCheck.cpp + src/ripple/app/tx/impl/Change.cpp + src/ripple/app/tx/impl/Clawback.cpp + src/ripple/app/tx/impl/CreateCheck.cpp + src/ripple/app/tx/impl/CreateOffer.cpp + src/ripple/app/tx/impl/CreateTicket.cpp + src/ripple/app/tx/impl/DeleteAccount.cpp + src/ripple/app/tx/impl/DepositPreauth.cpp + src/ripple/app/tx/impl/DID.cpp + src/ripple/app/tx/impl/Escrow.cpp + src/ripple/app/tx/impl/InvariantCheck.cpp + src/ripple/app/tx/impl/NFTokenAcceptOffer.cpp + src/ripple/app/tx/impl/NFTokenBurn.cpp + src/ripple/app/tx/impl/NFTokenCancelOffer.cpp + src/ripple/app/tx/impl/NFTokenCreateOffer.cpp + src/ripple/app/tx/impl/NFTokenMint.cpp + src/ripple/app/tx/impl/OfferStream.cpp + src/ripple/app/tx/impl/PayChan.cpp + src/ripple/app/tx/impl/Payment.cpp + src/ripple/app/tx/impl/SetAccount.cpp + src/ripple/app/tx/impl/SetRegularKey.cpp + src/ripple/app/tx/impl/SetSignerList.cpp + src/ripple/app/tx/impl/SetTrust.cpp + src/ripple/app/tx/impl/XChainBridge.cpp + src/ripple/app/tx/impl/SignerEntries.cpp + src/ripple/app/tx/impl/Taker.cpp + src/ripple/app/tx/impl/Transactor.cpp + src/ripple/app/tx/impl/apply.cpp + src/ripple/app/tx/impl/applySteps.cpp + src/ripple/app/tx/impl/details/NFTokenUtils.cpp + #[===============================[ + main sources: + subdir: basics (partial) + #]===============================] + src/ripple/basics/impl/Archive.cpp + src/ripple/basics/impl/BasicConfig.cpp + src/ripple/basics/impl/ResolverAsio.cpp + src/ripple/basics/impl/UptimeClock.cpp + src/ripple/basics/impl/make_SSLContext.cpp + src/ripple/basics/impl/mulDiv.cpp + src/ripple/basics/impl/partitioned_unordered_map.cpp + #[===============================[ + main sources: + subdir: conditions + #]===============================] + src/ripple/conditions/impl/Condition.cpp + src/ripple/conditions/impl/Fulfillment.cpp + src/ripple/conditions/impl/error.cpp + #[===============================[ + main sources: + subdir: core + #]===============================] + src/ripple/core/impl/Config.cpp + src/ripple/core/impl/DatabaseCon.cpp + src/ripple/core/impl/Job.cpp + src/ripple/core/impl/JobQueue.cpp + src/ripple/core/impl/LoadEvent.cpp + src/ripple/core/impl/LoadMonitor.cpp + src/ripple/core/impl/SociDB.cpp + src/ripple/core/impl/Workers.cpp + src/ripple/core/Pg.cpp + #[===============================[ + main sources: + subdir: consensus + #]===============================] + src/ripple/consensus/Consensus.cpp + #[===============================[ + main sources: + subdir: ledger + #]===============================] + src/ripple/ledger/impl/ApplyStateTable.cpp + src/ripple/ledger/impl/ApplyView.cpp + src/ripple/ledger/impl/ApplyViewBase.cpp + src/ripple/ledger/impl/ApplyViewImpl.cpp + src/ripple/ledger/impl/BookDirs.cpp + src/ripple/ledger/impl/CachedView.cpp + src/ripple/ledger/impl/Directory.cpp + src/ripple/ledger/impl/OpenView.cpp + src/ripple/ledger/impl/PaymentSandbox.cpp + src/ripple/ledger/impl/RawStateTable.cpp + src/ripple/ledger/impl/ReadView.cpp + src/ripple/ledger/impl/View.cpp + #[===============================[ + main sources: + subdir: net + #]===============================] + src/ripple/net/impl/DatabaseDownloader.cpp + src/ripple/net/impl/HTTPClient.cpp + src/ripple/net/impl/HTTPDownloader.cpp + src/ripple/net/impl/HTTPStream.cpp + src/ripple/net/impl/InfoSub.cpp + src/ripple/net/impl/RPCCall.cpp + src/ripple/net/impl/RPCErr.cpp + src/ripple/net/impl/RPCSub.cpp + src/ripple/net/impl/RegisterSSLCerts.cpp + #[===============================[ + main sources: + subdir: nodestore + #]===============================] + src/ripple/nodestore/backend/CassandraFactory.cpp + src/ripple/nodestore/backend/MemoryFactory.cpp + src/ripple/nodestore/backend/NuDBFactory.cpp + src/ripple/nodestore/backend/NullFactory.cpp + src/ripple/nodestore/backend/RocksDBFactory.cpp + src/ripple/nodestore/impl/BatchWriter.cpp + src/ripple/nodestore/impl/Database.cpp + src/ripple/nodestore/impl/DatabaseNodeImp.cpp + src/ripple/nodestore/impl/DatabaseRotatingImp.cpp + src/ripple/nodestore/impl/DatabaseShardImp.cpp + src/ripple/nodestore/impl/DeterministicShard.cpp + src/ripple/nodestore/impl/DecodedBlob.cpp + src/ripple/nodestore/impl/DummyScheduler.cpp + src/ripple/nodestore/impl/ManagerImp.cpp + src/ripple/nodestore/impl/NodeObject.cpp + src/ripple/nodestore/impl/Shard.cpp + src/ripple/nodestore/impl/ShardInfo.cpp + src/ripple/nodestore/impl/TaskQueue.cpp + #[===============================[ + main sources: + subdir: overlay + #]===============================] + src/ripple/overlay/impl/Cluster.cpp + src/ripple/overlay/impl/ConnectAttempt.cpp + src/ripple/overlay/impl/Handshake.cpp + src/ripple/overlay/impl/InboundHandoff.cpp + src/ripple/overlay/impl/Message.cpp + src/ripple/overlay/impl/OverlayImpl.cpp + src/ripple/overlay/impl/PeerImp.cpp + src/ripple/overlay/impl/PeerReservationTable.cpp + src/ripple/overlay/impl/PeerSet.cpp + src/ripple/overlay/impl/ProtocolVersion.cpp + src/ripple/overlay/impl/TrafficCount.cpp + src/ripple/overlay/impl/TxMetrics.cpp + #[===============================[ + main sources: + subdir: peerfinder + #]===============================] + src/ripple/peerfinder/impl/Bootcache.cpp + src/ripple/peerfinder/impl/Endpoint.cpp + src/ripple/peerfinder/impl/PeerfinderConfig.cpp + src/ripple/peerfinder/impl/PeerfinderManager.cpp + src/ripple/peerfinder/impl/SlotImp.cpp + src/ripple/peerfinder/impl/SourceStrings.cpp + #[===============================[ + main sources: + subdir: resource + #]===============================] + src/ripple/resource/impl/Charge.cpp + src/ripple/resource/impl/Consumer.cpp + src/ripple/resource/impl/Fees.cpp + src/ripple/resource/impl/ResourceManager.cpp + #[===============================[ + main sources: + subdir: rpc + #]===============================] + src/ripple/rpc/handlers/AccountChannels.cpp + src/ripple/rpc/handlers/AccountCurrenciesHandler.cpp + src/ripple/rpc/handlers/AccountInfo.cpp + src/ripple/rpc/handlers/AccountLines.cpp + src/ripple/rpc/handlers/AccountObjects.cpp + src/ripple/rpc/handlers/AccountOffers.cpp + src/ripple/rpc/handlers/AccountTx.cpp + src/ripple/rpc/handlers/AMMInfo.cpp + src/ripple/rpc/handlers/BlackList.cpp + src/ripple/rpc/handlers/BookOffers.cpp + src/ripple/rpc/handlers/CanDelete.cpp + src/ripple/rpc/handlers/Connect.cpp + src/ripple/rpc/handlers/ConsensusInfo.cpp + src/ripple/rpc/handlers/CrawlShards.cpp + src/ripple/rpc/handlers/DepositAuthorized.cpp + src/ripple/rpc/handlers/DownloadShard.cpp + src/ripple/rpc/handlers/Feature1.cpp + src/ripple/rpc/handlers/Fee1.cpp + src/ripple/rpc/handlers/FetchInfo.cpp + src/ripple/rpc/handlers/GatewayBalances.cpp + src/ripple/rpc/handlers/GetCounts.cpp + src/ripple/rpc/handlers/LedgerAccept.cpp + src/ripple/rpc/handlers/LedgerCleanerHandler.cpp + src/ripple/rpc/handlers/LedgerClosed.cpp + src/ripple/rpc/handlers/LedgerCurrent.cpp + src/ripple/rpc/handlers/LedgerData.cpp + src/ripple/rpc/handlers/LedgerDiff.cpp + src/ripple/rpc/handlers/LedgerEntry.cpp + src/ripple/rpc/handlers/LedgerHandler.cpp + src/ripple/rpc/handlers/LedgerHeader.cpp + src/ripple/rpc/handlers/LedgerRequest.cpp + src/ripple/rpc/handlers/LogLevel.cpp + src/ripple/rpc/handlers/LogRotate.cpp + src/ripple/rpc/handlers/Manifest.cpp + src/ripple/rpc/handlers/NFTOffers.cpp + src/ripple/rpc/handlers/NodeToShard.cpp + src/ripple/rpc/handlers/NoRippleCheck.cpp + src/ripple/rpc/handlers/OwnerInfo.cpp + src/ripple/rpc/handlers/PathFind.cpp + src/ripple/rpc/handlers/PayChanClaim.cpp + src/ripple/rpc/handlers/Peers.cpp + src/ripple/rpc/handlers/Ping.cpp + src/ripple/rpc/handlers/Print.cpp + src/ripple/rpc/handlers/Random.cpp + src/ripple/rpc/handlers/Reservations.cpp + src/ripple/rpc/handlers/RipplePathFind.cpp + src/ripple/rpc/handlers/ServerInfo.cpp + src/ripple/rpc/handlers/ServerState.cpp + src/ripple/rpc/handlers/SignFor.cpp + src/ripple/rpc/handlers/SignHandler.cpp + src/ripple/rpc/handlers/Stop.cpp + src/ripple/rpc/handlers/Submit.cpp + src/ripple/rpc/handlers/SubmitMultiSigned.cpp + src/ripple/rpc/handlers/Subscribe.cpp + src/ripple/rpc/handlers/TransactionEntry.cpp + src/ripple/rpc/handlers/Tx.cpp + src/ripple/rpc/handlers/TxHistory.cpp + src/ripple/rpc/handlers/TxReduceRelay.cpp + src/ripple/rpc/handlers/UnlList.cpp + src/ripple/rpc/handlers/Unsubscribe.cpp + src/ripple/rpc/handlers/ValidationCreate.cpp + src/ripple/rpc/handlers/ValidatorInfo.cpp + src/ripple/rpc/handlers/ValidatorListSites.cpp + src/ripple/rpc/handlers/Validators.cpp + src/ripple/rpc/handlers/WalletPropose.cpp + src/ripple/rpc/impl/DeliveredAmount.cpp + src/ripple/rpc/impl/Handler.cpp + src/ripple/rpc/impl/LegacyPathFind.cpp + src/ripple/rpc/impl/RPCHandler.cpp + src/ripple/rpc/impl/RPCHelpers.cpp + src/ripple/rpc/impl/Role.cpp + src/ripple/rpc/impl/ServerHandler.cpp + src/ripple/rpc/impl/ShardArchiveHandler.cpp + src/ripple/rpc/impl/ShardVerificationScheduler.cpp + src/ripple/rpc/impl/Status.cpp + src/ripple/rpc/impl/TransactionSign.cpp + #[===============================[ + main sources: + subdir: perflog + #]===============================] + src/ripple/perflog/impl/PerfLogImp.cpp + + #[===============================[ + main sources: + subdir: server + #]===============================] + src/ripple/server/impl/JSONRPCUtil.cpp + src/ripple/server/impl/Port.cpp + #[===============================[ + main sources: + subdir: shamap + #]===============================] + src/ripple/shamap/impl/NodeFamily.cpp + src/ripple/shamap/impl/SHAMap.cpp + src/ripple/shamap/impl/SHAMapDelta.cpp + src/ripple/shamap/impl/SHAMapInnerNode.cpp + src/ripple/shamap/impl/SHAMapLeafNode.cpp + src/ripple/shamap/impl/SHAMapNodeID.cpp + src/ripple/shamap/impl/SHAMapSync.cpp + src/ripple/shamap/impl/SHAMapTreeNode.cpp + src/ripple/shamap/impl/ShardFamily.cpp) + + #[===============================[ + test sources: + subdir: app + #]===============================] +if (tests) + target_sources (rippled PRIVATE + src/test/app/AccountDelete_test.cpp + src/test/app/AccountTxPaging_test.cpp + src/test/app/AmendmentTable_test.cpp + src/test/app/AMM_test.cpp + src/test/app/AMMCalc_test.cpp + src/test/app/AMMExtended_test.cpp + src/test/app/Check_test.cpp + src/test/app/Clawback_test.cpp + src/test/app/CrossingLimits_test.cpp + src/test/app/DeliverMin_test.cpp + src/test/app/DepositAuth_test.cpp + src/test/app/Discrepancy_test.cpp + src/test/app/DID_test.cpp + src/test/app/DNS_test.cpp + src/test/app/Escrow_test.cpp + src/test/app/FeeVote_test.cpp + src/test/app/Flow_test.cpp + src/test/app/Freeze_test.cpp + src/test/app/HashRouter_test.cpp + src/test/app/LedgerHistory_test.cpp + src/test/app/LedgerLoad_test.cpp + src/test/app/LedgerMaster_test.cpp + src/test/app/LedgerReplay_test.cpp + src/test/app/LoadFeeTrack_test.cpp + src/test/app/Manifest_test.cpp + src/test/app/MultiSign_test.cpp + src/test/app/NetworkID_test.cpp + src/test/app/NFToken_test.cpp + src/test/app/NFTokenBurn_test.cpp + src/test/app/NFTokenDir_test.cpp + src/test/app/OfferStream_test.cpp + src/test/app/Offer_test.cpp + src/test/app/OversizeMeta_test.cpp + src/test/app/Path_test.cpp + src/test/app/PayChan_test.cpp + src/test/app/PayStrand_test.cpp + src/test/app/PseudoTx_test.cpp + src/test/app/RCLCensorshipDetector_test.cpp + src/test/app/RCLValidations_test.cpp + src/test/app/ReducedOffer_test.cpp + src/test/app/Regression_test.cpp + src/test/app/SHAMapStore_test.cpp + src/test/app/XChain_test.cpp + src/test/app/SetAuth_test.cpp + src/test/app/SetRegularKey_test.cpp + src/test/app/SetTrust_test.cpp + src/test/app/Taker_test.cpp + src/test/app/TheoreticalQuality_test.cpp + src/test/app/Ticket_test.cpp + src/test/app/Transaction_ordering_test.cpp + src/test/app/TrustAndBalance_test.cpp + src/test/app/TxQ_test.cpp + src/test/app/ValidatorKeys_test.cpp + src/test/app/ValidatorList_test.cpp + src/test/app/ValidatorSite_test.cpp + src/test/app/tx/apply_test.cpp + #[===============================[ + test sources: + subdir: basics + #]===============================] + src/test/basics/Buffer_test.cpp + src/test/basics/DetectCrash_test.cpp + src/test/basics/Expected_test.cpp + src/test/basics/FileUtilities_test.cpp + src/test/basics/IntrusiveShared_test.cpp + src/test/basics/IOUAmount_test.cpp + src/test/basics/KeyCache_test.cpp + src/test/basics/Number_test.cpp + src/test/basics/PerfLog_test.cpp + src/test/basics/RangeSet_test.cpp + src/test/basics/scope_test.cpp + src/test/basics/Slice_test.cpp + src/test/basics/StringUtilities_test.cpp + src/test/basics/TaggedCache_test.cpp + src/test/basics/XRPAmount_test.cpp + src/test/basics/base64_test.cpp + src/test/basics/base_uint_test.cpp + src/test/basics/contract_test.cpp + src/test/basics/FeeUnits_test.cpp + src/test/basics/hardened_hash_test.cpp + src/test/basics/join_test.cpp + src/test/basics/mulDiv_test.cpp + src/test/basics/tagged_integer_test.cpp + #[===============================[ + test sources: + subdir: beast + #]===============================] + src/test/beast/IPEndpoint_test.cpp + src/test/beast/LexicalCast_test.cpp + src/test/beast/SemanticVersion_test.cpp + src/test/beast/aged_associative_container_test.cpp + src/test/beast/beast_CurrentThreadName_test.cpp + src/test/beast/beast_Journal_test.cpp + src/test/beast/beast_PropertyStream_test.cpp + src/test/beast/beast_Zero_test.cpp + src/test/beast/beast_abstract_clock_test.cpp + src/test/beast/beast_basic_seconds_clock_test.cpp + src/test/beast/beast_io_latency_probe_test.cpp + src/test/beast/define_print.cpp + #[===============================[ + test sources: + subdir: conditions + #]===============================] + src/test/conditions/PreimageSha256_test.cpp + #[===============================[ + test sources: + subdir: consensus + #]===============================] + src/test/consensus/ByzantineFailureSim_test.cpp + src/test/consensus/Consensus_test.cpp + src/test/consensus/DistributedValidatorsSim_test.cpp + src/test/consensus/LedgerTiming_test.cpp + src/test/consensus/LedgerTrie_test.cpp + src/test/consensus/NegativeUNL_test.cpp + src/test/consensus/ScaleFreeSim_test.cpp + src/test/consensus/Validations_test.cpp + #[===============================[ + test sources: + subdir: core + #]===============================] + src/test/core/ClosureCounter_test.cpp + src/test/core/Config_test.cpp + src/test/core/Coroutine_test.cpp + src/test/core/CryptoPRNG_test.cpp + src/test/core/JobQueue_test.cpp + src/test/core/SociDB_test.cpp + src/test/core/Workers_test.cpp + #[===============================[ + test sources: + subdir: csf + #]===============================] + src/test/csf/BasicNetwork_test.cpp + src/test/csf/Digraph_test.cpp + src/test/csf/Histogram_test.cpp + src/test/csf/Scheduler_test.cpp + src/test/csf/impl/Sim.cpp + src/test/csf/impl/ledgers.cpp + #[===============================[ + test sources: + subdir: json + #]===============================] + src/test/json/Object_test.cpp + src/test/json/Output_test.cpp + src/test/json/Writer_test.cpp + src/test/json/json_value_test.cpp + src/test/json/MultivarJson_test.cpp + #[===============================[ + test sources: + subdir: jtx + #]===============================] + src/test/jtx/Env_test.cpp + src/test/jtx/WSClient_test.cpp + src/test/jtx/impl/Account.cpp + src/test/jtx/impl/AMM.cpp + src/test/jtx/impl/AMMTest.cpp + src/test/jtx/impl/Env.cpp + src/test/jtx/impl/JSONRPCClient.cpp + src/test/jtx/impl/TestHelpers.cpp + src/test/jtx/impl/WSClient.cpp + src/test/jtx/impl/acctdelete.cpp + src/test/jtx/impl/account_txn_id.cpp + src/test/jtx/impl/amount.cpp + src/test/jtx/impl/attester.cpp + src/test/jtx/impl/balance.cpp + src/test/jtx/impl/check.cpp + src/test/jtx/impl/delivermin.cpp + src/test/jtx/impl/deposit.cpp + src/test/jtx/impl/did.cpp + src/test/jtx/impl/envconfig.cpp + src/test/jtx/impl/fee.cpp + src/test/jtx/impl/flags.cpp + src/test/jtx/impl/invoice_id.cpp + src/test/jtx/impl/jtx_json.cpp + src/test/jtx/impl/last_ledger_sequence.cpp + src/test/jtx/impl/memo.cpp + src/test/jtx/impl/multisign.cpp + src/test/jtx/impl/offer.cpp + src/test/jtx/impl/owners.cpp + src/test/jtx/impl/paths.cpp + src/test/jtx/impl/pay.cpp + src/test/jtx/impl/quality2.cpp + src/test/jtx/impl/rate.cpp + src/test/jtx/impl/regkey.cpp + src/test/jtx/impl/sendmax.cpp + src/test/jtx/impl/seq.cpp + src/test/jtx/impl/xchain_bridge.cpp + src/test/jtx/impl/sig.cpp + src/test/jtx/impl/tag.cpp + src/test/jtx/impl/ticket.cpp + src/test/jtx/impl/token.cpp + src/test/jtx/impl/trust.cpp + src/test/jtx/impl/txflags.cpp + src/test/jtx/impl/utility.cpp + + #[===============================[ + test sources: + subdir: ledger + #]===============================] + src/test/ledger/BookDirs_test.cpp + src/test/ledger/Directory_test.cpp + src/test/ledger/Invariants_test.cpp + src/test/ledger/PaymentSandbox_test.cpp + src/test/ledger/PendingSaves_test.cpp + src/test/ledger/SkipList_test.cpp + src/test/ledger/View_test.cpp + #[===============================[ + test sources: + subdir: net + #]===============================] + src/test/net/DatabaseDownloader_test.cpp + #[===============================[ + test sources: + subdir: nodestore + #]===============================] + src/test/nodestore/Backend_test.cpp + src/test/nodestore/Basics_test.cpp + src/test/nodestore/DatabaseShard_test.cpp + src/test/nodestore/Database_test.cpp + src/test/nodestore/Timing_test.cpp + src/test/nodestore/import_test.cpp + src/test/nodestore/varint_test.cpp + #[===============================[ + test sources: + subdir: overlay + #]===============================] + src/test/overlay/ProtocolVersion_test.cpp + src/test/overlay/cluster_test.cpp + src/test/overlay/short_read_test.cpp + src/test/overlay/compression_test.cpp + src/test/overlay/reduce_relay_test.cpp + src/test/overlay/handshake_test.cpp + src/test/overlay/tx_reduce_relay_test.cpp + #[===============================[ + test sources: + subdir: peerfinder + #]===============================] + src/test/peerfinder/Livecache_test.cpp + src/test/peerfinder/PeerFinder_test.cpp + #[===============================[ + test sources: + subdir: protocol + #]===============================] + src/test/protocol/BuildInfo_test.cpp + src/test/protocol/InnerObjectFormats_test.cpp + src/test/protocol/Issue_test.cpp + src/test/protocol/Hooks_test.cpp + src/test/protocol/Memo_test.cpp + src/test/protocol/PublicKey_test.cpp + src/test/protocol/Quality_test.cpp + src/test/protocol/STAccount_test.cpp + src/test/protocol/STAmount_test.cpp + src/test/protocol/STObject_test.cpp + src/test/protocol/STTx_test.cpp + src/test/protocol/STValidation_test.cpp + src/test/protocol/SecretKey_test.cpp + src/test/protocol/Seed_test.cpp + src/test/protocol/SeqProxy_test.cpp + src/test/protocol/TER_test.cpp + src/test/protocol/types_test.cpp + #[===============================[ + test sources: + subdir: resource + #]===============================] + src/test/resource/Logic_test.cpp + #[===============================[ + test sources: + subdir: rpc + #]===============================] + src/test/rpc/AccountCurrencies_test.cpp + src/test/rpc/AccountInfo_test.cpp + src/test/rpc/AccountLinesRPC_test.cpp + src/test/rpc/AccountObjects_test.cpp + src/test/rpc/AccountOffers_test.cpp + src/test/rpc/AccountSet_test.cpp + src/test/rpc/AccountTx_test.cpp + src/test/rpc/AmendmentBlocked_test.cpp + src/test/rpc/AMMInfo_test.cpp + src/test/rpc/Book_test.cpp + src/test/rpc/DepositAuthorized_test.cpp + src/test/rpc/DeliveredAmount_test.cpp + src/test/rpc/Feature_test.cpp + src/test/rpc/GatewayBalances_test.cpp + src/test/rpc/GetCounts_test.cpp + src/test/rpc/JSONRPC_test.cpp + src/test/rpc/KeyGeneration_test.cpp + src/test/rpc/LedgerClosed_test.cpp + src/test/rpc/LedgerData_test.cpp + src/test/rpc/LedgerHeader_test.cpp + src/test/rpc/LedgerRPC_test.cpp + src/test/rpc/LedgerRequestRPC_test.cpp + src/test/rpc/ManifestRPC_test.cpp + src/test/rpc/NodeToShardRPC_test.cpp + src/test/rpc/NoRippleCheck_test.cpp + src/test/rpc/NoRipple_test.cpp + src/test/rpc/OwnerInfo_test.cpp + src/test/rpc/Peers_test.cpp + src/test/rpc/ReportingETL_test.cpp + src/test/rpc/Roles_test.cpp + src/test/rpc/RPCCall_test.cpp + src/test/rpc/RPCOverload_test.cpp + src/test/rpc/RobustTransaction_test.cpp + src/test/rpc/ServerInfo_test.cpp + src/test/rpc/ShardArchiveHandler_test.cpp + src/test/rpc/Status_test.cpp + src/test/rpc/Subscribe_test.cpp + src/test/rpc/Transaction_test.cpp + src/test/rpc/TransactionEntry_test.cpp + src/test/rpc/TransactionHistory_test.cpp + src/test/rpc/ValidatorInfo_test.cpp + src/test/rpc/ValidatorRPC_test.cpp + src/test/rpc/Version_test.cpp + src/test/rpc/Handler_test.cpp + #[===============================[ + test sources: + subdir: server + #]===============================] + src/test/server/ServerStatus_test.cpp + src/test/server/Server_test.cpp + #[===============================[ + test sources: + subdir: shamap + #]===============================] + src/test/shamap/FetchPack_test.cpp + src/test/shamap/SHAMapSync_test.cpp + src/test/shamap/SHAMap_test.cpp + #[===============================[ + test sources: + subdir: unit_test + #]===============================] + src/test/unit_test/multi_runner.cpp) +endif () #tests + +target_link_libraries (rippled + Ripple::boost + Ripple::opts + Ripple::libs + Ripple::xrpl_core + ) +exclude_if_included (rippled) +# define a macro for tests that might need to +# be exluded or run differently in CI environment +if (is_ci) + target_compile_definitions(rippled PRIVATE RIPPLED_RUNNING_IN_CI) +endif () + +if(reporting) +set_target_properties(rippled PROPERTIES OUTPUT_NAME rippled-reporting) +get_target_property(BIN_NAME rippled OUTPUT_NAME) +message(STATUS "Reporting mode build: rippled renamed ${BIN_NAME}") + target_compile_definitions(rippled PRIVATE RIPPLED_REPORTING) +endif() + +# any files that don't play well with unity should be added here +if (tests) + set_source_files_properties( + # these two seem to produce conflicts in beast teardown template methods + src/test/rpc/ValidatorRPC_test.cpp + src/test/rpc/ShardArchiveHandler_test.cpp + PROPERTIES SKIP_UNITY_BUILD_INCLUSION TRUE) +endif () #tests diff --git a/include/xrpl/basics/IntrusivePointer.h b/include/xrpl/basics/IntrusivePointer.h new file mode 100644 index 00000000000..7e35fece403 --- /dev/null +++ b/include/xrpl/basics/IntrusivePointer.h @@ -0,0 +1,494 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2023 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_BASICS_INTRUSIVEPOINTER_H_INCLUDED +#define RIPPLE_BASICS_INTRUSIVEPOINTER_H_INCLUDED + +// shared pointer class for tree pointers +// The ref counts are kept on the tree pointers themselves +// I.e. this is an intrusive pointer type. + +#include +#include +#include +#include +#include + +namespace ripple { + +//------------------------------------------------------------------------------ + +/** Tag to create an intrusive pointer from another intrusive pointer by using a + static cast. This is useful to create an intrusive pointer to a derived + class from an intrusive pointer to a base class. +*/ +struct StaticCastTagSharedIntrusive +{ +}; + +/** Tag to create an intrusive pointer from another intrusive pointer by using a + static cast. This is useful to create an intrusive pointer to a derived + class from an intrusive pointer to a base class. If the cast fails an empty + (null) intrusive pointer is created. +*/ +struct DynamicCastTagSharedIntrusive +{ +}; + +/** When creating or adopting a raw pointer, controls whether the strong count + is incremented or not. Use this tag to increment the strong count. +*/ +struct SharedIntrusiveAdoptIncrementStrongTag +{ +}; + +/** When creating or adopting a raw pointer, controls whether the strong count + is incremented or not. Use this tag to leave the strong count unchanged. +*/ +struct SharedIntrusiveAdoptNoIncrementTag +{ +}; + +//------------------------------------------------------------------------------ +// + +// clang-format off +template +concept CAdoptTag = + std::is_same_v || + std::is_same_v; +// clang-format on + +//------------------------------------------------------------------------------ + +/** A shared intrusive pointer class that supports weak pointers. + + This is meant to be used for SHAMapInnerNodes, but may be useful for other + cases. Since the reference counts are stored on the pointee, the pointee is + not destroyed until both the strong _and_ weak pointer counts go to zero. + When the strong pointer count goes to zero, the "partialDestructor" is + called. This can be used to destroy as much of the object as possible while + still retaining the reference counts. For example, for SHAMapInnerNodes the + children may be reset in that function. Note that std::shared_poiner WILL + run the destructor when the strong count reaches zero, but may not free the + memory used by the object until the weak count reaches zero. In rippled, we + typically allocate shared pointers with the `make_shared` function. When + that is used, the memory is not reclaimed until the weak count reaches zero. +*/ +template +class SharedIntrusive +{ +public: + SharedIntrusive() = default; + + template + SharedIntrusive(T* p, TAdoptTag) noexcept; + + SharedIntrusive(SharedIntrusive const& rhs); + + template + // TODO: convertible_to isn't quite right. That include a static castable. + // Find the right concept. + requires std::convertible_to + SharedIntrusive(SharedIntrusive const& rhs); + + SharedIntrusive(SharedIntrusive&& rhs); + + template + requires std::convertible_to + SharedIntrusive(SharedIntrusive&& rhs); + + SharedIntrusive& + operator=(SharedIntrusive const& rhs); + + template + // clang-format off + requires std::convertible_to + // clang-format on + SharedIntrusive& + operator=(SharedIntrusive const& rhs); + + SharedIntrusive& + operator=(SharedIntrusive&& rhs); + + template + // clang-format off + requires std::convertible_to + // clang-format on + SharedIntrusive& + operator=(SharedIntrusive&& rhs); + + /** Adopt the raw pointer. The strong reference may or may not be + incremented, depending on the TAdoptTag + */ + template + void + adopt(T* p); + + ~SharedIntrusive(); + + /** Create a new SharedIntrusive by statically casting the pointer + controlled by the rhs param. + */ + template + SharedIntrusive( + StaticCastTagSharedIntrusive, + SharedIntrusive const& rhs); + + /** Create a new SharedIntrusive by statically casting the pointer + controlled by the rhs param. + */ + template + SharedIntrusive(StaticCastTagSharedIntrusive, SharedIntrusive&& rhs); + + /** Create a new SharedIntrusive by dynamically casting the pointer + controlled by the rhs param. + */ + template + SharedIntrusive( + DynamicCastTagSharedIntrusive, + SharedIntrusive const& rhs); + + /** Create a new SharedIntrusive by dynamically casting the pointer + controlled by the rhs param. + */ + template + SharedIntrusive(DynamicCastTagSharedIntrusive, SharedIntrusive&& rhs); + + T& + operator*() const noexcept; + + T* + operator->() const noexcept; + + explicit operator bool() const noexcept; + + /** Set the pointer to null, decrement the strong count, and run the + appropriate release action. + */ + void + reset(); + + /** Get the raw pointer */ + T* + get() const; + + /** Return the strong count */ + std::size_t + use_count() const; + + template + friend SharedIntrusive + make_SharedIntrusive(Args&&... args); + + /** Return the raw pointer held by this object. */ + T* + unsafeGetRawPtr() const; + + /** Exchange the current raw pointer held by this object with the given + pointer. Decrement the strong count of the raw pointer previously held + by this object and run the appropriate release action. + */ + void + unsafeReleaseAndStore(T* next); + + /** Set the raw pointer directly. This is wrapped in a function so the class + can support both atomic and non-atomic pointers in a future patch. + */ + void + unsafeSetRawPtr(T* p); + + /** Exchange the raw pointer directly. + This sets the raw pointer to the given value and returns the previous + value. This is wrapped in a function so the class can support both + atomic and non-atomic pointers in a future patch. + */ + T* + unsafeExchange(T* p); + +private: + /** pointer to the type with an intrusive count */ + T* ptr_{nullptr}; +}; + +//------------------------------------------------------------------------------ + +/** A weak intrusive pointer class for the SharedIntrusive pointer class. + +Note that this weak pointer class asks differently from normal weak pointer +classes. When the strong pointer count goes to zero, the "partialDestructor" +is called. See the comment on SharedIntrusive for a fuller explanation. +*/ +template +class WeakIntrusive +{ +public: + WeakIntrusive() = default; + + WeakIntrusive(WeakIntrusive const& rhs); + + WeakIntrusive(WeakIntrusive&& rhs); + + WeakIntrusive(SharedIntrusive const& rhs); + + // There is no move constructor from a strong intrusive ptr because + // moving would be move expensive than copying in this case (the strong + // ref would need to be decremented) + WeakIntrusive(SharedIntrusive const&& rhs) = delete; + + template + requires std::convertible_to WeakIntrusive& + operator=(SharedIntrusive const& rhs); + + /** Adopt the raw pointer and increment the weak count. */ + void + adopt(T* ptr); + + ~WeakIntrusive(); + + /** Get a strong pointer from the weak pointer, if possible. This will + only return a seated pointer if the strong count on the raw pointer + is non-zero before locking. + */ + SharedIntrusive + lock() const; + + /** Return true if the strong count is zero. */ + bool + expired() const; + + /** Set the pointer to null and decrement the weak count. + + Note: This may run the destructor if the strong count is zero. + */ + void + reset(); + +private: + T* ptr_ = nullptr; + + /** Decrement the weak count. This does _not_ set the raw pointer to + null. + + Note: This may run the destructor if the strong count is zero. + */ + void + unsafeReleaseNoStore(); +}; + +//------------------------------------------------------------------------------ + +/** A combination of a strong and a weak intrusive pointer stored in the + space of a single pointer. + + This class is similar to a `std::variant` + with some optimizations. In particular, it uses a low-order bit to + determine if the raw pointer represents a strong pointer or a weak + pointer. It can also be quickly switched between its strong pointer and + weak pointer representations. This class is useful for storing intrusive + pointers in tagged caches. + */ + +template +class SharedWeakUnion +{ + static_assert( + alignof(T) >= 2, + "Bad alignment: Combo pointer requires low bit to be zero"); + +public: + SharedWeakUnion() = default; + + SharedWeakUnion(SharedWeakUnion const& rhs); + + template + requires std::convertible_to + SharedWeakUnion(SharedIntrusive const& rhs); + + SharedWeakUnion(SharedWeakUnion&& rhs); + + template + requires std::convertible_to + SharedWeakUnion(SharedIntrusive&& rhs); + + SharedWeakUnion& + operator=(SharedWeakUnion const& rhs); + + template + requires std::convertible_to SharedWeakUnion& + operator=(SharedIntrusive const& rhs); + + template + requires std::convertible_to SharedWeakUnion& + operator=(SharedIntrusive&& rhs); + + ~SharedWeakUnion(); + + /** Return a strong pointer if this is already a strong pointer (i.e. + don't lock the weak pointer. Use the `lock` method if that's what's + needed) + */ + SharedIntrusive + getStrong() const; + + /** Return true if this is a strong pointer and the strong pointer is + seated. + */ + explicit operator bool() const noexcept; + + /** Set the pointer to null, decrement the appropriate ref count, and + run the appropriate release action. + */ + void + reset(); + + /** If this is a strong pointer, return the raw pointer. Otherwise + return null. + */ + T* + get() const; + + /** If this is a strong pointer, return the strong count. Otherwise + * return 0 + */ + std::size_t + use_count() const; + + /** Return true if there is a non-zero strong count. */ + bool + expired() const; + + /** If this is a strong pointer, return the strong pointer. Otherwise + attempt to lock the weak pointer. + */ + SharedIntrusive + lock() const; + + /** Return true is this represents a strong pointer. */ + bool + isStrong() const; + + /** Return true is this represents a weak pointer. */ + bool + isWeak() const; + + /** If this is a weak pointer, attempt to convert it to a strong + pointer. + + @return true if successfully converted to a strong pointer (or was + already a strong pointer). Otherwise false. + */ + bool + convertToStrong(); + + /** If this is a strong pointer, attempt to convert it to a weak + pointer. + + @return false if the pointer is null. Otherwise return true. + */ + bool + convertToWeak(); + +private: + // Tagged pointer. Low bit determines if this is a strong or a weak + // pointer. The low bit must be masked to zero when converting back to a + // pointer. If the low bit is '1', this is a weak pointer. + std::uintptr_t tp_{0}; + static constexpr std::uintptr_t tagMask = 1; + static constexpr std::uintptr_t ptrMask = ~tagMask; + +private: + /** Return the raw pointer held by this object. + */ + T* + unsafeGetRawPtr() const; + + enum class RefStrength { strong, weak }; + /** Set the raw pointer and tag bit directly. + */ + void + unsafeSetRawPtr(T* p, RefStrength rs); + + /** Set the raw pointer and tag bit to all zeros (strong null pointer). + */ + void unsafeSetRawPtr(std::nullptr_t); + + /** Decrement the appropriate ref count, and run the appropriate release + action. Note: this does _not_ set the raw pointer to null. + */ + void + unsafeReleaseNoStore(); +}; + +//------------------------------------------------------------------------------ + +/** Create a shared intrusive pointer. + + Note: unlike std::shared_ptr, where there is an advantage of allocating + the pointer and control block together, there is no benefit for intrusive + pointers. +*/ +template +SharedIntrusive +make_SharedIntrusive(Args&&... args) +{ + auto p = new TT(std::forward(args)...); + + static_assert( + noexcept(SharedIntrusive( + std::declval(), + std::declval())), + "SharedIntrusive constructor should not throw or this can leak " + "memory"); + + return SharedIntrusive(p, SharedIntrusiveAdoptNoIncrementTag{}); +} + +//------------------------------------------------------------------------------ + +namespace intr_ptr { +template +using SharedPtr = SharedIntrusive; + +template +using WeakPtr = WeakIntrusive; + +template +SharedPtr +make_shared(A&&... args) +{ + return make_SharedIntrusive(std::forward(args)...); +} + +template +SharedPtr +static_pointer_cast(TT const& v) +{ + return SharedPtr(StaticCastTagSharedIntrusive{}, v); +} + +template +SharedPtr +dynamic_pointer_cast(TT const& v) +{ + return SharedPtr(DynamicCastTagSharedIntrusive{}, v); +} +} // namespace intr_ptr +} // namespace ripple +#endif diff --git a/include/xrpl/basics/IntrusivePointer.ipp b/include/xrpl/basics/IntrusivePointer.ipp new file mode 100644 index 00000000000..1bd130eb140 --- /dev/null +++ b/include/xrpl/basics/IntrusivePointer.ipp @@ -0,0 +1,720 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2023 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_BASICS_INTRUSIVEPOINTER_IPP_INCLUDED +#define RIPPLE_BASICS_INTRUSIVEPOINTER_IPP_INCLUDED + +#include + +#include + +#include + +namespace ripple { + +template +template +SharedIntrusive::SharedIntrusive(T* p, TAdoptTag) noexcept : ptr_{p} +{ + if constexpr (std::is_same_v< + TAdoptTag, + SharedIntrusiveAdoptIncrementStrongTag>) + { + if (p) + p->addStrongRef(); + } +} + +template +SharedIntrusive::SharedIntrusive(SharedIntrusive const& rhs) + : ptr_{[&] { + auto p = rhs.unsafeGetRawPtr(); + if (p) + p->addStrongRef(); + return p; + }()} +{ +} + +template +template +requires std::convertible_to +SharedIntrusive::SharedIntrusive(SharedIntrusive const& rhs) + : ptr_{[&] { + auto p = rhs.unsafeGetRawPtr(); + if (p) + p->addStrongRef(); + return p; + }()} +{ +} + +template +SharedIntrusive::SharedIntrusive(SharedIntrusive&& rhs) + : ptr_{rhs.unsafeExchange(nullptr)} +{ +} + +template +template +requires std::convertible_to +SharedIntrusive::SharedIntrusive(SharedIntrusive&& rhs) + : ptr_{rhs.unsafeExchange(nullptr)} +{ +} +template +SharedIntrusive& +SharedIntrusive::operator=(SharedIntrusive const& rhs) +{ + if (this == &rhs) + return *this; + auto p = rhs.unsafeGetRawPtr(); + if (p) + p->addStrongRef(); + unsafeReleaseAndStore(p); + return *this; +} + +template +template +// clang-format off +requires std::convertible_to + // clang-format on + SharedIntrusive& + SharedIntrusive::operator=(SharedIntrusive const& rhs) +{ + if constexpr (std::is_same_v) + { + // This case should never be hit. The operator above will run instead. + // (The normal operator= is needed or it will be marked `deleted`) + if (this == &rhs) + return *this; + } + auto p = rhs.unsafeGetRawPtr(); + if (p) + p->addStrongRef(); + unsafeReleaseAndStore(p); + return *this; +} + +template +SharedIntrusive& +SharedIntrusive::operator=(SharedIntrusive&& rhs) +{ + if (this == &rhs) + return *this; + + unsafeReleaseAndStore(rhs.unsafeExchange(nullptr)); + return *this; +} + +template +template +// clang-format off +requires std::convertible_to + // clang-format on + SharedIntrusive& + SharedIntrusive::operator=(SharedIntrusive&& rhs) +{ + if constexpr (std::is_same_v) + { + // This case should never be hit. The operator above will run instead. + // (The normal operator= is needed or it will be marked `deleted`) + if (this == &rhs) + return *this; + } + + unsafeReleaseAndStore(rhs.unsafeExchange(nullptr)); + return *this; +} + +template +template +void +SharedIntrusive::adopt(T* p) +{ + if constexpr (std::is_same_v< + TAdoptTag, + SharedIntrusiveAdoptIncrementStrongTag>) + { + if (p) + p->addStrongRef(); + } + unsafeReleaseAndStore(p); +} + +template +SharedIntrusive::~SharedIntrusive() +{ + unsafeReleaseAndStore(nullptr); +}; + +template +template +SharedIntrusive::SharedIntrusive( + StaticCastTagSharedIntrusive, + SharedIntrusive const& rhs) + : ptr_{[&] { + auto p = static_cast(rhs.unsafeGetRawPtr()); + if (p) + p->addStrongRef(); + return p; + }()} +{ +} + +template +template +SharedIntrusive::SharedIntrusive( + StaticCastTagSharedIntrusive, + SharedIntrusive&& rhs) + : ptr_{static_cast(rhs.unsafeExchange(nullptr))} +{ +} + +template +template +SharedIntrusive::SharedIntrusive( + DynamicCastTagSharedIntrusive, + SharedIntrusive const& rhs) + : ptr_{[&] { + auto p = dynamic_cast(rhs.unsafeGetRawPtr()); + if (p) + p->addStrongRef(); + return p; + }()} +{ +} + +template +template +SharedIntrusive::SharedIntrusive( + DynamicCastTagSharedIntrusive, + SharedIntrusive&& rhs) +{ + // This can be simplified without the `exchange`, but the `exchange` is kept + // in anticipation of supporting atomic operations. + auto toSet = rhs.unsafeExchange(nullptr); + if (toSet) + { + ptr_ = dynamic_cast(toSet); + if (!ptr_) + // need to set the pointer back or will leak + rhs.unsafeExchange(toSet); + } +} + +template +T& +SharedIntrusive::operator*() const noexcept +{ + return *unsafeGetRawPtr(); +} + +template +T* +SharedIntrusive::operator->() const noexcept +{ + return unsafeGetRawPtr(); +} + +template +SharedIntrusive::operator bool() const noexcept +{ + return bool(unsafeGetRawPtr()); +} + +template +void +SharedIntrusive::reset() +{ + unsafeReleaseAndStore(nullptr); +} + +template +T* +SharedIntrusive::get() const +{ + return unsafeGetRawPtr(); +} + +template +std::size_t +SharedIntrusive::use_count() const +{ + if (auto p = unsafeGetRawPtr()) + return p->use_count(); + return 0; +} + +template +T* +SharedIntrusive::unsafeGetRawPtr() const +{ + return ptr_; +} + +template +void +SharedIntrusive::unsafeSetRawPtr(T* p) +{ + ptr_ = p; +} + +template +T* +SharedIntrusive::unsafeExchange(T* p) +{ + return std::exchange(ptr_, p); +} + +template +void +SharedIntrusive::unsafeReleaseAndStore(T* next) +{ + auto prev = unsafeExchange(next); + if (!prev) + return; + + using enum ReleaseRefAction; + auto action = prev->releaseStrongRef(); + switch (action) + { + case noop: + break; + case destroy: + delete prev; + break; + case partialDestroy: + prev->partialDestructor(); + partialDestructorFinished(&prev); + // prev is null and may no longer be used + break; + } +} + +//------------------------------------------------------------------------------ + +template +WeakIntrusive::WeakIntrusive(WeakIntrusive const& rhs) : ptr_{rhs.ptr_} +{ + if (ptr_) + ptr_->addWeakRef(); +} + +template +WeakIntrusive::WeakIntrusive(WeakIntrusive&& rhs) : ptr_{rhs.ptr_} +{ + rhs.ptr_ = nullptr; +} + +template +WeakIntrusive::WeakIntrusive(SharedIntrusive const& rhs) + : ptr_{rhs.unsafeGetRawPtr()} +{ + if (ptr_) + ptr_->addWeakRef(); +} + +template +template +// clang-format off +requires std::convertible_to + // clang-format on + WeakIntrusive& + WeakIntrusive::operator=(SharedIntrusive const& rhs) +{ + unsafeReleaseNoStore(); + auto p = rhs.unsafeGetRawPtr(); + if (p) + p->addWeakRef(); + return *this; +} + +template +void +WeakIntrusive::adopt(T* ptr) +{ + unsafeReleaseNoStore(); + if (ptr) + ptr->addWeakRef(); + ptr_ = ptr; +} + +template +WeakIntrusive::~WeakIntrusive() +{ + unsafeReleaseNoStore(); +} + +template +SharedIntrusive +WeakIntrusive::lock() const +{ + if (ptr_ && ptr_->checkoutStrongRefFromWeak()) + { + return SharedIntrusive{ptr_, SharedIntrusiveAdoptNoIncrementTag{}}; + } + return {}; +} + +template +bool +WeakIntrusive::expired() const +{ + return (!ptr_ || ptr_->expired()); +} + +template +void +WeakIntrusive::reset() +{ + if (!ptr_) + return; + + unsafeReleaseNoStore(); + ptr_ = nullptr; +} + +template +void +WeakIntrusive::unsafeReleaseNoStore() +{ + if (!ptr_) + return; + + using enum ReleaseRefAction; + auto action = ptr_->releaseWeakRef(); + switch (action) + { + case noop: + break; + case destroy: + delete ptr_; + break; + case partialDestroy: + assert(0); // only a strong pointer should case a + // partialDestruction + ptr_->partialDestructor(); + partialDestructorFinished(&ptr_); + // ptr_ is null and may no longer be used + break; + } +} + +//------------------------------------------------------------------------------ + +template +SharedWeakUnion::SharedWeakUnion(SharedWeakUnion const& rhs) : tp_{rhs.tp_} +{ + auto p = rhs.unsafeGetRawPtr(); + if (!p) + return; + + if (rhs.isStrong()) + p->addStrongRef(); + else + p->addWeakRef(); +} + +template +template +requires std::convertible_to +SharedWeakUnion::SharedWeakUnion(SharedIntrusive const& rhs) +{ + auto p = rhs.unsafeGetRawPtr(); + if (p) + p->addStrongRef(); + unsafeSetRawPtr(p, RefStrength::strong); +} + +template +SharedWeakUnion::SharedWeakUnion(SharedWeakUnion&& rhs) : tp_{rhs.tp_} +{ + rhs.unsafeSetRawPtr(nullptr); +} + +template +template +requires std::convertible_to +SharedWeakUnion::SharedWeakUnion(SharedIntrusive&& rhs) +{ + auto p = rhs.unsafeGetRawPtr(); + if (p) + unsafeSetRawPtr(p, RefStrength::strong); + rhs.unsafeSetRawPtr(nullptr); +} + +template +SharedWeakUnion& +SharedWeakUnion::operator=(SharedWeakUnion const& rhs) +{ + if (this == &rhs) + return *this; + unsafeReleaseNoStore(); + + if (auto p = rhs.unsafeGetRawPtr()) + { + if (rhs.isStrong()) + { + p->addStrongRef(); + unsafeSetRawPtr(p, RefStrength::strong); + } + else + { + p->addWeakRef(); + unsafeSetRawPtr(p, RefStrength::weak); + } + } + else + { + unsafeSetRawPtr(nullptr); + } + return *this; +} + +template +template +// clang-format off +requires std::convertible_to + // clang-format on + SharedWeakUnion& + SharedWeakUnion::operator=(SharedIntrusive const& rhs) +{ + unsafeReleaseNoStore(); + auto p = rhs.unsafeGetRawPtr(); + if (p) + p->addStrongRef(); + unsafeSetRawPtr(p, RefStrength::strong); + return *this; +} + +template +template +// clang-format off +requires std::convertible_to + // clang-format on + SharedWeakUnion& + SharedWeakUnion::operator=(SharedIntrusive&& rhs) +{ + unsafeReleaseNoStore(); + unsafeSetRawPtr(rhs.unsafeGetRawPtr(), RefStrength::strong); + rhs.unsafeSetRawPtr(nullptr); + return *this; +} + +template +SharedWeakUnion::~SharedWeakUnion() +{ + unsafeReleaseNoStore(); +}; + +// Return a strong pointer if this is already a strong pointer (i.e. don't +// lock the weak pointer. Use the `lock` method if that's what's needed) +template +SharedIntrusive +SharedWeakUnion::getStrong() const +{ + SharedIntrusive result; + auto p = unsafeGetRawPtr(); + if (p && isStrong()) + { + result.template adopt(p); + } + return result; +} + +template +SharedWeakUnion::operator bool() const noexcept +{ + return bool(get()); +} + +template +void +SharedWeakUnion::reset() +{ + unsafeReleaseNoStore(); + unsafeSetRawPtr(nullptr); +} + +template +T* +SharedWeakUnion::get() const +{ + return isStrong() ? unsafeGetRawPtr() : nullptr; +} + +template +std::size_t +SharedWeakUnion::use_count() const +{ + if (auto p = get()) + return p->use_count(); + return 0; +} + +template +bool +SharedWeakUnion::expired() const +{ + auto p = unsafeGetRawPtr(); + return (!p || p->expired()); +} + +template +SharedIntrusive +SharedWeakUnion::lock() const +{ + SharedIntrusive result; + auto p = unsafeGetRawPtr(); + if (!p) + return result; + + if (isStrong()) + { + result.template adopt(p); + return result; + } + + if (p->checkoutStrongRefFromWeak()) + { + result.template adopt(p); + return result; + } + return result; +} + +template +bool +SharedWeakUnion::isStrong() const +{ + return !(tp_ & tagMask); +} + +template +bool +SharedWeakUnion::isWeak() const +{ + return tp_ & tagMask; +} + +template +bool +SharedWeakUnion::convertToStrong() +{ + if (isStrong()) + return true; + + auto p = unsafeGetRawPtr(); + if (p && p->checkoutStrongRefFromWeak()) + { + auto action = p->releaseWeakRef(); + (void)action; + assert(action == ReleaseRefAction::noop); + unsafeSetRawPtr(p, RefStrength::strong); + return true; + } + return false; +} + +template +bool +SharedWeakUnion::convertToWeak() +{ + if (isWeak()) + return true; + + auto p = unsafeGetRawPtr(); + if (!p) + return false; + + using enum ReleaseRefAction; + auto action = p->addWeakReleaseStrongRef(); + switch (action) + { + case noop: + break; + case destroy: + // We just added a weak ref. How could we destroy? + assert(0); + delete p; + unsafeSetRawPtr(nullptr); + return true; // Should never happen + case partialDestroy: + // This is a weird case. We just converted the last strong + // pointer to a weak pointer. + p->partialDestructor(); + partialDestructorFinished(&p); + // p is null and may no longer be used + break; + } + unsafeSetRawPtr(p, RefStrength::weak); + return true; +} + +template +T* +SharedWeakUnion::unsafeGetRawPtr() const +{ + return reinterpret_cast(tp_ & ptrMask); +} + +template +void +SharedWeakUnion::unsafeSetRawPtr(T* p, RefStrength rs) +{ + tp_ = reinterpret_cast(p); + if (tp_ && rs == RefStrength::weak) + tp_ |= tagMask; +} + +template +void SharedWeakUnion::unsafeSetRawPtr(std::nullptr_t) +{ + tp_ = 0; +} + +template +void +SharedWeakUnion::unsafeReleaseNoStore() +{ + auto p = unsafeGetRawPtr(); + if (!p) + return; + + using enum ReleaseRefAction; + auto action = isStrong() ? p->releaseStrongRef() : p->releaseWeakRef(); + switch (action) + { + case noop: + break; + case destroy: + delete p; + break; + case partialDestroy: + p->partialDestructor(); + partialDestructorFinished(&p); + // p is null and may no longer be used + break; + } +} + +} // namespace ripple +#endif diff --git a/include/xrpl/basics/IntrusiveRefCounts.h b/include/xrpl/basics/IntrusiveRefCounts.h new file mode 100644 index 00000000000..07a3c301e2f --- /dev/null +++ b/include/xrpl/basics/IntrusiveRefCounts.h @@ -0,0 +1,466 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2023 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_BASICS_INTRUSIVEREFCOUNTS_H_INCLUDED +#define RIPPLE_BASICS_INTRUSIVEREFCOUNTS_H_INCLUDED + +#include +#include +#include + +namespace ripple { + +/** Action to perform when releasing a strong or weak pointer. + + noop: Do nothing. For example, a `noop` action will occur when a count is + decremented to a non-zero value. + + partialDestroy: Run the `partialDestructor`. This action will happen when a + strong count is decremented to zero and the weak count is non-zero. + + destroy: Run the destructor. This action will occur when either the strong + count or weak count is decremented and the other count is also zero. + */ +enum class ReleaseRefAction { noop, partialDestroy, destroy }; + +/** Implement the strong count, weak count, and bit flags for an intrusive + pointer. + + A class can satisfy the requirements of a ripple::IntrusivePointer by + inheriting from this class. + */ +struct IntrusiveRefCounts +{ + virtual ~IntrusiveRefCounts() noexcept; + + // This must be `noexcept` or the make_SharedIntrusive function could leak + // memory. + void + addStrongRef() const noexcept; + + void + addWeakRef() const noexcept; + + ReleaseRefAction + releaseStrongRef() const; + + // Same as: + // { + // addWeakRef(); + // return releaseStrongRef; + // } + // done as one atomic operation + ReleaseRefAction + addWeakReleaseStrongRef() const; + + ReleaseRefAction + releaseWeakRef() const; + + // Returns true is able to checkout a strong ref. False otherwise + bool + checkoutStrongRefFromWeak() const noexcept; + + bool + expired() const noexcept; + + std::size_t + use_count() const noexcept; + + // This function MUST be called after a partial destructor finishes running. + // Calling this function may cause other threads to delete the object + // pointed to by `o`, so `o` should never be used after calling this + // function. The parameter will be set to a `nullptr` after calling this + // function to emphasize that it should not be used. + // Note: This is intentionally NOT called at the end of `partialDestructor`. + // The reason for this is if new classes are written to support this smart + // pointer class, they need to write their own `partialDestructor` function + // and ensure `partialDestructorFinished` is called at the end. Putting this + // call inside the smart pointer class itself is expected to be less error + // prone. + // Note: The "two-star" programming is intentional. It emphasizes that `o` + // may be deleted and the unergonomic API is meant to signal the special + // nature of this function call to callers. + // Note: This is a template to support incompletely defined classes. + template + friend void + partialDestructorFinished(T** o); + +private: + // TODO: We may need to use a uint64_t for both counts. This will reduce the + // memory savings. We need to audit the code to make sure 16 bit counts are + // enough for strong pointers and 14 bit counts are enough for weak + // pointers. Use type aliases to make it easy to switch types. + using CountType = std::uint16_t; + static constexpr size_t StrongCountNumBits = sizeof(CountType) * 8; + static constexpr size_t WeakCountNumBits = StrongCountNumBits - 2; + using FieldType = std::uint32_t; + static constexpr size_t FieldTypeBits = sizeof(FieldType) * 8; + static constexpr FieldType one = 1; + + /** `refCounts` consists of four fields that are treated atomically: + + 1. Strong count. This is a count of the number of shared pointers that + hold a reference to this object. When the strong counts goes to zero, + if the weak count is zero, the destructor is run. If the weak count is + non-zero when the strong count goes to zero then the partialDestructor + is run. + + 2. Weak count. This is a count of the number of weak pointer that hold + a reference to this object. When the weak count goes to zero and the + strong count is also zero, then the destructor is run. + + 3. Partial destroy started bit. This bit is set if the + `partialDestructor` function has been started (or is about to be + started). This is used to prevent the destructor from running + concurrently with the partial destructor. This can easily happen when + the last strong pointer release its reference in one thread and starts + the partialDestructor, while in another thread the last weak pointer + goes out of scope and starts the destructor while the partialDestructor + is still running. Both a start and finished bit is needed to handle a + corner-case where the last strong pointer goes out of scope, then then + last `weakPointer` goes out of scope, but this happens before the + `partialDestructor` bit is set. It would be possible to use a single + bit if it could also be set atomically when the strong count goes to + zero and the weak count is non-zero, but that would add complexity (and + likely slow down common cases as well). + + 4. Partial destroy finished bit. This bit is set when the + `partialDestructor` has finished running. See (3) above for more + information. + + */ + + mutable std::atomic refCounts{strongDelta}; + + /** Amount to change the strong count when adding or releasing a reference + + Note: The strong count is stored in the low `StrongCountNumBits` bits + of refCounts + */ + static constexpr FieldType strongDelta = 1; + + /** Amount to change the weak count when adding or releasing a reference + + Note: The weak count is stored in the high `WeakCountNumBits` bits of + refCounts + */ + static constexpr FieldType weakDelta = (one << StrongCountNumBits); + + /** Flag that is set when the partialDestroy function has started running + (or is about to start running). + + See description of the `refCounts` field for a fuller description of + this field. + */ + static constexpr FieldType partialDestroyStartedMask = + (one << (FieldTypeBits - 1)); + + /** Flag that is set when the partialDestroy function has finished running + + See description of the `refCounts` field for a fuller description of + this field. + */ + static constexpr FieldType partialDestroyFinishedMask = + (one << (FieldTypeBits - 2)); + + /** Mask that will zero out all the `count` bits and leave the tag bits + unchanged. + */ + static constexpr FieldType tagMask = + partialDestroyStartedMask | partialDestroyFinishedMask; + + /** Mask that will zero out the `tag` bits and leave the count bits + unchanged. + */ + static constexpr FieldType valueMask = ~tagMask; + + /** Mask that will zero out everything except the strong count. + */ + static constexpr FieldType strongMask = + ((one << StrongCountNumBits) - 1) & valueMask; + + /** Mask that will zero out everything except the weak count. + */ + static constexpr FieldType weakMask = + (((one << WeakCountNumBits) - 1) << StrongCountNumBits) & valueMask; + + /** Unpack the count and tag fields from the packed atomic integer form. */ + struct RefCountPair + { + CountType strong; + CountType weak; + /** The `partialDestroyStartedBit` is set to on when the partial + destroy function is started. It is not a boolean; it is a uint32 + with all bits zero with the possible exception of the + `partialDestroyStartedMask` bit. This is done so it can be directly + masked into the `combinedValue`. + */ + FieldType partialDestroyStartedBit{0}; + /** The `partialDestroyFinishedBit` is set to on when the partial + destroy function has finished. + */ + FieldType partialDestroyFinishedBit{0}; + RefCountPair(FieldType v) noexcept; + RefCountPair(CountType s, CountType w) noexcept; + + /** Convert back to the packed integer form. */ + FieldType + combinedValue() const noexcept; + + static constexpr CountType maxStrongValue = + static_cast((one << StrongCountNumBits) - 1); + static constexpr CountType maxWeakValue = + static_cast((one << WeakCountNumBits) - 1); + /** Put an extra margin to detect when running up against limits. + This is only used in debug code, and is useful if we reduce the + number of bits in the strong and weak counts (to 16 and 14 bits). + */ + static constexpr CountType checkStrongMaxValue = maxStrongValue - 32; + static constexpr CountType checkWeakMaxValue = maxWeakValue - 32; + }; +}; + +inline void +IntrusiveRefCounts::addStrongRef() const noexcept +{ + refCounts.fetch_add(strongDelta, std::memory_order_acq_rel); +} + +inline void +IntrusiveRefCounts::addWeakRef() const noexcept +{ + refCounts.fetch_add(weakDelta, std::memory_order_acq_rel); +} + +inline ReleaseRefAction +IntrusiveRefCounts::releaseStrongRef() const +{ + // Subtract `strongDelta` from refCounts. If this releases the last strong + // ref, set the `partialDestroyStarted` bit. It is important that the ref + // count and the `partialDestroyStartedBit` are changed atomically (hence + // the loop and `compare_exchange` op). If this didn't need to be done + // atomically, the loop could be replaced with a `fetch_sub` and a + // conditional `fetch_or`. This loop will almost always run once. + + using enum ReleaseRefAction; + auto prevIntVal = refCounts.load(std::memory_order_acquire); + while (1) + { + RefCountPair const prevVal{prevIntVal}; + assert(prevVal.strong >= strongDelta); + auto nextIntVal = prevIntVal - strongDelta; + ReleaseRefAction action = noop; + if (prevVal.strong == 1) + { + if (prevVal.weak == 0) + { + action = destroy; + } + else + { + nextIntVal |= partialDestroyStartedMask; + action = partialDestroy; + } + } + + if (refCounts.compare_exchange_weak( + prevIntVal, nextIntVal, std::memory_order_release)) + { + // Can't be in partial destroy because only decrementing the strong + // count to zero can start a partial destroy, and that can't happen + // twice. + assert( + (action == noop) || !(prevIntVal & partialDestroyStartedMask)); + return action; + } + } +} + +inline ReleaseRefAction +IntrusiveRefCounts::addWeakReleaseStrongRef() const +{ + using enum ReleaseRefAction; + + static_assert(weakDelta > strongDelta); + auto constexpr delta = weakDelta - strongDelta; + auto prevIntVal = refCounts.load(std::memory_order_acquire); + // This loop will almost always run once. The loop is needed to atomically + // change the counts and flags (the count could be atomically changed, but + // the flags depend on the current value of the counts). + // + // Note: If this becomes a perf bottleneck, the `partialDestoryStartedMask` + // may be able to be set non-atomically. But it is easier to reason about + // the code if the flag is set atomically. + while (1) + { + RefCountPair const prevVal{prevIntVal}; + // Converted the last strong pointer to a weak pointer. + // + // Can't be in partial destroy because only decrementing the + // strong count to zero can start a partial destroy, and that + // can't happen twice. + assert(!prevVal.partialDestroyStartedBit); + + auto nextIntVal = prevIntVal + delta; + ReleaseRefAction action = noop; + if (prevVal.strong == 1) + { + if (prevVal.weak == 0) + { + action = noop; + } + else + { + nextIntVal |= partialDestroyStartedMask; + action = partialDestroy; + } + } + if (refCounts.compare_exchange_weak( + prevIntVal, nextIntVal, std::memory_order_release)) + { + assert(!(prevIntVal & partialDestroyStartedMask)); + return action; + } + } +} + +inline ReleaseRefAction +IntrusiveRefCounts::releaseWeakRef() const +{ + auto prevIntVal = refCounts.fetch_sub(weakDelta, std::memory_order_acq_rel); + RefCountPair prev = prevIntVal; + if (prev.weak == 1 && prev.strong == 0) + { + if (!prev.partialDestroyStartedBit) + { + // This case should only be hit if the partialDestroyStartedBit is + // set non-atomically (and even then very rarely). The code is kept + // in case we need to set the flag non-atomically for perf reasons. + refCounts.wait(prevIntVal, std::memory_order_acq_rel); + prevIntVal = refCounts.load(std::memory_order_acquire); + prev = RefCountPair{prevIntVal}; + } + if (!prev.partialDestroyFinishedBit) + { + // partial destroy MUST finish before running a full destroy (when + // using weak pointers) + refCounts.wait(prevIntVal - weakDelta, std::memory_order_acq_rel); + } + return ReleaseRefAction::destroy; + } + return ReleaseRefAction::noop; +} + +inline bool +IntrusiveRefCounts::checkoutStrongRefFromWeak() const noexcept +{ + auto curValue = RefCountPair{1, 1}.combinedValue(); + auto desiredValue = RefCountPair{2, 1}.combinedValue(); + + while (!refCounts.compare_exchange_weak( + curValue, desiredValue, std::memory_order_release)) + { + RefCountPair const prev{curValue}; + if (!prev.strong) + return false; + + desiredValue = curValue + strongDelta; + } + return true; +} + +inline bool +IntrusiveRefCounts::expired() const noexcept +{ + RefCountPair const val = refCounts.load(std::memory_order_acquire); + return val.strong == 0; +} + +inline std::size_t +IntrusiveRefCounts::use_count() const noexcept +{ + RefCountPair const val = refCounts.load(std::memory_order_acquire); + return val.strong; +} + +inline IntrusiveRefCounts::~IntrusiveRefCounts() noexcept +{ +#ifndef NDEBUG + auto v = refCounts.load(std::memory_order_acquire); + assert(!(v & valueMask)); + auto t = v & tagMask; + assert(!t || t == tagMask); +#endif +} + +//------------------------------------------------------------------------------ + +inline IntrusiveRefCounts::RefCountPair::RefCountPair( + IntrusiveRefCounts::FieldType v) noexcept + : strong{static_cast(v & strongMask)} + , weak{static_cast((v & weakMask) >> StrongCountNumBits)} + , partialDestroyStartedBit{v & partialDestroyStartedMask} + , partialDestroyFinishedBit{v & partialDestroyFinishedMask} +{ + assert(strong < checkStrongMaxValue && weak < checkWeakMaxValue); +} + +inline IntrusiveRefCounts::RefCountPair::RefCountPair( + IntrusiveRefCounts::CountType s, + IntrusiveRefCounts::CountType w) noexcept + : strong{s}, weak{w} +{ + assert(strong < checkStrongMaxValue && weak < checkWeakMaxValue); +} + +inline IntrusiveRefCounts::FieldType +IntrusiveRefCounts::RefCountPair::combinedValue() const noexcept +{ + assert(strong < checkStrongMaxValue && weak < checkWeakMaxValue); + return (static_cast(weak) + << IntrusiveRefCounts::StrongCountNumBits) | + static_cast(strong) | + partialDestroyStartedBit | partialDestroyFinishedBit; +} + +template +inline void +partialDestructorFinished(T** o) +{ + T& self = **o; + IntrusiveRefCounts::RefCountPair p = + self.refCounts.fetch_or(IntrusiveRefCounts::partialDestroyFinishedMask); + assert( + !p.partialDestroyFinishedBit && p.partialDestroyStartedBit && + !p.strong); + if (!p.weak) + { + // There was a weak count before the partial destructor ran (or we would + // have run the full destructor) and now there isn't a weak count. Some + // thread is waiting to run the destructor. + self.refCounts.notify_one(); + } + // Set the pointer to null to emphasize that the object shouldn't be used + // after calling this function as it may be destroyed in another thread. + *o = nullptr; +} +//------------------------------------------------------------------------------ + +} // namespace ripple +#endif diff --git a/include/xrpl/basics/SharedWeakCachePointer.h b/include/xrpl/basics/SharedWeakCachePointer.h new file mode 100644 index 00000000000..5682ac998ab --- /dev/null +++ b/include/xrpl/basics/SharedWeakCachePointer.h @@ -0,0 +1,132 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2023 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_BASICS_SHAREDWEAKCACHEPOINTER_H_INCLUDED +#define RIPPLE_BASICS_SHAREDWEAKCACHEPOINTER_H_INCLUDED + +#include +#include + +namespace ripple { + +/** A combination of a std::shared_ptr and a std::weak_pointer. + + +This class is a wrapper to a `std::variant` +This class is useful for storing intrusive pointers in tagged caches using less +memory than storing both pointers directly. +*/ + +template +class SharedWeakCachePointer +{ +public: + SharedWeakCachePointer() = default; + + SharedWeakCachePointer(SharedWeakCachePointer const& rhs); + + template + requires std::convertible_to + SharedWeakCachePointer(std::shared_ptr const& rhs); + + SharedWeakCachePointer(SharedWeakCachePointer&& rhs); + + template + requires std::convertible_to + SharedWeakCachePointer(std::shared_ptr&& rhs); + + SharedWeakCachePointer& + operator=(SharedWeakCachePointer const& rhs); + + template + requires std::convertible_to SharedWeakCachePointer& + operator=(std::shared_ptr const& rhs); + + template + requires std::convertible_to SharedWeakCachePointer& + operator=(std::shared_ptr&& rhs); + + ~SharedWeakCachePointer(); + + /** Return a strong pointer if this is already a strong pointer (i.e. don't + lock the weak pointer. Use the `lock` method if that's what's needed) + */ + std::shared_ptr const& + getStrong() const; + + /** Return true if this is a strong pointer and the strong pointer is + seated. + */ + explicit operator bool() const noexcept; + + /** Set the pointer to null, decrement the appropriate ref count, and run + the appropriate release action. + */ + void + reset(); + + /** If this is a strong pointer, return the raw pointer. Otherwise return + null. + */ + T* + get() const; + + /** If this is a strong pointer, return the strong count. Otherwise return 0 + */ + std::size_t + use_count() const; + + /** Return true if there is a non-zero strong count. */ + bool + expired() const; + + /** If this is a strong pointer, return the strong pointer. Otherwise + attempt to lock the weak pointer. + */ + std::shared_ptr + lock() const; + + /** Return true is this represents a strong pointer. */ + bool + isStrong() const; + + /** Return true is this represents a weak pointer. */ + bool + isWeak() const; + + /** If this is a weak pointer, attempt to convert it to a strong pointer. + + @return true if successfully converted to a strong pointer (or was + already a strong pointer). Otherwise false. + */ + bool + convertToStrong(); + + /** If this is a strong pointer, attempt to convert it to a weak pointer. + + @return false if the pointer is null. Otherwise return true. + */ + bool + convertToWeak(); + +private: + std::variant, std::weak_ptr> combo_; +}; +} // namespace ripple +#endif diff --git a/include/xrpl/basics/SharedWeakCachePointer.ipp b/include/xrpl/basics/SharedWeakCachePointer.ipp new file mode 100644 index 00000000000..2def4110b12 --- /dev/null +++ b/include/xrpl/basics/SharedWeakCachePointer.ipp @@ -0,0 +1,190 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2023 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_BASICS_SHAREDWEAKCACHEPOINTER_IPP_INCLUDED +#define RIPPLE_BASICS_SHAREDWEAKCACHEPOINTER_IPP_INCLUDED + +#include + +namespace ripple { +template +SharedWeakCachePointer::SharedWeakCachePointer( + SharedWeakCachePointer const& rhs) = default; + +template +template +requires std::convertible_to +SharedWeakCachePointer::SharedWeakCachePointer( + std::shared_ptr const& rhs) + : combo_{rhs} +{ +} + +template +SharedWeakCachePointer::SharedWeakCachePointer( + SharedWeakCachePointer&& rhs) = default; + +template +template +requires std::convertible_to +SharedWeakCachePointer::SharedWeakCachePointer(std::shared_ptr&& rhs) + : combo_{std::move(rhs)} +{ +} + +template +SharedWeakCachePointer& +SharedWeakCachePointer::operator=(SharedWeakCachePointer const& rhs) = + default; + +template +template +requires std::convertible_to SharedWeakCachePointer& +SharedWeakCachePointer::operator=(std::shared_ptr const& rhs) +{ + combo_ = rhs; + return *this; +} + +template +template +requires std::convertible_to SharedWeakCachePointer& +SharedWeakCachePointer::operator=(std::shared_ptr&& rhs) +{ + combo_ = std::move(rhs); + return *this; +} + +template +SharedWeakCachePointer::~SharedWeakCachePointer() = default; + +// Return a strong pointer if this is already a strong pointer (i.e. don't +// lock the weak pointer. Use the `lock` method if that's what's needed) +template +std::shared_ptr const& +SharedWeakCachePointer::getStrong() const +{ + static std::shared_ptr const empty; + if (auto p = std::get_if>(&combo_)) + return *p; + return empty; +} + +template +SharedWeakCachePointer::operator bool() const noexcept +{ + return !!std::get_if>(&combo_); +} + +template +void +SharedWeakCachePointer::reset() +{ + combo_ = std::shared_ptr{}; +} + +template +T* +SharedWeakCachePointer::get() const +{ + return std::get_if>(&combo_).get(); +} + +template +std::size_t +SharedWeakCachePointer::use_count() const +{ + if (auto p = std::get_if>(&combo_)) + return p->use_count(); + return 0; +} + +template +bool +SharedWeakCachePointer::expired() const +{ + if (auto p = std::get_if>(&combo_)) + return p->expired(); + return !std::get_if>(&combo_); +} + +template +std::shared_ptr +SharedWeakCachePointer::lock() const +{ + if (auto p = std::get_if>(&combo_)) + return *p; + + if (auto p = std::get_if>(&combo_)) + return p->lock(); + + return {}; +} + +template +bool +SharedWeakCachePointer::isStrong() const +{ + if (auto p = std::get_if>(&combo_)) + return !!p->get(); + return false; +} + +template +bool +SharedWeakCachePointer::isWeak() const +{ + return !isStrong(); +} + +template +bool +SharedWeakCachePointer::convertToStrong() +{ + if (isStrong()) + return true; + + if (auto p = std::get_if>(&combo_)) + { + if (auto s = p->lock()) + { + combo_ = std::move(s); + return true; + } + } + return false; +} + +template +bool +SharedWeakCachePointer::convertToWeak() +{ + if (isWeak()) + return true; + + if (auto p = std::get_if>(&combo_)) + { + combo_ = std::weak_ptr(*p); + return true; + } + + return false; +} +} // namespace ripple +#endif diff --git a/include/xrpl/basics/TaggedCache.h b/include/xrpl/basics/TaggedCache.h index 9ff63b1e0ce..658ed64774b 100644 --- a/include/xrpl/basics/TaggedCache.h +++ b/include/xrpl/basics/TaggedCache.h @@ -20,6 +20,8 @@ #ifndef RIPPLE_BASICS_TAGGEDCACHE_H_INCLUDED #define RIPPLE_BASICS_TAGGEDCACHE_H_INCLUDED +#include +#include #include #include #include @@ -50,6 +52,8 @@ template < class Key, class T, bool IsKeyCache = false, + class SharedWeakUnionPointerType = SharedWeakCachePointer, + class SharedPointerType = std::shared_ptr, class Hash = hardened_hash<>, class KeyEqual = std::equal_to, class Mutex = std::recursive_mutex> @@ -60,6 +64,8 @@ class TaggedCache using key_type = Key; using mapped_type = T; using clock_type = beast::abstract_clock; + using shared_weak_combo_pointer_type = SharedWeakUnionPointerType; + using shared_pointer_type = SharedPointerType; public: TaggedCache( @@ -111,9 +117,7 @@ class TaggedCache bool touch_if_exists(KeyComparable const& key); - using SweptPointersVector = std::pair< - std::vector>, - std::vector>>; + using SweptPointersVector = std::vector; void sweep(); @@ -135,21 +139,22 @@ class TaggedCache @return `true` If the key already existed. */ public: + template bool canonicalize( const key_type& key, - std::shared_ptr& data, - std::function const&)>&& replace); + SharedPointerType& data, + R&& replaceCallback); bool canonicalize_replace_cache( const key_type& key, - std::shared_ptr const& data); + SharedPointerType const& data); bool - canonicalize_replace_client(const key_type& key, std::shared_ptr& data); + canonicalize_replace_client(const key_type& key, SharedPointerType& data); - std::shared_ptr + SharedPointerType fetch(const key_type& key); /** Insert the element into the container. @@ -190,12 +195,12 @@ class TaggedCache std::shared_ptr(void) */ template - std::shared_ptr + SharedPointerType fetch(key_type const& digest, Handler const& h); // End CachedSLEs functions. private: - std::shared_ptr + SharedPointerType initialFetch(key_type const& key, std::lock_guard const& l); void @@ -245,36 +250,35 @@ class TaggedCache class ValueEntry { public: - std::shared_ptr ptr; - std::weak_ptr weak_ptr; + shared_weak_combo_pointer_type ptr; clock_type::time_point last_access; ValueEntry( clock_type::time_point const& last_access_, - std::shared_ptr const& ptr_) - : ptr(ptr_), weak_ptr(ptr_), last_access(last_access_) + shared_pointer_type const& ptr_) + : ptr(ptr_), last_access(last_access_) { } bool isWeak() const { - return ptr == nullptr; + return !ptr; } bool isCached() const { - return ptr != nullptr; + return !!ptr; } bool isExpired() const { - return weak_ptr.expired(); + return ptr.expired(); } - std::shared_ptr + SharedPointerType lock() { - return weak_ptr.lock(); + return ptr.lock(); } void touch(clock_type::time_point const& now) diff --git a/include/xrpl/basics/TaggedCache.ipp b/include/xrpl/basics/TaggedCache.ipp index ebf70f9af84..08f231325c8 100644 --- a/include/xrpl/basics/TaggedCache.ipp +++ b/include/xrpl/basics/TaggedCache.ipp @@ -22,22 +22,35 @@ #include +#include + namespace ripple { template < class Key, class T, bool IsKeyCache, + class SharedWeakUnionPointer, + class SharedPointerType, class Hash, class KeyEqual, class Mutex> -TaggedCache::TaggedCache( - std::string const& name, - int size, - clock_type::duration expiration, - clock_type& clock, - beast::Journal journal, - beast::insight::Collector::ptr const& collector) +TaggedCache< + Key, + T, + IsKeyCache, + SharedWeakUnionPointer, + SharedPointerType, + Hash, + KeyEqual, + Mutex>:: + TaggedCache( + std::string const& name, + int size, + clock_type::duration expiration, + clock_type& clock, + beast::Journal journal, + beast::insight::Collector::ptr const& collector) : m_journal(journal) , m_clock(clock) , m_stats(name, std::bind(&TaggedCache::collect_metrics, this), collector) @@ -50,30 +63,48 @@ TaggedCache::TaggedCache( { } -/** Return the clock associated with the cache. */ template < class Key, class T, bool IsKeyCache, + class SharedWeakUnionPointer, + class SharedPointerType, class Hash, class KeyEqual, class Mutex> auto -TaggedCache::clock() -> clock_type& +TaggedCache< + Key, + T, + IsKeyCache, + SharedWeakUnionPointer, + SharedPointerType, + Hash, + KeyEqual, + Mutex>::clock() -> clock_type& { return m_clock; } -/** Returns the number of items in the container. */ template < class Key, class T, bool IsKeyCache, + class SharedWeakUnionPointer, + class SharedPointerType, class Hash, class KeyEqual, class Mutex> std::size_t -TaggedCache::size() const +TaggedCache< + Key, + T, + IsKeyCache, + SharedWeakUnionPointer, + SharedPointerType, + Hash, + KeyEqual, + Mutex>::size() const { std::lock_guard lock(m_mutex); return m_cache.size(); @@ -83,11 +114,21 @@ template < class Key, class T, bool IsKeyCache, + class SharedWeakUnionPointer, + class SharedPointerType, class Hash, class KeyEqual, class Mutex> void -TaggedCache::setTargetSize(int s) +TaggedCache< + Key, + T, + IsKeyCache, + SharedWeakUnionPointer, + SharedPointerType, + Hash, + KeyEqual, + Mutex>::setTargetSize(int s) { std::lock_guard lock(m_mutex); m_target_size = s; @@ -110,12 +151,21 @@ template < class Key, class T, bool IsKeyCache, + class SharedWeakUnionPointer, + class SharedPointerType, class Hash, class KeyEqual, class Mutex> auto -TaggedCache::getTargetAge() const - -> clock_type::duration +TaggedCache< + Key, + T, + IsKeyCache, + SharedWeakUnionPointer, + SharedPointerType, + Hash, + KeyEqual, + Mutex>::getTargetAge() const -> clock_type::duration { std::lock_guard lock(m_mutex); return m_target_age; @@ -125,12 +175,21 @@ template < class Key, class T, bool IsKeyCache, + class SharedWeakUnionPointer, + class SharedPointerType, class Hash, class KeyEqual, class Mutex> void -TaggedCache::setTargetAge( - clock_type::duration s) +TaggedCache< + Key, + T, + IsKeyCache, + SharedWeakUnionPointer, + SharedPointerType, + Hash, + KeyEqual, + Mutex>::setTargetAge(clock_type::duration s) { std::lock_guard lock(m_mutex); m_target_age = s; @@ -142,11 +201,21 @@ template < class Key, class T, bool IsKeyCache, + class SharedWeakUnionPointer, + class SharedPointerType, class Hash, class KeyEqual, class Mutex> int -TaggedCache::getCacheSize() const +TaggedCache< + Key, + T, + IsKeyCache, + SharedWeakUnionPointer, + SharedPointerType, + Hash, + KeyEqual, + Mutex>::getCacheSize() const { std::lock_guard lock(m_mutex); return m_cache_count; @@ -156,11 +225,21 @@ template < class Key, class T, bool IsKeyCache, + class SharedWeakUnionPointer, + class SharedPointerType, class Hash, class KeyEqual, class Mutex> int -TaggedCache::getTrackSize() const +TaggedCache< + Key, + T, + IsKeyCache, + SharedWeakUnionPointer, + SharedPointerType, + Hash, + KeyEqual, + Mutex>::getTrackSize() const { std::lock_guard lock(m_mutex); return m_cache.size(); @@ -170,11 +249,21 @@ template < class Key, class T, bool IsKeyCache, + class SharedWeakUnionPointer, + class SharedPointerType, class Hash, class KeyEqual, class Mutex> float -TaggedCache::getHitRate() +TaggedCache< + Key, + T, + IsKeyCache, + SharedWeakUnionPointer, + SharedPointerType, + Hash, + KeyEqual, + Mutex>::getHitRate() { std::lock_guard lock(m_mutex); auto const total = static_cast(m_hits + m_misses); @@ -185,11 +274,21 @@ template < class Key, class T, bool IsKeyCache, + class SharedWeakUnionPointer, + class SharedPointerType, class Hash, class KeyEqual, class Mutex> void -TaggedCache::clear() +TaggedCache< + Key, + T, + IsKeyCache, + SharedWeakUnionPointer, + SharedPointerType, + Hash, + KeyEqual, + Mutex>::clear() { std::lock_guard lock(m_mutex); m_cache.clear(); @@ -200,11 +299,21 @@ template < class Key, class T, bool IsKeyCache, + class SharedWeakUnionPointer, + class SharedPointerType, class Hash, class KeyEqual, class Mutex> void -TaggedCache::reset() +TaggedCache< + Key, + T, + IsKeyCache, + SharedWeakUnionPointer, + SharedPointerType, + Hash, + KeyEqual, + Mutex>::reset() { std::lock_guard lock(m_mutex); m_cache.clear(); @@ -217,13 +326,22 @@ template < class Key, class T, bool IsKeyCache, + class SharedWeakUnionPointer, + class SharedPointerType, class Hash, class KeyEqual, class Mutex> template bool -TaggedCache::touch_if_exists( - KeyComparable const& key) +TaggedCache< + Key, + T, + IsKeyCache, + SharedWeakUnionPointer, + SharedPointerType, + Hash, + KeyEqual, + Mutex>::touch_if_exists(KeyComparable const& key) { std::lock_guard lock(m_mutex); auto const iter(m_cache.find(key)); @@ -241,11 +359,21 @@ template < class Key, class T, bool IsKeyCache, + class SharedWeakUnionPointer, + class SharedPointerType, class Hash, class KeyEqual, class Mutex> void -TaggedCache::sweep() +TaggedCache< + Key, + T, + IsKeyCache, + SharedWeakUnionPointer, + SharedPointerType, + Hash, + KeyEqual, + Mutex>::sweep() { // Keep references to all the stuff we sweep // For performance, each worker thread should exit before the swept data @@ -311,13 +439,21 @@ template < class Key, class T, bool IsKeyCache, + class SharedWeakUnionPointer, + class SharedPointerType, class Hash, class KeyEqual, class Mutex> bool -TaggedCache::del( - const key_type& key, - bool valid) +TaggedCache< + Key, + T, + IsKeyCache, + SharedWeakUnionPointer, + SharedPointerType, + Hash, + KeyEqual, + Mutex>::del(const key_type& key, bool valid) { // Remove from cache, if !valid, remove from map too. Returns true if // removed from cache @@ -335,7 +471,7 @@ TaggedCache::del( if (entry.isCached()) { --m_cache_count; - entry.ptr.reset(); + entry.ptr.convertToWeak(); ret = true; } @@ -349,14 +485,26 @@ template < class Key, class T, bool IsKeyCache, + class SharedWeakUnionPointer, + class SharedPointerType, class Hash, class KeyEqual, class Mutex> +template bool -TaggedCache::canonicalize( - const key_type& key, - std::shared_ptr& data, - std::function const&)>&& replace) +TaggedCache< + Key, + T, + IsKeyCache, + SharedWeakUnionPointer, + SharedPointerType, + Hash, + KeyEqual, + Mutex>:: + canonicalize( + const key_type& key, + SharedPointerType& data, + R&& replaceCallback) { // Return canonical value, store if needed, refresh in cache // Return values: true=we had the data already @@ -377,16 +525,29 @@ TaggedCache::canonicalize( Entry& entry = cit->second; entry.touch(m_clock.now()); + auto shouldReplace = [&] { + if constexpr (std::is_invocable_r_v) + { + // The reason for this extra complexity is for intrusive + // strong/weak combo getting a strong is relatively expensive + // and not needed for many cases. + return replaceCallback(); + } + else + { + return replaceCallback(entry.ptr.getStrong()); + } + }; + if (entry.isCached()) { - if (replace(entry.ptr)) + if (shouldReplace()) { entry.ptr = data; - entry.weak_ptr = data; } else { - data = entry.ptr; + data = entry.ptr.getStrong(); } return true; @@ -396,14 +557,13 @@ TaggedCache::canonicalize( if (cachedData) { - if (replace(entry.ptr)) + if (shouldReplace()) { entry.ptr = data; - entry.weak_ptr = data; } else { - entry.ptr = cachedData; + entry.ptr.convertToStrong(); data = cachedData; } @@ -412,7 +572,6 @@ TaggedCache::canonicalize( } entry.ptr = data; - entry.weak_ptr = data; ++m_cache_count; return false; @@ -422,46 +581,72 @@ template < class Key, class T, bool IsKeyCache, + class SharedWeakUnionPointer, + class SharedPointerType, class Hash, class KeyEqual, class Mutex> bool -TaggedCache:: +TaggedCache< + Key, + T, + IsKeyCache, + SharedWeakUnionPointer, + SharedPointerType, + Hash, + KeyEqual, + Mutex>:: canonicalize_replace_cache( const key_type& key, - std::shared_ptr const& data) + SharedPointerType const& data) { return canonicalize( - key, - const_cast&>(data), - [](std::shared_ptr const&) { return true; }); + key, const_cast(data), []() { return true; }); } template < class Key, class T, bool IsKeyCache, + class SharedWeakUnionPointer, + class SharedPointerType, class Hash, class KeyEqual, class Mutex> bool -TaggedCache:: - canonicalize_replace_client(const key_type& key, std::shared_ptr& data) +TaggedCache< + Key, + T, + IsKeyCache, + SharedWeakUnionPointer, + SharedPointerType, + Hash, + KeyEqual, + Mutex>:: + canonicalize_replace_client(const key_type& key, SharedPointerType& data) { - return canonicalize( - key, data, [](std::shared_ptr const&) { return false; }); + return canonicalize(key, data, []() { return false; }); } template < class Key, class T, bool IsKeyCache, + class SharedWeakUnionPointer, + class SharedPointerType, class Hash, class KeyEqual, class Mutex> -std::shared_ptr -TaggedCache::fetch( - const key_type& key) +SharedPointerType +TaggedCache< + Key, + T, + IsKeyCache, + SharedWeakUnionPointer, + SharedPointerType, + Hash, + KeyEqual, + Mutex>::fetch(const key_type& key) { std::lock_guard l(m_mutex); auto ret = initialFetch(key, l); @@ -474,30 +659,61 @@ template < class Key, class T, bool IsKeyCache, + class SharedWeakUnionPointer, + class SharedPointerType, class Hash, class KeyEqual, class Mutex> template auto -TaggedCache::insert( - key_type const& key, - T const& value) -> std::enable_if_t +TaggedCache< + Key, + T, + IsKeyCache, + SharedWeakUnionPointer, + SharedPointerType, + Hash, + KeyEqual, + Mutex>::insert(key_type const& key, T const& value) + -> std::enable_if_t { - auto p = std::make_shared(std::cref(value)); - return canonicalize_replace_client(key, p); + static_assert( + std::is_same_v, SharedPointerType> || + std::is_same_v, SharedPointerType>); + + if constexpr (std::is_same_v, SharedPointerType>) + { + auto p = std::make_shared(std::cref(value)); + return canonicalize_replace_client(key, p); + } + if constexpr (std::is_same_v, SharedPointerType>) + { + auto p = intr_ptr::make_shared(std::cref(value)); + return canonicalize_replace_client(key, p); + } } template < class Key, class T, bool IsKeyCache, + class SharedWeakUnionPointer, + class SharedPointerType, class Hash, class KeyEqual, class Mutex> template auto -TaggedCache::insert( - key_type const& key) -> std::enable_if_t +TaggedCache< + Key, + T, + IsKeyCache, + SharedWeakUnionPointer, + SharedPointerType, + Hash, + KeyEqual, + Mutex>::insert(key_type const& key) + -> std::enable_if_t { std::lock_guard lock(m_mutex); clock_type::time_point const now(m_clock.now()); @@ -514,13 +730,21 @@ template < class Key, class T, bool IsKeyCache, + class SharedWeakUnionPointer, + class SharedPointerType, class Hash, class KeyEqual, class Mutex> bool -TaggedCache::retrieve( - const key_type& key, - T& data) +TaggedCache< + Key, + T, + IsKeyCache, + SharedWeakUnionPointer, + SharedPointerType, + Hash, + KeyEqual, + Mutex>::retrieve(const key_type& key, T& data) { // retrieve the value of the stored data auto entry = fetch(key); @@ -536,12 +760,21 @@ template < class Key, class T, bool IsKeyCache, + class SharedWeakUnionPointer, + class SharedPointerType, class Hash, class KeyEqual, class Mutex> auto -TaggedCache::peekMutex() - -> mutex_type& +TaggedCache< + Key, + T, + IsKeyCache, + SharedWeakUnionPointer, + SharedPointerType, + Hash, + KeyEqual, + Mutex>::peekMutex() -> mutex_type& { return m_mutex; } @@ -550,12 +783,21 @@ template < class Key, class T, bool IsKeyCache, + class SharedWeakUnionPointer, + class SharedPointerType, class Hash, class KeyEqual, class Mutex> auto -TaggedCache::getKeys() const - -> std::vector +TaggedCache< + Key, + T, + IsKeyCache, + SharedWeakUnionPointer, + SharedPointerType, + Hash, + KeyEqual, + Mutex>::getKeys() const -> std::vector { std::vector v; @@ -573,11 +815,21 @@ template < class Key, class T, bool IsKeyCache, + class SharedWeakUnionPointer, + class SharedPointerType, class Hash, class KeyEqual, class Mutex> double -TaggedCache::rate() const +TaggedCache< + Key, + T, + IsKeyCache, + SharedWeakUnionPointer, + SharedPointerType, + Hash, + KeyEqual, + Mutex>::rate() const { std::lock_guard lock(m_mutex); auto const tot = m_hits + m_misses; @@ -590,14 +842,22 @@ template < class Key, class T, bool IsKeyCache, + class SharedWeakUnionPointer, + class SharedPointerType, class Hash, class KeyEqual, class Mutex> template -std::shared_ptr -TaggedCache::fetch( - key_type const& digest, - Handler const& h) +SharedPointerType +TaggedCache< + Key, + T, + IsKeyCache, + SharedWeakUnionPointer, + SharedPointerType, + Hash, + KeyEqual, + Mutex>::fetch(key_type const& digest, Handler const& h) { { std::lock_guard l(m_mutex); @@ -615,7 +875,7 @@ TaggedCache::fetch( m_cache.emplace(digest, Entry(m_clock.now(), std::move(sle))); if (!inserted) it->second.touch(m_clock.now()); - return it->second.ptr; + return it->second.ptr.getStrong(); } // End CachedSLEs functions. @@ -623,13 +883,22 @@ template < class Key, class T, bool IsKeyCache, + class SharedWeakUnionPointer, + class SharedPointerType, class Hash, class KeyEqual, class Mutex> -std::shared_ptr -TaggedCache::initialFetch( - key_type const& key, - std::lock_guard const& l) +SharedPointerType +TaggedCache< + Key, + T, + IsKeyCache, + SharedWeakUnionPointer, + SharedPointerType, + Hash, + KeyEqual, + Mutex>:: + initialFetch(key_type const& key, std::lock_guard const& l) { auto cit = m_cache.find(key); if (cit == m_cache.end()) @@ -640,7 +909,7 @@ TaggedCache::initialFetch( { ++m_hits; entry.touch(m_clock.now()); - return entry.ptr; + return entry.ptr.getStrong(); } entry.ptr = entry.lock(); if (entry.isCached()) @@ -648,7 +917,7 @@ TaggedCache::initialFetch( // independent of cache size, so not counted as a hit ++m_cache_count; entry.touch(m_clock.now()); - return entry.ptr; + return entry.ptr.getStrong(); } m_cache.erase(cit); @@ -659,11 +928,21 @@ template < class Key, class T, bool IsKeyCache, + class SharedWeakUnionPointer, + class SharedPointerType, class Hash, class KeyEqual, class Mutex> void -TaggedCache::collect_metrics() +TaggedCache< + Key, + T, + IsKeyCache, + SharedWeakUnionPointer, + SharedPointerType, + Hash, + KeyEqual, + Mutex>::collect_metrics() { m_stats.size.set(getCacheSize()); @@ -683,17 +962,28 @@ template < class Key, class T, bool IsKeyCache, + class SharedWeakUnionPointer, + class SharedPointerType, class Hash, class KeyEqual, class Mutex> std::thread -TaggedCache::sweepHelper( - clock_type::time_point const& when_expire, - [[maybe_unused]] clock_type::time_point const& now, - typename KeyValueCacheType::map_type& partition, - SweptPointersVector& stuffToSweep, - std::atomic& allRemovals, - std::lock_guard const&) +TaggedCache< + Key, + T, + IsKeyCache, + SharedWeakUnionPointer, + SharedPointerType, + Hash, + KeyEqual, + Mutex>:: + sweepHelper( + clock_type::time_point const& when_expire, + [[maybe_unused]] clock_type::time_point const& now, + typename KeyValueCacheType::map_type& partition, + SweptPointersVector& stuffToSweep, + std::atomic& allRemovals, + std::lock_guard const&) { return std::thread([&, this]() { int cacheRemovals = 0; @@ -701,8 +991,7 @@ TaggedCache::sweepHelper( // Keep references to all the stuff we sweep // so that we can destroy them outside the lock. - stuffToSweep.first.reserve(partition.size()); - stuffToSweep.second.reserve(partition.size()); + stuffToSweep.reserve(partition.size()); { auto cit = partition.begin(); while (cit != partition.end()) @@ -712,8 +1001,7 @@ TaggedCache::sweepHelper( // weak if (cit->second.isExpired()) { - stuffToSweep.second.push_back( - std::move(cit->second.weak_ptr)); + stuffToSweep.emplace_back(std::move(cit->second.ptr)); ++mapRemovals; cit = partition.erase(cit); } @@ -728,15 +1016,14 @@ TaggedCache::sweepHelper( ++cacheRemovals; if (cit->second.ptr.use_count() == 1) { - stuffToSweep.first.push_back( - std::move(cit->second.ptr)); + stuffToSweep.emplace_back(std::move(cit->second.ptr)); ++mapRemovals; cit = partition.erase(cit); } else { // remains weakly cached - cit->second.ptr.reset(); + cit->second.ptr.convertToWeak(); ++cit; } } @@ -764,17 +1051,28 @@ template < class Key, class T, bool IsKeyCache, + class SharedWeakUnionPointer, + class SharedPointerType, class Hash, class KeyEqual, class Mutex> std::thread -TaggedCache::sweepHelper( - clock_type::time_point const& when_expire, - clock_type::time_point const& now, - typename KeyOnlyCacheType::map_type& partition, - SweptPointersVector&, - std::atomic& allRemovals, - std::lock_guard const&) +TaggedCache< + Key, + T, + IsKeyCache, + SharedWeakUnionPointer, + SharedPointerType, + Hash, + KeyEqual, + Mutex>:: + sweepHelper( + clock_type::time_point const& when_expire, + clock_type::time_point const& now, + typename KeyOnlyCacheType::map_type& partition, + SweptPointersVector&, + std::atomic& allRemovals, + std::lock_guard const&) { return std::thread([&, this]() { int cacheRemovals = 0; diff --git a/include/xrpl/protocol/AccountID.h b/include/xrpl/protocol/AccountID.h index ebe7f014d9c..e9722b458f9 100644 --- a/include/xrpl/protocol/AccountID.h +++ b/include/xrpl/protocol/AccountID.h @@ -22,7 +22,7 @@ #include // VFALCO Uncomment when the header issues are resolved -//#include +//#include #include #include #include diff --git a/src/test/basics/IntrusiveShared_test.cpp b/src/test/basics/IntrusiveShared_test.cpp new file mode 100644 index 00000000000..ad62b14f498 --- /dev/null +++ b/src/test/basics/IntrusiveShared_test.cpp @@ -0,0 +1,787 @@ +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ripple { +namespace tests { + +namespace { +enum class TrackedState : std::uint8_t { + uninitialized, + alive, + partiallyDeletedStarted, + partiallyDeleted, + deletedStarted, + deleted +}; + +class TIBase : public IntrusiveRefCounts +{ +public: + static constexpr std::size_t maxStates = 128; + static std::array, maxStates> state; + static std::atomic nextId; + static TrackedState + getState(int id) + { + assert(id < state.size()); + return state[id].load(std::memory_order_acquire); + } + static void + resetStates(bool resetCallback) + { + for (int i = 0; i < maxStates; ++i) + { + state[i].store( + TrackedState::uninitialized, std::memory_order_release); + } + nextId.store(0, std::memory_order_release); + if (resetCallback) + TIBase::tracingCallback_ = [](TrackedState, + std::optional) {}; + } + + struct ResetStatesGuard + { + bool resetCallback_{false}; + + ResetStatesGuard(bool resetCallback) : resetCallback_{resetCallback} + { + TIBase::resetStates(resetCallback_); + } + ~ResetStatesGuard() + { + TIBase::resetStates(resetCallback_); + } + }; + + TIBase() : id_{checkoutID()} + { + assert(state.size() > id_); + state[id_].store(TrackedState::alive, std::memory_order_relaxed); + } + ~TIBase() + { + using enum TrackedState; + + assert(state.size() > id_); + tracingCallback_( + state[id_].load(std::memory_order_relaxed), deletedStarted); + + assert(state.size() > id_); + // Use relaxed memory order to try to avoid atomic operations from + // adding additional memory synchronizations that may hide threading + // errors in the underlying shared pointer class. + state[id_].store(deletedStarted, std::memory_order_relaxed); + + tracingCallback_(deletedStarted, deleted); + + assert(state.size() > id_); + state[id_].store(TrackedState::deleted, std::memory_order_relaxed); + + tracingCallback_(TrackedState::deleted, std::nullopt); + } + + void + partialDestructor() + { + using enum TrackedState; + + assert(state.size() > id_); + tracingCallback_( + state[id_].load(std::memory_order_relaxed), + partiallyDeletedStarted); + + assert(state.size() > id_); + state[id_].store(partiallyDeletedStarted, std::memory_order_relaxed); + + tracingCallback_(partiallyDeletedStarted, partiallyDeleted); + + assert(state.size() > id_); + state[id_].store(partiallyDeleted, std::memory_order_relaxed); + + tracingCallback_(partiallyDeleted, std::nullopt); + } + + static std::function)> + tracingCallback_; + + int id_; + +private: + static int + checkoutID() + { + return nextId.fetch_add(1, std::memory_order_acq_rel); + } +}; + +std::array, TIBase::maxStates> TIBase::state; +std::atomic TIBase::nextId{0}; + +std::function)> + TIBase::tracingCallback_ = [](TrackedState, std::optional) {}; + +} // namespace + +class IntrusiveShared_test : public beast::unit_test::suite +{ +public: + void + testBasics() + { + testcase("Basics"); + + { + TIBase::ResetStatesGuard rsg{true}; + + TIBase b; + BEAST_EXPECT(b.use_count() == 1); + b.addWeakRef(); + BEAST_EXPECT(b.use_count() == 1); + auto a = b.releaseStrongRef(); + BEAST_EXPECT(a == ReleaseRefAction::partialDestroy); + BEAST_EXPECT(b.use_count() == 0); + TIBase* pb = &b; + partialDestructorFinished(&pb); + BEAST_EXPECT(!pb); + a = b.releaseWeakRef(); + BEAST_EXPECT(a == ReleaseRefAction::destroy); + } + + std::vector> strong; + std::vector> weak; + { + TIBase::ResetStatesGuard rsg{true}; + + using enum TrackedState; + auto b = make_SharedIntrusive(); + auto id = b->id_; + BEAST_EXPECT(TIBase::getState(id) == alive); + BEAST_EXPECT(b->use_count() == 1); + for (int i = 0; i < 10; ++i) + { + strong.push_back(b); + } + b.reset(); + BEAST_EXPECT(TIBase::getState(id) == alive); + strong.resize(strong.size() - 1); + BEAST_EXPECT(TIBase::getState(id) == alive); + strong.clear(); + BEAST_EXPECT(TIBase::getState(id) == deleted); + + b = make_SharedIntrusive(); + id = b->id_; + BEAST_EXPECT(TIBase::getState(id) == alive); + BEAST_EXPECT(b->use_count() == 1); + for (int i = 0; i < 10; ++i) + { + weak.push_back(b); + BEAST_EXPECT(b->use_count() == 1); + } + BEAST_EXPECT(TIBase::getState(id) == alive); + weak.resize(weak.size() - 1); + BEAST_EXPECT(TIBase::getState(id) == alive); + b.reset(); + BEAST_EXPECT(TIBase::getState(id) == partiallyDeleted); + while (!weak.empty()) + { + weak.resize(weak.size() - 1); + if (weak.size()) + BEAST_EXPECT(TIBase::getState(id) == partiallyDeleted); + } + BEAST_EXPECT(TIBase::getState(id) == deleted); + } + { + TIBase::ResetStatesGuard rsg{true}; + + using enum TrackedState; + auto b = make_SharedIntrusive(); + auto id = b->id_; + BEAST_EXPECT(TIBase::getState(id) == alive); + WeakIntrusive w{b}; + BEAST_EXPECT(TIBase::getState(id) == alive); + auto s = w.lock(); + BEAST_EXPECT(s && s->use_count() == 2); + b.reset(); + BEAST_EXPECT(TIBase::getState(id) == alive); + BEAST_EXPECT(s && s->use_count() == 1); + s.reset(); + BEAST_EXPECT(TIBase::getState(id) == partiallyDeleted); + BEAST_EXPECT(w.expired()); + s = w.lock(); + // Cannot convert a weak pointer to a strong pointer if object is + // already partially deleted + BEAST_EXPECT(!s); + w.reset(); + BEAST_EXPECT(TIBase::getState(id) == deleted); + } + { + TIBase::ResetStatesGuard rsg{true}; + + using enum TrackedState; + using swu = SharedWeakUnion; + swu b = make_SharedIntrusive(); + BEAST_EXPECT(b.isStrong() && b.use_count() == 1); + auto id = b.get()->id_; + BEAST_EXPECT(TIBase::getState(id) == alive); + swu w = b; + BEAST_EXPECT(TIBase::getState(id) == alive); + BEAST_EXPECT(w.isStrong() && b.use_count() == 2); + w.convertToWeak(); + BEAST_EXPECT(w.isWeak() && b.use_count() == 1); + swu s = w; + BEAST_EXPECT(s.isWeak() && b.use_count() == 1); + s.convertToStrong(); + BEAST_EXPECT(s.isStrong() && b.use_count() == 2); + b.reset(); + BEAST_EXPECT(TIBase::getState(id) == alive); + BEAST_EXPECT(s.use_count() == 1); + BEAST_EXPECT(!w.expired()); + s.reset(); + BEAST_EXPECT(TIBase::getState(id) == partiallyDeleted); + BEAST_EXPECT(w.expired()); + w.convertToStrong(); + // Cannot convert a weak pointer to a strong pointer if object is + // already partially deleted + BEAST_EXPECT(w.isWeak()); + w.reset(); + BEAST_EXPECT(TIBase::getState(id) == deleted); + } + } + + void + testPartialDelete() + { + testcase("Partial Delete"); + + // This test creates two threads. One with a strong pointer and one + // with a weak pointer. The strong pointer is reset while the weak + // pointer still holds a reference, triggering a partial delete. + // While the partial delete function runs (a sleep is inserted) the + // weak pointer is reset. The destructor should wait to run until + // after the partial delete function has completed running. + + using enum TrackedState; + + TIBase::ResetStatesGuard rsg{true}; + + auto strong = make_SharedIntrusive(); + WeakIntrusive weak{strong}; + bool destructorRan = false; + bool partialDeleteRan = false; + std::latch partialDeleteStartedSyncPoint{2}; + strong->tracingCallback_ = [&](TrackedState cur, + std::optional next) { + using enum TrackedState; + if (next == deletedStarted) + { + // strong goes out of scope while weak is still in scope + // This checks that partialDelete has run to completion + // before the desturctor is called. A sleep is inserted + // inside the partial delete to make sure the destructor is + // given an opportunity to run durring partial delete. + BEAST_EXPECT(cur == partiallyDeleted); + } + if (next == partiallyDeletedStarted) + { + partialDeleteStartedSyncPoint.arrive_and_wait(); + using namespace std::chrono_literals; + // Sleep and let the weak pointer go out of scope, + // potentially triggering a destructor while partial delete + // is running. The test is to make sure that doesn't happen. + std::this_thread::sleep_for(800ms); + } + if (next == partiallyDeleted) + { + BEAST_EXPECT(!partialDeleteRan && !destructorRan); + partialDeleteRan = true; + } + if (next == deleted) + { + BEAST_EXPECT(!destructorRan); + destructorRan = true; + } + }; + std::jthread t1{[&] { + partialDeleteStartedSyncPoint.arrive_and_wait(); + weak.reset(); // Trigger a full delete as soon as the partial + // delete starts + }}; + std::jthread t2{[&] { + strong.reset(); // Trigger a partial delete + }}; + t1.join(); + t2.join(); + + BEAST_EXPECT(destructorRan && partialDeleteRan); + } + + void + testDestructor() + { + testcase("Destructor"); + + // This test creates two threads. One with a strong pointer and one + // with a weak pointer. The weak pointer is reset while the strong + // pointer still holds a reference. Then the strong pointer is + // reset. Only the destructor should run. The partial destructor + // should not be called. Since the weak reset runs to completion + // before the strong pointer is reset, threading doesn't add much to + // this test, but there is no harm in keeping it. + + using enum TrackedState; + + TIBase::ResetStatesGuard rsg{true}; + + auto strong = make_SharedIntrusive(); + WeakIntrusive weak{strong}; + bool destructorRan = false; + bool partialDeleteRan = false; + std::latch weakResetSyncPoint{2}; + strong->tracingCallback_ = [&](TrackedState cur, + std::optional next) { + using enum TrackedState; + if (next == partiallyDeleted) + { + BEAST_EXPECT(!partialDeleteRan && !destructorRan); + partialDeleteRan = true; + } + if (next == deleted) + { + BEAST_EXPECT(!destructorRan); + destructorRan = true; + } + }; + std::jthread t1{[&] { + weak.reset(); + weakResetSyncPoint.arrive_and_wait(); + }}; + std::jthread t2{[&] { + weakResetSyncPoint.arrive_and_wait(); + strong.reset(); // Trigger a partial delete + }}; + t1.join(); + t2.join(); + + BEAST_EXPECT(destructorRan && !partialDeleteRan); + } + + void + testMultithreadedClearMixedVariant() + { + testcase("Multithreaded Clear Mixed Variant"); + + // This test creates and destroys many strong and weak pointers in a + // loop. There is a random mix of strong and weak pointers stored in + // a vector (held as a variant). Both threads clear all the pointers + // and check that the invariants hold. + + using enum TrackedState; + TIBase::ResetStatesGuard rsg{true}; + + std::atomic destructionState{0}; + // returns destructorRan and partialDestructorRan (in that order) + auto getDestructorState = [&]() -> std::pair { + int s = destructionState.load(std::memory_order_relaxed); + return {(s & 1) != 0, (s & 2) != 0}; + }; + auto setDestructorRan = [&]() -> void { + destructionState.fetch_or(1, std::memory_order_acq_rel); + }; + auto setPartialDeleteRan = [&]() -> void { + destructionState.fetch_or(2, std::memory_order_acq_rel); + }; + auto tracingCallback = [&](TrackedState cur, + std::optional next) { + using enum TrackedState; + auto [destructorRan, partialDeleteRan] = getDestructorState(); + if (next == partiallyDeleted) + { + BEAST_EXPECT(!partialDeleteRan && !destructorRan); + setPartialDeleteRan(); + } + if (next == deleted) + { + BEAST_EXPECT(!destructorRan); + setDestructorRan(); + } + }; + auto createVecOfPointers = [&](auto const& toClone, + std::default_random_engine& eng) + -> std::vector< + std::variant, WeakIntrusive>> { + std::vector< + std::variant, WeakIntrusive>> + result; + std::uniform_int_distribution<> toCreateDist(4, 64); + std::uniform_int_distribution<> isStrongDist(0, 1); + auto numToCreate = toCreateDist(eng); + result.reserve(numToCreate); + for (int i = 0; i < numToCreate; ++i) + { + if (isStrongDist(eng)) + { + result.push_back(SharedIntrusive(toClone)); + } + else + { + result.push_back(WeakIntrusive(toClone)); + } + } + return result; + }; + constexpr int loopIters = 2 * 1024; + constexpr int numThreads = 16; + std::vector> toClone; + std::barrier loopStartSyncPoint{numThreads}; + std::barrier postCreateToCloneSyncPoint{numThreads}; + std::barrier postCreateVecOfPointersSyncPoint{numThreads}; + auto engines = [&]() -> std::vector { + std::random_device rd; + std::vector result; + result.reserve(numThreads); + for (int i = 0; i < numThreads; ++i) + result.emplace_back(rd()); + return result; + }(); + + // cloneAndDestroy clones the strong pointer into a vector of mixed + // strong and weak pointers and destroys them all at once. + // threadId==0 is special. + auto cloneAndDestroy = [&](int threadId) { + for (int i = 0; i < loopIters; ++i) + { + // ------ Sync Point ------ + loopStartSyncPoint.arrive_and_wait(); + + // only thread 0 should reset the state + std::optional rsg; + if (threadId == 0) + { + // Thread 0 is the genesis thread. It creates the strong + // pointers to be cloned by the other threads. This + // thread will also check that the destructor ran and + // clear the temporary variables. + + rsg.emplace(false); + auto [destructorRan, partialDeleteRan] = + getDestructorState(); + BEAST_EXPECT(!i || destructorRan); + destructionState.store(0, std::memory_order_release); + + toClone.clear(); + toClone.resize(numThreads); + auto strong = make_SharedIntrusive(); + strong->tracingCallback_ = tracingCallback; + std::fill(toClone.begin(), toClone.end(), strong); + } + + // ------ Sync Point ------ + postCreateToCloneSyncPoint.arrive_and_wait(); + + auto v = + createVecOfPointers(toClone[threadId], engines[threadId]); + toClone[threadId].reset(); + + // ------ Sync Point ------ + postCreateVecOfPointersSyncPoint.arrive_and_wait(); + + v.clear(); + } + }; + std::vector threads; + for (int i = 0; i < numThreads; ++i) + { + threads.emplace_back(cloneAndDestroy, i); + } + for (int i = 0; i < numThreads; ++i) + { + threads[i].join(); + } + } + + void + testMultithreadedClearMixedUnion() + { + testcase("Multithreaded Clear Mixed Union"); + + // This test creates and destroys many SharedWeak pointers in a + // loop. All the pointers start as strong and a loop randomly + // convert them between strong and weak pointers. Both threads clear + // all the pointers and check that the invariants hold. + // + // Note: This test also differs from the test above in that the pointers + // randomly change from strong to weak and from weak to strong in a + // loop. This can't be done in the variant test above because variant is + // not thread safe while the SharedWeakUnion is thread safe. + + using enum TrackedState; + + TIBase::ResetStatesGuard rsg{true}; + + std::atomic destructionState{0}; + // returns destructorRan and partialDestructorRan (in that order) + auto getDestructorState = [&]() -> std::pair { + int s = destructionState.load(std::memory_order_relaxed); + return {(s & 1) != 0, (s & 2) != 0}; + }; + auto setDestructorRan = [&]() -> void { + destructionState.fetch_or(1, std::memory_order_acq_rel); + }; + auto setPartialDeleteRan = [&]() -> void { + destructionState.fetch_or(2, std::memory_order_acq_rel); + }; + auto tracingCallback = [&](TrackedState cur, + std::optional next) { + using enum TrackedState; + auto [destructorRan, partialDeleteRan] = getDestructorState(); + if (next == partiallyDeleted) + { + BEAST_EXPECT(!partialDeleteRan && !destructorRan); + setPartialDeleteRan(); + } + if (next == deleted) + { + BEAST_EXPECT(!destructorRan); + setDestructorRan(); + } + }; + auto createVecOfPointers = [&](auto const& toClone, + std::default_random_engine& eng) + -> std::vector> { + std::vector> result; + std::uniform_int_distribution<> toCreateDist(4, 64); + auto numToCreate = toCreateDist(eng); + result.reserve(numToCreate); + for (int i = 0; i < numToCreate; ++i) + result.push_back(SharedIntrusive(toClone)); + return result; + }; + constexpr int loopIters = 2 * 1024; + constexpr int flipPointersLoopIters = 256; + constexpr int numThreads = 16; + std::vector> toClone; + std::barrier loopStartSyncPoint{numThreads}; + std::barrier postCreateToCloneSyncPoint{numThreads}; + std::barrier postCreateVecOfPointersSyncPoint{numThreads}; + std::barrier postFlipPointersLoopSyncPoint{numThreads}; + auto engines = [&]() -> std::vector { + std::random_device rd; + std::vector result; + result.reserve(numThreads); + for (int i = 0; i < numThreads; ++i) + result.emplace_back(rd()); + return result; + }(); + + // cloneAndDestroy clones the strong pointer into a vector of + // mixed strong and weak pointers, runs a loop that randomly + // changes strong pointers to weak pointers, and destroys them + // all at once. + auto cloneAndDestroy = [&](int threadId) { + for (int i = 0; i < loopIters; ++i) + { + // ------ Sync Point ------ + loopStartSyncPoint.arrive_and_wait(); + + // only thread 0 should reset the state + std::optional rsg; + if (threadId == 0) + { + // threadId 0 is the genesis thread. It creates the + // strong point to be cloned by the other threads. This + // thread will also check that the destructor ran and + // clear the temporary variables. + rsg.emplace(false); + auto [destructorRan, partialDeleteRan] = + getDestructorState(); + BEAST_EXPECT(!i || destructorRan); + destructionState.store(0, std::memory_order_release); + + toClone.clear(); + toClone.resize(numThreads); + auto strong = make_SharedIntrusive(); + strong->tracingCallback_ = tracingCallback; + std::fill(toClone.begin(), toClone.end(), strong); + } + + // ------ Sync Point ------ + postCreateToCloneSyncPoint.arrive_and_wait(); + + auto v = + createVecOfPointers(toClone[threadId], engines[threadId]); + toClone[threadId].reset(); + + // ------ Sync Point ------ + postCreateVecOfPointersSyncPoint.arrive_and_wait(); + + std::uniform_int_distribution<> isStrongDist(0, 1); + for (int f = 0; f < flipPointersLoopIters; ++f) + { + for (auto& p : v) + { + if (isStrongDist(engines[threadId])) + { + p.convertToStrong(); + } + else + { + p.convertToWeak(); + } + } + } + + // ------ Sync Point ------ + postFlipPointersLoopSyncPoint.arrive_and_wait(); + + v.clear(); + } + }; + std::vector threads; + for (int i = 0; i < numThreads; ++i) + { + threads.emplace_back(cloneAndDestroy, i); + } + for (int i = 0; i < numThreads; ++i) + { + threads[i].join(); + } + } + + void + testMultithreadedLockingWeak() + { + testcase("Multithreaded Locking Weak"); + + // This test creates a single shared atomic pointer that multiple thread + // create weak pointers from. The threads then lock the weak pointers. + // Both threads clear all the pointers and check that the invariants + // hold. + + using enum TrackedState; + + TIBase::ResetStatesGuard rsg{true}; + + std::atomic destructionState{0}; + // returns destructorRan and partialDestructorRan (in that order) + auto getDestructorState = [&]() -> std::pair { + int s = destructionState.load(std::memory_order_relaxed); + return {(s & 1) != 0, (s & 2) != 0}; + }; + auto setDestructorRan = [&]() -> void { + destructionState.fetch_or(1, std::memory_order_acq_rel); + }; + auto setPartialDeleteRan = [&]() -> void { + destructionState.fetch_or(2, std::memory_order_acq_rel); + }; + auto tracingCallback = [&](TrackedState cur, + std::optional next) { + using enum TrackedState; + auto [destructorRan, partialDeleteRan] = getDestructorState(); + if (next == partiallyDeleted) + { + BEAST_EXPECT(!partialDeleteRan && !destructorRan); + setPartialDeleteRan(); + } + if (next == deleted) + { + BEAST_EXPECT(!destructorRan); + setDestructorRan(); + } + }; + + constexpr int loopIters = 2 * 1024; + constexpr int lockWeakLoopIters = 256; + constexpr int numThreads = 16; + std::vector> toLock; + std::barrier loopStartSyncPoint{numThreads}; + std::barrier postCreateToLockSyncPoint{numThreads}; + std::barrier postLockWeakLoopSyncPoint{numThreads}; + + // lockAndDestroy creates weak pointers from the strong pointer + // and runs a loop that locks the weak pointer. At the end of the loop + // all the pointers are destroyed all at once. + auto lockAndDestroy = [&](int threadId) { + for (int i = 0; i < loopIters; ++i) + { + // ------ Sync Point ------ + loopStartSyncPoint.arrive_and_wait(); + + // only thread 0 should reset the state + std::optional rsg; + if (threadId == 0) + { + // threadId 0 is the genesis thread. It creates the + // strong point to be locked by the other threads. This + // thread will also check that the destructor ran and + // clear the temporary variables. + rsg.emplace(false); + auto [destructorRan, partialDeleteRan] = + getDestructorState(); + BEAST_EXPECT(!i || destructorRan); + destructionState.store(0, std::memory_order_release); + + toLock.clear(); + toLock.resize(numThreads); + auto strong = make_SharedIntrusive(); + strong->tracingCallback_ = tracingCallback; + std::fill(toLock.begin(), toLock.end(), strong); + } + + // ------ Sync Point ------ + postCreateToLockSyncPoint.arrive_and_wait(); + + // Multiple threads all create a weak pointer from the same + // strong pointer + WeakIntrusive weak{toLock[threadId]}; + for (int wi = 0; wi < lockWeakLoopIters; ++wi) + { + BEAST_EXPECT(!weak.expired()); + auto strong = weak.lock(); + BEAST_EXPECT(strong); + } + + // ------ Sync Point ------ + postLockWeakLoopSyncPoint.arrive_and_wait(); + + toLock[threadId].reset(); + } + }; + std::vector threads; + for (int i = 0; i < numThreads; ++i) + { + threads.emplace_back(lockAndDestroy, i); + } + for (int i = 0; i < numThreads; ++i) + { + threads[i].join(); + } + } + + void + run() override + { + testBasics(); + testPartialDelete(); + testDestructor(); + testMultithreadedClearMixedVariant(); + testMultithreadedClearMixedUnion(); + testMultithreadedLockingWeak(); + } +}; // namespace tests + +BEAST_DEFINE_TESTSUITE(IntrusiveShared, ripple_basics, ripple); +} // namespace tests +} // namespace ripple diff --git a/src/xrpld/app/main/Application.h b/src/xrpld/app/main/Application.h index 8f2dd606ded..15857f8c5e1 100644 --- a/src/xrpld/app/main/Application.h +++ b/src/xrpld/app/main/Application.h @@ -54,6 +54,8 @@ template < class Key, class T, bool IsKeyCache, + class SharedWeakUnionPointer, + class SharedPointerType, class Hash, class KeyEqual, class Mutex> diff --git a/src/xrpld/nodestore/detail/DatabaseNodeImp.cpp b/src/xrpld/nodestore/detail/DatabaseNodeImp.cpp index 3159219964c..f14f4b7dc58 100644 --- a/src/xrpld/nodestore/detail/DatabaseNodeImp.cpp +++ b/src/xrpld/nodestore/detail/DatabaseNodeImp.cpp @@ -18,6 +18,7 @@ //============================================================================== #include +#include #include #include diff --git a/src/xrpld/shamap/SHAMap.h b/src/xrpld/shamap/SHAMap.h index a47f1c1c2bc..8b2c451bf0a 100644 --- a/src/xrpld/shamap/SHAMap.h +++ b/src/xrpld/shamap/SHAMap.h @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -104,7 +105,7 @@ class SHAMap /** The sequence of the ledger that this map references, if any. */ std::uint32_t ledgerSeq_ = 0; - std::shared_ptr root_; + intr_ptr::SharedPtr root_; mutable SHAMapState state_; SHAMapType const type_; bool backed_ = true; // Map is backed by the database @@ -366,29 +367,30 @@ class SHAMap invariants() const; private: - using SharedPtrNodeStack = - std::stack, SHAMapNodeID>>; + using SharedPtrNodeStack = std::stack< + std::pair, SHAMapNodeID>>; using DeltaRef = std::pair< boost::intrusive_ptr, boost::intrusive_ptr>; // tree node cache operations - std::shared_ptr + intr_ptr::SharedPtr cacheLookup(SHAMapHash const& hash) const; + void - canonicalize(SHAMapHash const& hash, std::shared_ptr&) + canonicalize(SHAMapHash const& hash, intr_ptr::SharedPtr&) const; // database operations - std::shared_ptr + intr_ptr::SharedPtr fetchNodeFromDB(SHAMapHash const& hash) const; - std::shared_ptr + intr_ptr::SharedPtr fetchNodeNT(SHAMapHash const& hash) const; - std::shared_ptr + intr_ptr::SharedPtr fetchNodeNT(SHAMapHash const& hash, SHAMapSyncFilter* filter) const; - std::shared_ptr + intr_ptr::SharedPtr fetchNode(SHAMapHash const& hash) const; - std::shared_ptr + intr_ptr::SharedPtr checkFilter(SHAMapHash const& hash, SHAMapSyncFilter* filter) const; /** Update hashes up to the root */ @@ -396,7 +398,7 @@ class SHAMap dirtyUp( SharedPtrNodeStack& stack, uint256 const& target, - std::shared_ptr terminal); + intr_ptr::SharedPtr terminal); /** Walk towards the specified id, returning the node. Caller must check if the return is nullptr, and if not, if the node->peekItem()->key() == @@ -410,36 +412,36 @@ class SHAMap /** Unshare the node, allowing it to be modified */ template - std::shared_ptr - unshareNode(std::shared_ptr, SHAMapNodeID const& nodeID); + intr_ptr::SharedPtr + unshareNode(intr_ptr::SharedPtr, SHAMapNodeID const& nodeID); /** prepare a node to be modified before flushing */ template - std::shared_ptr - preFlushNode(std::shared_ptr node) const; + intr_ptr::SharedPtr + preFlushNode(intr_ptr::SharedPtr node) const; /** write and canonicalize modified node */ - std::shared_ptr - writeNode(NodeObjectType t, std::shared_ptr node) const; + intr_ptr::SharedPtr + writeNode(NodeObjectType t, intr_ptr::SharedPtr node) const; // returns the first item at or below this node SHAMapLeafNode* firstBelow( - std::shared_ptr, + intr_ptr::SharedPtr, SharedPtrNodeStack& stack, int branch = 0) const; // returns the last item at or below this node SHAMapLeafNode* lastBelow( - std::shared_ptr node, + intr_ptr::SharedPtr node, SharedPtrNodeStack& stack, int branch = branchFactor) const; // helper function for firstBelow and lastBelow SHAMapLeafNode* belowHelper( - std::shared_ptr node, + intr_ptr::SharedPtr node, SharedPtrNodeStack& stack, int branch, std::tuple< @@ -453,15 +455,15 @@ class SHAMap descend(SHAMapInnerNode*, int branch) const; SHAMapTreeNode* descendThrow(SHAMapInnerNode*, int branch) const; - std::shared_ptr - descend(std::shared_ptr const&, int branch) const; - std::shared_ptr - descendThrow(std::shared_ptr const&, int branch) const; + intr_ptr::SharedPtr + descend(SHAMapInnerNode&, int branch) const; + intr_ptr::SharedPtr + descendThrow(SHAMapInnerNode&, int branch) const; // Descend with filter // If pending, callback is called as if it called fetchNodeNT - using descendCallback = - std::function, SHAMapHash const&)>; + using descendCallback = std::function< + void(intr_ptr::SharedPtr, SHAMapHash const&)>; SHAMapTreeNode* descendAsync( SHAMapInnerNode* parent, @@ -479,8 +481,8 @@ class SHAMap // Non-storing // Does not hook the returned node to its parent - std::shared_ptr - descendNoStore(std::shared_ptr const&, int branch) const; + intr_ptr::SharedPtr + descendNoStore(SHAMapInnerNode&, int branch) const; /** If there is only one leaf below this node, get its contents */ boost::intrusive_ptr const& @@ -541,10 +543,10 @@ class SHAMap // nodes we may have acquired from deferred reads using DeferredNode = std::tuple< - SHAMapInnerNode*, // parent node - SHAMapNodeID, // parent node ID - int, // branch - std::shared_ptr>; // node + SHAMapInnerNode*, // parent node + SHAMapNodeID, // parent node ID + int, // branch + intr_ptr::SharedPtr>; // node int deferred_; std::mutex deferLock_; @@ -578,7 +580,7 @@ class SHAMap gmn_ProcessDeferredReads(MissingNodes&); // fetch from DB helper function - std::shared_ptr + intr_ptr::SharedPtr finishFetch( SHAMapHash const& hash, std::shared_ptr const& object) const; diff --git a/src/xrpld/shamap/SHAMapAccountStateLeafNode.h b/src/xrpld/shamap/SHAMapAccountStateLeafNode.h index 842c1092dd9..1e6c1df261f 100644 --- a/src/xrpld/shamap/SHAMapAccountStateLeafNode.h +++ b/src/xrpld/shamap/SHAMapAccountStateLeafNode.h @@ -51,10 +51,10 @@ class SHAMapAccountStateLeafNode final { } - std::shared_ptr + intr_ptr::SharedPtr clone(std::uint32_t cowid) const final override { - return std::make_shared( + return intr_ptr::make_shared( item_, cowid, hash_); } diff --git a/src/xrpld/shamap/SHAMapInnerNode.h b/src/xrpld/shamap/SHAMapInnerNode.h index d2791915c3c..874eb7cd9b7 100644 --- a/src/xrpld/shamap/SHAMapInnerNode.h +++ b/src/xrpld/shamap/SHAMapInnerNode.h @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -49,7 +50,7 @@ class SHAMapInnerNode final : public SHAMapTreeNode, private: /** Opaque type that contains the `hashes` array (array of type `SHAMapHash`) and the `children` array (array of type - `std::shared_ptr`). + `intr_ptr::SharedPtr`). */ TaggedPointer hashesAndChildren_; @@ -114,7 +115,11 @@ class SHAMapInnerNode final : public SHAMapTreeNode, operator=(SHAMapInnerNode const&) = delete; ~SHAMapInnerNode(); - std::shared_ptr + // Needed to support intrusive weak pointers + void + partialDestructor() override; + + intr_ptr::SharedPtr clone(std::uint32_t cowid) const override; SHAMapNodeType @@ -148,19 +153,20 @@ class SHAMapInnerNode final : public SHAMapTreeNode, getChildHash(int m) const; void - setChild(int m, std::shared_ptr child); + setChild(int m, intr_ptr::SharedPtr child); - void - shareChild(int m, std::shared_ptr const& child); + template + requires std::derived_from void + shareChild(int m, SharedIntrusive const& child); SHAMapTreeNode* getChildPointer(int branch); - std::shared_ptr + intr_ptr::SharedPtr getChild(int branch); - std::shared_ptr - canonicalizeChild(int branch, std::shared_ptr node); + intr_ptr::SharedPtr + canonicalizeChild(int branch, intr_ptr::SharedPtr node); // sync functions bool @@ -188,10 +194,10 @@ class SHAMapInnerNode final : public SHAMapTreeNode, void invariants(bool is_root = false) const override; - static std::shared_ptr + static intr_ptr::SharedPtr makeFullInner(Slice data, SHAMapHash const& hash, bool hashValid); - static std::shared_ptr + static intr_ptr::SharedPtr makeCompressedInner(Slice data); }; diff --git a/src/xrpld/shamap/SHAMapTreeNode.h b/src/xrpld/shamap/SHAMapTreeNode.h index d6b0ebce9e1..09d8d43c34a 100644 --- a/src/xrpld/shamap/SHAMapTreeNode.h +++ b/src/xrpld/shamap/SHAMapTreeNode.h @@ -23,6 +23,8 @@ #include #include #include +#include +#include #include #include #include @@ -50,7 +52,7 @@ enum class SHAMapNodeType { tnACCOUNT_STATE = 4 }; -class SHAMapTreeNode +class SHAMapTreeNode : public IntrusiveRefCounts { protected: SHAMapHash hash_; @@ -89,15 +91,19 @@ class SHAMapTreeNode public: virtual ~SHAMapTreeNode() noexcept = default; + // Needed to support weak intrusive pointers + virtual void + partialDestructor(){}; + /** \defgroup SHAMap Copy-on-Write Support - By nature, a node may appear in multiple SHAMap instances. Rather than - actually duplicating these nodes, SHAMap opts to be memory efficient - and uses copy-on-write semantics for nodes. + By nature, a node may appear in multiple SHAMap instances. Rather + than actually duplicating these nodes, SHAMap opts to be memory + efficient and uses copy-on-write semantics for nodes. - Only nodes that are not modified and don't need to be flushed back can - be shared. Once a node needs to be changed, it must first be copied and - the copy must marked as not shareable. + Only nodes that are not modified and don't need to be flushed back + can be shared. Once a node needs to be changed, it must first be + copied and the copy must marked as not shareable. Note that just because a node may not be *owned* by a given SHAMap instance does not mean that the node is NOT a part of any SHAMap. It @@ -109,8 +115,8 @@ class SHAMapTreeNode /** @{ */ /** Returns the SHAMap that owns this node. - @return the ID of the SHAMap that owns this node, or 0 if the node - is not owned by any SHAMap and is a candidate for sharing. + @return the ID of the SHAMap that owns this node, or 0 if the + node is not owned by any SHAMap and is a candidate for sharing. */ std::uint32_t cowid() const @@ -130,7 +136,7 @@ class SHAMapTreeNode } /** Make a copy of this node, setting the owner. */ - virtual std::shared_ptr + virtual intr_ptr::SharedPtr clone(std::uint32_t cowid) const = 0; /** @} */ @@ -171,20 +177,20 @@ class SHAMapTreeNode virtual void invariants(bool is_root = false) const = 0; - static std::shared_ptr + static intr_ptr::SharedPtr makeFromPrefix(Slice rawNode, SHAMapHash const& hash); - static std::shared_ptr + static intr_ptr::SharedPtr makeFromWire(Slice rawNode); private: - static std::shared_ptr + static intr_ptr::SharedPtr makeTransaction(Slice data, SHAMapHash const& hash, bool hashValid); - static std::shared_ptr + static intr_ptr::SharedPtr makeAccountState(Slice data, SHAMapHash const& hash, bool hashValid); - static std::shared_ptr + static intr_ptr::SharedPtr makeTransactionWithMeta(Slice data, SHAMapHash const& hash, bool hashValid); }; diff --git a/src/xrpld/shamap/SHAMapTxLeafNode.h b/src/xrpld/shamap/SHAMapTxLeafNode.h index f4d3f21e908..b3d6c0704de 100644 --- a/src/xrpld/shamap/SHAMapTxLeafNode.h +++ b/src/xrpld/shamap/SHAMapTxLeafNode.h @@ -50,10 +50,10 @@ class SHAMapTxLeafNode final : public SHAMapLeafNode, { } - std::shared_ptr + intr_ptr::SharedPtr clone(std::uint32_t cowid) const final override { - return std::make_shared(item_, cowid, hash_); + return intr_ptr::make_shared(item_, cowid, hash_); } SHAMapNodeType diff --git a/src/xrpld/shamap/SHAMapTxPlusMetaLeafNode.h b/src/xrpld/shamap/SHAMapTxPlusMetaLeafNode.h index 6ea55f4ac46..633dc49522b 100644 --- a/src/xrpld/shamap/SHAMapTxPlusMetaLeafNode.h +++ b/src/xrpld/shamap/SHAMapTxPlusMetaLeafNode.h @@ -51,10 +51,11 @@ class SHAMapTxPlusMetaLeafNode final { } - std::shared_ptr + intr_ptr::SharedPtr clone(std::uint32_t cowid) const override { - return std::make_shared(item_, cowid, hash_); + return intr_ptr::make_shared( + item_, cowid, hash_); } SHAMapNodeType diff --git a/src/xrpld/shamap/TreeNodeCache.h b/src/xrpld/shamap/TreeNodeCache.h index f59fdc92801..f182424b0c7 100644 --- a/src/xrpld/shamap/TreeNodeCache.h +++ b/src/xrpld/shamap/TreeNodeCache.h @@ -21,10 +21,17 @@ #define RIPPLE_SHAMAP_TREENODECACHE_H_INCLUDED #include +#include +#include namespace ripple { -using TreeNodeCache = TaggedCache; +using TreeNodeCache = TaggedCache< + uint256, + SHAMapTreeNode, + /*IsKeyCache*/ false, + SharedWeakUnion, + SharedIntrusive>; } // namespace ripple diff --git a/src/xrpld/shamap/detail/NodeFamily.cpp b/src/xrpld/shamap/detail/NodeFamily.cpp index bf95003aef8..5ad59ecabc7 100644 --- a/src/xrpld/shamap/detail/NodeFamily.cpp +++ b/src/xrpld/shamap/detail/NodeFamily.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include namespace ripple { diff --git a/src/xrpld/shamap/detail/SHAMap.cpp b/src/xrpld/shamap/detail/SHAMap.cpp index d06ba2a153a..bb003635d2f 100644 --- a/src/xrpld/shamap/detail/SHAMap.cpp +++ b/src/xrpld/shamap/detail/SHAMap.cpp @@ -23,25 +23,26 @@ #include #include #include +#include #include namespace ripple { -[[nodiscard]] std::shared_ptr +[[nodiscard]] intr_ptr::SharedPtr makeTypedLeaf( SHAMapNodeType type, boost::intrusive_ptr item, std::uint32_t owner) { if (type == SHAMapNodeType::tnTRANSACTION_NM) - return std::make_shared(std::move(item), owner); + return intr_ptr::make_shared(std::move(item), owner); if (type == SHAMapNodeType::tnTRANSACTION_MD) - return std::make_shared( + return intr_ptr::make_shared( std::move(item), owner); if (type == SHAMapNodeType::tnACCOUNT_STATE) - return std::make_shared( + return intr_ptr::make_shared( std::move(item), owner); LogicError( @@ -53,7 +54,7 @@ makeTypedLeaf( SHAMap::SHAMap(SHAMapType t, Family& f) : f_(f), journal_(f.journal()), state_(SHAMapState::Modifying), type_(t) { - root_ = std::make_shared(cowid_); + root_ = intr_ptr::make_shared(cowid_); } // The `hash` parameter is unused. It is part of the interface so it's clear @@ -63,7 +64,7 @@ SHAMap::SHAMap(SHAMapType t, Family& f) SHAMap::SHAMap(SHAMapType t, uint256 const& hash, Family& f) : f_(f), journal_(f.journal()), state_(SHAMapState::Synching), type_(t) { - root_ = std::make_shared(cowid_); + root_ = intr_ptr::make_shared(cowid_); } SHAMap::SHAMap(SHAMap const& other, bool isMutable) @@ -94,7 +95,7 @@ void SHAMap::dirtyUp( SharedPtrNodeStack& stack, uint256 const& target, - std::shared_ptr child) + intr_ptr::SharedPtr child) { // walk the tree up from through the inner nodes to the root_ // update hashes and links @@ -109,10 +110,10 @@ SHAMap::dirtyUp( while (!stack.empty()) { auto node = - std::dynamic_pointer_cast(stack.top().first); + intr_ptr::dynamic_pointer_cast(stack.top().first); SHAMapNodeID nodeID = stack.top().second; stack.pop(); - assert(node != nullptr); + assert(node); int branch = selectBranch(nodeID, target); assert(branch >= 0); @@ -136,12 +137,13 @@ SHAMap::walkTowardsKey(uint256 const& id, SharedPtrNodeStack* stack) const if (stack != nullptr) stack->push({inNode, nodeID}); - auto const inner = std::static_pointer_cast(inNode); + auto const inner = + intr_ptr::static_pointer_cast(inNode); auto const branch = selectBranch(nodeID, id); if (inner->isEmptyBranch(branch)) return nullptr; - inNode = descendThrow(inner, branch); + inNode = descendThrow(*inner, branch); nodeID = nodeID.getChildNodeID(branch); } @@ -159,7 +161,7 @@ SHAMap::findKey(uint256 const& id) const return leaf; } -std::shared_ptr +intr_ptr::SharedPtr SHAMap::fetchNodeFromDB(SHAMapHash const& hash) const { assert(backed_); @@ -167,7 +169,7 @@ SHAMap::fetchNodeFromDB(SHAMapHash const& hash) const return finishFetch(hash, obj); } -std::shared_ptr +intr_ptr::SharedPtr SHAMap::finishFetch( SHAMapHash const& hash, std::shared_ptr const& object) const @@ -206,7 +208,7 @@ SHAMap::finishFetch( } // See if a sync filter has a node -std::shared_ptr +intr_ptr::SharedPtr SHAMap::checkFilter(SHAMapHash const& hash, SHAMapSyncFilter* filter) const { if (auto nodeData = filter->getNode(hash)) @@ -239,7 +241,7 @@ SHAMap::checkFilter(SHAMapHash const& hash, SHAMapSyncFilter* filter) const // Get a node without throwing // Used on maps where missing nodes are expected -std::shared_ptr +intr_ptr::SharedPtr SHAMap::fetchNodeNT(SHAMapHash const& hash, SHAMapSyncFilter* filter) const { auto node = cacheLookup(hash); @@ -262,7 +264,7 @@ SHAMap::fetchNodeNT(SHAMapHash const& hash, SHAMapSyncFilter* filter) const return node; } -std::shared_ptr +intr_ptr::SharedPtr SHAMap::fetchNodeNT(SHAMapHash const& hash) const { auto node = cacheLookup(hash); @@ -274,7 +276,7 @@ SHAMap::fetchNodeNT(SHAMapHash const& hash) const } // Throw if the node is missing -std::shared_ptr +intr_ptr::SharedPtr SHAMap::fetchNode(SHAMapHash const& hash) const { auto node = fetchNodeNT(hash); @@ -296,14 +298,13 @@ SHAMap::descendThrow(SHAMapInnerNode* parent, int branch) const return ret; } -std::shared_ptr -SHAMap::descendThrow(std::shared_ptr const& parent, int branch) - const +intr_ptr::SharedPtr +SHAMap::descendThrow(SHAMapInnerNode& parent, int branch) const { - std::shared_ptr ret = descend(parent, branch); + intr_ptr::SharedPtr ret = descend(parent, branch); - if (!ret && !parent->isEmptyBranch(branch)) - Throw(type_, parent->getChildHash(branch)); + if (!ret && !parent.isEmptyBranch(branch)) + Throw(type_, parent.getChildHash(branch)); return ret; } @@ -315,7 +316,7 @@ SHAMap::descend(SHAMapInnerNode* parent, int branch) const if (ret || !backed_) return ret; - std::shared_ptr node = + intr_ptr::SharedPtr node = fetchNodeNT(parent->getChildHash(branch)); if (!node) return nullptr; @@ -324,32 +325,29 @@ SHAMap::descend(SHAMapInnerNode* parent, int branch) const return node.get(); } -std::shared_ptr -SHAMap::descend(std::shared_ptr const& parent, int branch) - const +intr_ptr::SharedPtr +SHAMap::descend(SHAMapInnerNode& parent, int branch) const { - std::shared_ptr node = parent->getChild(branch); + intr_ptr::SharedPtr node = parent.getChild(branch); if (node || !backed_) return node; - node = fetchNode(parent->getChildHash(branch)); + node = fetchNode(parent.getChildHash(branch)); if (!node) - return nullptr; + return {}; - node = parent->canonicalizeChild(branch, std::move(node)); + node = parent.canonicalizeChild(branch, std::move(node)); return node; } // Gets the node that would be hooked to this branch, // but doesn't hook it up. -std::shared_ptr -SHAMap::descendNoStore( - std::shared_ptr const& parent, - int branch) const +intr_ptr::SharedPtr +SHAMap::descendNoStore(SHAMapInnerNode& parent, int branch) const { - std::shared_ptr ret = parent->getChild(branch); + intr_ptr::SharedPtr ret = parent.getChild(branch); if (!ret && backed_) - ret = fetchNode(parent->getChildHash(branch)); + ret = fetchNode(parent.getChildHash(branch)); return ret; } @@ -369,7 +367,7 @@ SHAMap::descend( if (!child) { auto const& childHash = parent->getChildHash(branch); - std::shared_ptr childNode = + intr_ptr::SharedPtr childNode = fetchNodeNT(childHash, filter); if (childNode) @@ -426,8 +424,8 @@ SHAMap::descendAsync( } template -std::shared_ptr -SHAMap::unshareNode(std::shared_ptr node, SHAMapNodeID const& nodeID) +intr_ptr::SharedPtr +SHAMap::unshareNode(intr_ptr::SharedPtr node, SHAMapNodeID const& nodeID) { // make sure the node is suitable for the intended operation (copy on write) assert(node->cowid() <= cowid_); @@ -435,7 +433,7 @@ SHAMap::unshareNode(std::shared_ptr node, SHAMapNodeID const& nodeID) { // have a CoW assert(state_ != SHAMapState::Immutable); - node = std::static_pointer_cast(node->clone(cowid_)); + node = intr_ptr::static_pointer_cast(node->clone(cowid_)); if (nodeID.isRoot()) root_ = node; } @@ -444,7 +442,7 @@ SHAMap::unshareNode(std::shared_ptr node, SHAMapNodeID const& nodeID) SHAMapLeafNode* SHAMap::belowHelper( - std::shared_ptr node, + intr_ptr::SharedPtr node, SharedPtrNodeStack& stack, int branch, std::tuple, std::function> const& @@ -453,11 +451,11 @@ SHAMap::belowHelper( auto& [init, cmp, incr] = loopParams; if (node->isLeaf()) { - auto n = std::static_pointer_cast(node); + auto n = intr_ptr::static_pointer_cast(node); stack.push({node, {leafDepth, n->peekItem()->key()}}); return n.get(); } - auto inner = std::static_pointer_cast(node); + auto inner = intr_ptr::static_pointer_cast(node); if (stack.empty()) stack.push({inner, SHAMapNodeID{}}); else @@ -466,15 +464,15 @@ SHAMap::belowHelper( { if (!inner->isEmptyBranch(i)) { - node = descendThrow(inner, i); + node = descendThrow(*inner, i); assert(!stack.empty()); if (node->isLeaf()) { - auto n = std::static_pointer_cast(node); + auto n = intr_ptr::static_pointer_cast(node); stack.push({n, {leafDepth, n->peekItem()->key()}}); return n.get(); } - inner = std::static_pointer_cast(node); + inner = intr_ptr::static_pointer_cast(node); stack.push({inner, stack.top().second.getChildNodeID(branch)}); i = init; // descend and reset loop } @@ -485,7 +483,7 @@ SHAMap::belowHelper( } SHAMapLeafNode* SHAMap::lastBelow( - std::shared_ptr node, + intr_ptr::SharedPtr node, SharedPtrNodeStack& stack, int branch) const { @@ -497,7 +495,7 @@ SHAMap::lastBelow( } SHAMapLeafNode* SHAMap::firstBelow( - std::shared_ptr node, + intr_ptr::SharedPtr node, SharedPtrNodeStack& stack, int branch) const { @@ -569,12 +567,12 @@ SHAMap::peekNextItem(uint256 const& id, SharedPtrNodeStack& stack) const { auto [node, nodeID] = stack.top(); assert(!node->isLeaf()); - auto inner = std::static_pointer_cast(node); + auto inner = intr_ptr::static_pointer_cast(node); for (auto i = selectBranch(nodeID, id) + 1; i < branchFactor; ++i) { if (!inner->isEmptyBranch(i)) { - node = descendThrow(inner, i); + node = descendThrow(*inner, i); auto leaf = firstBelow(node, stack, i); if (!leaf) Throw(type_, id); @@ -628,14 +626,14 @@ SHAMap::upper_bound(uint256 const& id) const } else { - auto inner = std::static_pointer_cast(node); + auto inner = intr_ptr::static_pointer_cast(node); for (auto branch = selectBranch(nodeID, id) + 1; branch < branchFactor; ++branch) { if (!inner->isEmptyBranch(branch)) { - node = descendThrow(inner, branch); + node = descendThrow(*inner, branch); auto leaf = firstBelow(node, stack, branch); if (!leaf) Throw(type_, id); @@ -665,13 +663,13 @@ SHAMap::lower_bound(uint256 const& id) const } else { - auto inner = std::static_pointer_cast(node); + auto inner = intr_ptr::static_pointer_cast(node); for (int branch = selectBranch(nodeID, id) - 1; branch >= 0; --branch) { if (!inner->isEmptyBranch(branch)) { - node = descendThrow(inner, branch); + node = descendThrow(*inner, branch); auto leaf = lastBelow(node, stack, branch); if (!leaf) Throw(type_, id); @@ -704,7 +702,8 @@ SHAMap::delItem(uint256 const& id) if (stack.empty()) Throw(type_, id); - auto leaf = std::dynamic_pointer_cast(stack.top().first); + auto leaf = + intr_ptr::dynamic_pointer_cast(stack.top().first); stack.pop(); if (!leaf || (leaf->peekItem()->key() != id)) @@ -714,12 +713,12 @@ SHAMap::delItem(uint256 const& id) // What gets attached to the end of the chain // (For now, nothing, since we deleted the leaf) - std::shared_ptr prevNode; + intr_ptr::SharedPtr prevNode; while (!stack.empty()) { auto node = - std::static_pointer_cast(stack.top().first); + intr_ptr::static_pointer_cast(stack.top().first); SHAMapNodeID nodeID = stack.top().second; stack.pop(); @@ -747,7 +746,8 @@ SHAMap::delItem(uint256 const& id) { if (!node->isEmptyBranch(i)) { - node->setChild(i, nullptr); + node->setChild( + i, intr_ptr::SharedPtr{}); break; } } @@ -792,7 +792,7 @@ SHAMap::addGiveItem( if (node->isLeaf()) { - auto leaf = std::static_pointer_cast(node); + auto leaf = intr_ptr::static_pointer_cast(node); if (leaf->peekItem()->key() == tag) return false; } @@ -800,7 +800,7 @@ SHAMap::addGiveItem( if (node->isInner()) { // easy case, we end on an inner node - auto inner = std::static_pointer_cast(node); + auto inner = intr_ptr::static_pointer_cast(node); int branch = selectBranch(nodeID, tag); assert(inner->isEmptyBranch(branch)); inner->setChild(branch, makeTypedLeaf(type, std::move(item), cowid_)); @@ -809,11 +809,11 @@ SHAMap::addGiveItem( { // this is a leaf node that has to be made an inner node holding two // items - auto leaf = std::static_pointer_cast(node); + auto leaf = intr_ptr::static_pointer_cast(node); auto otherItem = leaf->peekItem(); assert(otherItem && (tag != otherItem->key())); - node = std::make_shared(node->cowid()); + node = intr_ptr::make_shared(node->cowid()); unsigned int b1, b2; @@ -825,7 +825,7 @@ SHAMap::addGiveItem( // we need a new inner node, since both go on same branch at this // level nodeID = nodeID.getChildNodeID(b1); - node = std::make_shared(cowid_); + node = intr_ptr::make_shared(cowid_); } // we can add the two leaf nodes here @@ -876,7 +876,8 @@ SHAMap::updateGiveItem( if (stack.empty()) Throw(type_, tag); - auto node = std::dynamic_pointer_cast(stack.top().first); + auto node = + intr_ptr::dynamic_pointer_cast(stack.top().first); auto nodeID = stack.top().second; stack.pop(); @@ -946,8 +947,9 @@ SHAMap::fetchRoot(SHAMapHash const& hash, SHAMapSyncFilter* filter) @note The node must have already been unshared by having the caller first call SHAMapTreeNode::unshare(). */ -std::shared_ptr -SHAMap::writeNode(NodeObjectType t, std::shared_ptr node) const +intr_ptr::SharedPtr +SHAMap::writeNode(NodeObjectType t, intr_ptr::SharedPtr node) + const { assert(node->cowid() == 0); assert(backed_); @@ -965,8 +967,8 @@ SHAMap::writeNode(NodeObjectType t, std::shared_ptr node) const // pointer to because flushing modifies inner nodes -- it // makes them point to canonical/shared nodes. template -std::shared_ptr -SHAMap::preFlushNode(std::shared_ptr node) const +intr_ptr::SharedPtr +SHAMap::preFlushNode(intr_ptr::SharedPtr node) const { // A shared node should never need to be flushed // because that would imply someone modified it @@ -976,7 +978,7 @@ SHAMap::preFlushNode(std::shared_ptr node) const { // Node is not uniquely ours, so unshare it before // possibly modifying it - node = std::static_pointer_cast(node->clone(cowid_)); + node = intr_ptr::static_pointer_cast(node->clone(cowid_)); } return node; } @@ -1017,17 +1019,17 @@ SHAMap::walkSubTree(bool doWrite, NodeObjectType t) return 1; } - auto node = std::static_pointer_cast(root_); + auto node = intr_ptr::static_pointer_cast(root_); if (node->isEmpty()) { // replace empty root with a new empty root - root_ = std::make_shared(0); + root_ = intr_ptr::make_shared(0); return 1; } // Stack of {parent,index,child} pointers representing // inner nodes we are in the process of flushing - using StackEntry = std::pair, int>; + using StackEntry = std::pair, int>; std::stack> stack; node = preFlushNode(std::move(node)); @@ -1064,7 +1066,7 @@ SHAMap::walkSubTree(bool doWrite, NodeObjectType t) // The semantics of this changes when we move to c++-20 // Right now no move will occur; With c++-20 child will // be moved from. - node = std::static_pointer_cast( + node = intr_ptr::static_pointer_cast( std::move(child)); pos = 0; } @@ -1093,7 +1095,7 @@ SHAMap::walkSubTree(bool doWrite, NodeObjectType t) node->unshare(); if (doWrite) - node = std::static_pointer_cast( + node = intr_ptr::static_pointer_cast( writeNode(t, std::move(node))); ++flushed; @@ -1163,7 +1165,7 @@ SHAMap::dump(bool hash) const JLOG(journal_.info()) << leafCount << " resident leaves"; } -std::shared_ptr +intr_ptr::SharedPtr SHAMap::cacheLookup(SHAMapHash const& hash) const { auto ret = f_.getTreeNodeCache()->fetch(hash.as_uint256()); @@ -1174,7 +1176,7 @@ SHAMap::cacheLookup(SHAMapHash const& hash) const void SHAMap::canonicalize( SHAMapHash const& hash, - std::shared_ptr& node) const + intr_ptr::SharedPtr& node) const { assert(backed_); assert(node->cowid() == 0); @@ -1188,7 +1190,7 @@ SHAMap::invariants() const { (void)getHash(); // update node hashes auto node = root_.get(); - assert(node != nullptr); + assert(node); assert(!node->isLeaf()); SharedPtrNodeStack stack; for (auto leaf = peekFirstItem(stack); leaf != nullptr; diff --git a/src/xrpld/shamap/detail/SHAMapDelta.cpp b/src/xrpld/shamap/detail/SHAMapDelta.cpp index 0dcb861a63f..0582499630a 100644 --- a/src/xrpld/shamap/detail/SHAMapDelta.cpp +++ b/src/xrpld/shamap/detail/SHAMapDelta.cpp @@ -18,6 +18,7 @@ //============================================================================== #include +#include #include #include @@ -239,28 +240,28 @@ SHAMap::walkMap(std::vector& missingNodes, int maxMissing) if (!root_->isInner()) // root_ is only node, and we have it return; - using StackEntry = std::shared_ptr; + using StackEntry = intr_ptr::SharedPtr; std::stack> nodeStack; - nodeStack.push(std::static_pointer_cast(root_)); + nodeStack.push(intr_ptr::static_pointer_cast(root_)); while (!nodeStack.empty()) { - std::shared_ptr node = std::move(nodeStack.top()); + intr_ptr::SharedPtr node = std::move(nodeStack.top()); nodeStack.pop(); for (int i = 0; i < 16; ++i) { if (!node->isEmptyBranch(i)) { - std::shared_ptr nextNode = - descendNoStore(node, i); + intr_ptr::SharedPtr nextNode = + descendNoStore(*node, i); if (nextNode) { if (nextNode->isInner()) nodeStack.push( - std::static_pointer_cast( + intr_ptr::static_pointer_cast( nextNode)); } else @@ -282,15 +283,15 @@ SHAMap::walkMapParallel( if (!root_->isInner()) // root_ is only node, and we have it return false; - using StackEntry = std::shared_ptr; - std::array, 16> topChildren; + using StackEntry = intr_ptr::SharedPtr; + std::array, 16> topChildren; { auto const& innerRoot = - std::static_pointer_cast(root_); + intr_ptr::static_pointer_cast(root_); for (int i = 0; i < 16; ++i) { if (!innerRoot->isEmptyBranch(i)) - topChildren[i] = descendNoStore(innerRoot, i); + topChildren[i] = descendNoStore(*innerRoot, i); } } std::vector workers; @@ -311,7 +312,7 @@ SHAMap::walkMapParallel( continue; nodeStacks[rootChildIndex].push( - std::static_pointer_cast(child)); + intr_ptr::static_pointer_cast(child)); JLOG(journal_.debug()) << "starting worker " << rootChildIndex; workers.push_back(std::thread( @@ -321,7 +322,7 @@ SHAMap::walkMapParallel( { while (!nodeStack.empty()) { - std::shared_ptr node = + intr_ptr::SharedPtr node = std::move(nodeStack.top()); assert(node); nodeStack.pop(); @@ -330,14 +331,15 @@ SHAMap::walkMapParallel( { if (node->isEmptyBranch(i)) continue; - std::shared_ptr nextNode = - descendNoStore(node, i); + intr_ptr::SharedPtr nextNode = + descendNoStore(*node, i); if (nextNode) { if (nextNode->isInner()) - nodeStack.push(std::static_pointer_cast< - SHAMapInnerNode>(nextNode)); + nodeStack.push( + intr_ptr::static_pointer_cast< + SHAMapInnerNode>(nextNode)); } else { diff --git a/src/xrpld/shamap/detail/SHAMapInnerNode.cpp b/src/xrpld/shamap/detail/SHAMapInnerNode.cpp index 99155a6401f..e5c81a29680 100644 --- a/src/xrpld/shamap/detail/SHAMapInnerNode.cpp +++ b/src/xrpld/shamap/detail/SHAMapInnerNode.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -44,6 +45,17 @@ SHAMapInnerNode::SHAMapInnerNode( SHAMapInnerNode::~SHAMapInnerNode() = default; +void +SHAMapInnerNode::partialDestructor() +{ + intr_ptr::SharedPtr* children; + // structured bindings can't be captured in c++ 17; use tie instead + std::tie(std::ignore, std::ignore, children) = + hashesAndChildren_.getHashesAndChildren(); + iterNonEmptyChildIndexes( + [&](auto branchNum, auto indexNum) { children[indexNum].reset(); }); +} + template void SHAMapInnerNode::iterChildren(F&& f) const @@ -71,17 +83,17 @@ SHAMapInnerNode::getChildIndex(int i) const return hashesAndChildren_.getChildIndex(isBranch_, i); } -std::shared_ptr +intr_ptr::SharedPtr SHAMapInnerNode::clone(std::uint32_t cowid) const { auto const branchCount = getBranchCount(); auto const thisIsSparse = !hashesAndChildren_.isDense(); - auto p = std::make_shared(cowid, branchCount); + auto p = intr_ptr::make_shared(cowid, branchCount); p->hash_ = hash_; p->isBranch_ = isBranch_; p->fullBelowGen_ = fullBelowGen_; SHAMapHash *cloneHashes, *thisHashes; - std::shared_ptr*cloneChildren, *thisChildren; + intr_ptr::SharedPtr*cloneChildren, *thisChildren; // structured bindings can't be captured in c++ 17; use tie instead std::tie(std::ignore, cloneHashes, cloneChildren) = p->hashesAndChildren_.getHashesAndChildren(); @@ -122,7 +134,7 @@ SHAMapInnerNode::clone(std::uint32_t cowid) const return p; } -std::shared_ptr +intr_ptr::SharedPtr SHAMapInnerNode::makeFullInner( Slice data, SHAMapHash const& hash, @@ -132,7 +144,7 @@ SHAMapInnerNode::makeFullInner( if (data.size() != branchFactor * uint256::bytes) Throw("Invalid FI node"); - auto ret = std::make_shared(0, branchFactor); + auto ret = intr_ptr::make_shared(0, branchFactor); SerialIter si(data); @@ -156,7 +168,7 @@ SHAMapInnerNode::makeFullInner( return ret; } -std::shared_ptr +intr_ptr::SharedPtr SHAMapInnerNode::makeCompressedInner(Slice data) { // A compressed inner node is serialized as a series of 33 byte chunks, @@ -169,7 +181,7 @@ SHAMapInnerNode::makeCompressedInner(Slice data) SerialIter si(data); - auto ret = std::make_shared(0, branchFactor); + auto ret = intr_ptr::make_shared(0, branchFactor); auto hashes = ret->hashesAndChildren_.getHashes(); @@ -211,13 +223,13 @@ void SHAMapInnerNode::updateHashDeep() { SHAMapHash* hashes; - std::shared_ptr* children; + intr_ptr::SharedPtr* children; // structured bindings can't be captured in c++ 17; use tie instead std::tie(std::ignore, hashes, children) = hashesAndChildren_.getHashesAndChildren(); iterNonEmptyChildIndexes([&](auto branchNum, auto indexNum) { - if (children[indexNum] != nullptr) - hashes[indexNum] = children[indexNum]->getHash(); + if (auto p = children[indexNum].get()) + hashes[indexNum] = p->getHash(); }); updateHash(); } @@ -272,7 +284,7 @@ SHAMapInnerNode::getString(const SHAMapNodeID& id) const // We are modifying an inner node void -SHAMapInnerNode::setChild(int m, std::shared_ptr child) +SHAMapInnerNode::setChild(int m, intr_ptr::SharedPtr child) { assert((m >= 0) && (m < branchFactor)); assert(cowid_ != 0); @@ -307,8 +319,9 @@ SHAMapInnerNode::setChild(int m, std::shared_ptr child) } // finished modifying, now make shareable -void -SHAMapInnerNode::shareChild(int m, std::shared_ptr const& child) +template +requires std::derived_from void +SHAMapInnerNode::shareChild(int m, SharedIntrusive const& child) { assert((m >= 0) && (m < branchFactor)); assert(cowid_ != 0); @@ -332,7 +345,7 @@ SHAMapInnerNode::getChildPointer(int branch) return hashesAndChildren_.getChildren()[index].get(); } -std::shared_ptr +intr_ptr::SharedPtr SHAMapInnerNode::getChild(int branch) { assert(branch >= 0 && branch < branchFactor); @@ -355,10 +368,10 @@ SHAMapInnerNode::getChildHash(int m) const return zeroSHAMapHash; } -std::shared_ptr +intr_ptr::SharedPtr SHAMapInnerNode::canonicalizeChild( int branch, - std::shared_ptr node) + intr_ptr::SharedPtr node) { assert(branch >= 0 && branch < branchFactor); assert(node); @@ -396,8 +409,8 @@ SHAMapInnerNode::invariants(bool is_root) const for (int i = 0; i < branchCount; ++i) { assert(hashes[i].isNonZero()); - if (children[i] != nullptr) - children[i]->invariants(); + if (auto p = children[i].get()) + p->invariants(); ++count; } } @@ -408,8 +421,8 @@ SHAMapInnerNode::invariants(bool is_root) const if (hashes[i].isNonZero()) { assert((isBranch_ & (1 << i)) != 0); - if (children[i] != nullptr) - children[i]->invariants(); + if (auto p = children[i].get()) + p->invariants(); ++count; } else @@ -427,4 +440,14 @@ SHAMapInnerNode::invariants(bool is_root) const assert((count == 0) ? hash_.isZero() : hash_.isNonZero()); } +template void +ripple::SHAMapInnerNode::shareChild( + int, + ripple::SharedIntrusive const&); + +template void +ripple::SHAMapInnerNode::shareChild( + int, + ripple::SharedIntrusive const&); + } // namespace ripple diff --git a/src/xrpld/shamap/detail/SHAMapLeafNode.cpp b/src/xrpld/shamap/detail/SHAMapLeafNode.cpp index 972919a9bda..9de4da57b75 100644 --- a/src/xrpld/shamap/detail/SHAMapLeafNode.cpp +++ b/src/xrpld/shamap/detail/SHAMapLeafNode.cpp @@ -18,6 +18,7 @@ //============================================================================== #include +#include #include #include diff --git a/src/xrpld/shamap/detail/SHAMapSync.cpp b/src/xrpld/shamap/detail/SHAMapSync.cpp index 7235e526560..66e1a4ce796 100644 --- a/src/xrpld/shamap/detail/SHAMapSync.cpp +++ b/src/xrpld/shamap/detail/SHAMapSync.cpp @@ -19,6 +19,7 @@ #include #include +#include #include namespace ripple { @@ -46,10 +47,10 @@ SHAMap::visitNodes(std::function const& function) const if (!root_->isInner()) return; - using StackEntry = std::pair>; + using StackEntry = std::pair>; std::stack> stack; - auto node = std::static_pointer_cast(root_); + auto node = intr_ptr::static_pointer_cast(root_); int pos = 0; while (true) @@ -58,8 +59,8 @@ SHAMap::visitNodes(std::function const& function) const { if (!node->isEmptyBranch(pos)) { - std::shared_ptr child = - descendNoStore(node, pos); + intr_ptr::SharedPtr child = + descendNoStore(*node, pos); if (!function(*child)) return; @@ -78,7 +79,8 @@ SHAMap::visitNodes(std::function const& function) const } // descend to the child's first position - node = std::static_pointer_cast(child); + node = + intr_ptr::static_pointer_cast(child); pos = 0; } } @@ -114,7 +116,7 @@ SHAMap::visitDifferences( if (root_->isLeaf()) { - auto leaf = std::static_pointer_cast(root_); + auto leaf = intr_ptr::static_pointer_cast(root_); if (!have || !have->hasLeafNode(leaf->peekItem()->key(), leaf->getHash())) function(*root_); @@ -201,7 +203,8 @@ SHAMap::gmn_ProcessNodes(MissingNodes& mn, MissingNodes::StackEntry& se) mn.filter_, pending, [node, nodeID, branch, &mn]( - std::shared_ptr found, SHAMapHash const&) { + intr_ptr::SharedPtr found, + SHAMapHash const&) { // a read completed asynchronously std::unique_lock lock{mn.deferLock_}; mn.finishedReads_.emplace_back( @@ -270,7 +273,7 @@ SHAMap::gmn_ProcessDeferredReads(MissingNodes& mn) SHAMapInnerNode*, SHAMapNodeID, int, - std::shared_ptr> + intr_ptr::SharedPtr> deferredNode; { std::unique_lock lock{mn.deferLock_}; @@ -324,7 +327,7 @@ SHAMap::getMissingNodes(int max, SHAMapSyncFilter* filter) f_.getFullBelowCache()->getGeneration()); if (!root_->isInner() || - std::static_pointer_cast(root_)->isFullBelow( + intr_ptr::static_pointer_cast(root_)->isFullBelow( mn.generation_)) { clearSynching(); @@ -797,8 +800,9 @@ SHAMap::getProofPath(uint256 const& key) const } if (auto const& node = stack.top().first; !node || node->isInner() || - std::static_pointer_cast(node)->peekItem()->key() != - key) + intr_ptr::static_pointer_cast(node) + ->peekItem() + ->key() != key) { JLOG(journal_.debug()) << "no path to " << key; return {}; diff --git a/src/xrpld/shamap/detail/SHAMapTreeNode.cpp b/src/xrpld/shamap/detail/SHAMapTreeNode.cpp index fe5b5377eee..58d0f14f74f 100644 --- a/src/xrpld/shamap/detail/SHAMapTreeNode.cpp +++ b/src/xrpld/shamap/detail/SHAMapTreeNode.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -36,7 +37,7 @@ namespace ripple { -std::shared_ptr +intr_ptr::SharedPtr SHAMapTreeNode::makeTransaction( Slice data, SHAMapHash const& hash, @@ -46,12 +47,13 @@ SHAMapTreeNode::makeTransaction( make_shamapitem(sha512Half(HashPrefix::transactionID, data), data); if (hashValid) - return std::make_shared(std::move(item), 0, hash); + return intr_ptr::make_shared( + std::move(item), 0, hash); - return std::make_shared(std::move(item), 0); + return intr_ptr::make_shared(std::move(item), 0); } -std::shared_ptr +intr_ptr::SharedPtr SHAMapTreeNode::makeTransactionWithMeta( Slice data, SHAMapHash const& hash, @@ -74,13 +76,13 @@ SHAMapTreeNode::makeTransactionWithMeta( auto item = make_shamapitem(tag, s.slice()); if (hashValid) - return std::make_shared( + return intr_ptr::make_shared( std::move(item), 0, hash); - return std::make_shared(std::move(item), 0); + return intr_ptr::make_shared(std::move(item), 0); } -std::shared_ptr +intr_ptr::SharedPtr SHAMapTreeNode::makeAccountState( Slice data, SHAMapHash const& hash, @@ -106,13 +108,14 @@ SHAMapTreeNode::makeAccountState( auto item = make_shamapitem(tag, s.slice()); if (hashValid) - return std::make_shared( + return intr_ptr::make_shared( std::move(item), 0, hash); - return std::make_shared(std::move(item), 0); + return intr_ptr::make_shared( + std::move(item), 0); } -std::shared_ptr +intr_ptr::SharedPtr SHAMapTreeNode::makeFromWire(Slice rawNode) { if (rawNode.empty()) @@ -144,7 +147,7 @@ SHAMapTreeNode::makeFromWire(Slice rawNode) "wire: Unknown type (" + std::to_string(type) + ")"); } -std::shared_ptr +intr_ptr::SharedPtr SHAMapTreeNode::makeFromPrefix(Slice rawNode, SHAMapHash const& hash) { if (rawNode.size() < 4) diff --git a/src/xrpld/shamap/detail/TaggedPointer.h b/src/xrpld/shamap/detail/TaggedPointer.h index 48534076548..cae8534cd24 100644 --- a/src/xrpld/shamap/detail/TaggedPointer.h +++ b/src/xrpld/shamap/detail/TaggedPointer.h @@ -21,6 +21,7 @@ #define RIPPLE_SHAMAP_TAGGEDPOINTER_H_INCLUDED #include +#include #include #include @@ -58,6 +59,7 @@ namespace ripple { */ class TaggedPointer { +private: static_assert( alignof(SHAMapHash) >= 4, "Bad alignment: Tag pointer requires low two bits to be zero."); @@ -171,7 +173,7 @@ class TaggedPointer of each array. */ [[nodiscard]] std:: - tuple*> + tuple*> getHashesAndChildren() const; /** Get the `hashes` array */ @@ -179,7 +181,7 @@ class TaggedPointer getHashes() const; /** Get the `children` array */ - [[nodiscard]] std::shared_ptr* + [[nodiscard]] intr_ptr::SharedPtr* getChildren() const; /** Call the `f` callback for all 16 (branchFactor) branches - even if diff --git a/src/xrpld/shamap/detail/TaggedPointer.ipp b/src/xrpld/shamap/detail/TaggedPointer.ipp index 309913c79c0..0d329b2a0b3 100644 --- a/src/xrpld/shamap/detail/TaggedPointer.ipp +++ b/src/xrpld/shamap/detail/TaggedPointer.ipp @@ -50,7 +50,7 @@ static_assert( // contains multiple chunks. This is the terminology the boost documentation // uses. Pools use "Simple Segregated Storage" as their storage format. constexpr size_t elementSizeBytes = - (sizeof(SHAMapHash) + sizeof(std::shared_ptr)); + (sizeof(SHAMapHash) + sizeof(intr_ptr::SharedPtr)); constexpr size_t blockSizeBytes = kilobytes(512); @@ -234,7 +234,7 @@ TaggedPointer::destroyHashesAndChildren() for (std::size_t i = 0; i < numAllocated; ++i) { hashes[i].~SHAMapHash(); - children[i].~shared_ptr(); + std::destroy_at(&children[i]); } auto [tag, ptr] = decode(); @@ -383,8 +383,10 @@ inline TaggedPointer::TaggedPointer( { // keep new (&dstHashes[dstIndex]) SHAMapHash{srcHashes[srcIndex]}; - new (&dstChildren[dstIndex]) std::shared_ptr{ - std::move(srcChildren[srcIndex])}; + + new (&dstChildren[dstIndex]) + intr_ptr::SharedPtr{ + std::move(srcChildren[srcIndex])}; ++dstIndex; ++srcIndex; } @@ -396,7 +398,7 @@ inline TaggedPointer::TaggedPointer( { new (&dstHashes[dstIndex]) SHAMapHash{}; new (&dstChildren[dstIndex]) - std::shared_ptr{}; + intr_ptr::SharedPtr{}; ++dstIndex; } } @@ -404,7 +406,8 @@ inline TaggedPointer::TaggedPointer( { // add new (&dstHashes[dstIndex]) SHAMapHash{}; - new (&dstChildren[dstIndex]) std::shared_ptr{}; + new (&dstChildren[dstIndex]) + intr_ptr::SharedPtr{}; ++dstIndex; if (srcIsDense) { @@ -418,7 +421,7 @@ inline TaggedPointer::TaggedPointer( { new (&dstHashes[dstIndex]) SHAMapHash{}; new (&dstChildren[dstIndex]) - std::shared_ptr{}; + intr_ptr::SharedPtr{}; ++dstIndex; } if (srcIsDense) @@ -432,7 +435,7 @@ inline TaggedPointer::TaggedPointer( for (int i = dstIndex; i < dstNumAllocated; ++i) { new (&dstHashes[i]) SHAMapHash{}; - new (&dstChildren[i]) std::shared_ptr{}; + new (&dstChildren[i]) intr_ptr::SharedPtr{}; } *this = std::move(dst); } @@ -452,7 +455,7 @@ inline TaggedPointer::TaggedPointer( // allocate hashes and children, but do not run constructors TaggedPointer newHashesAndChildren{RawAllocateTag{}, toAllocate}; SHAMapHash *newHashes, *oldHashes; - std::shared_ptr*newChildren, *oldChildren; + intr_ptr::SharedPtr*newChildren, *oldChildren; std::uint8_t newNumAllocated; // structured bindings can't be captured in c++ 17; use tie instead std::tie(newNumAllocated, newHashes, newChildren) = @@ -464,7 +467,7 @@ inline TaggedPointer::TaggedPointer( // new arrays are dense, old arrays are sparse iterNonEmptyChildIndexes(isBranch, [&](auto branchNum, auto indexNum) { new (&newHashes[branchNum]) SHAMapHash{oldHashes[indexNum]}; - new (&newChildren[branchNum]) std::shared_ptr{ + new (&newChildren[branchNum]) intr_ptr::SharedPtr{ std::move(oldChildren[indexNum])}; }); // Run the constructors for the remaining elements @@ -473,7 +476,7 @@ inline TaggedPointer::TaggedPointer( if ((1 << i) & isBranch) continue; new (&newHashes[i]) SHAMapHash{}; - new (&newChildren[i]) std::shared_ptr{}; + new (&newChildren[i]) intr_ptr::SharedPtr{}; } } else @@ -484,7 +487,7 @@ inline TaggedPointer::TaggedPointer( new (&newHashes[curCompressedIndex]) SHAMapHash{oldHashes[indexNum]}; new (&newChildren[curCompressedIndex]) - std::shared_ptr{ + intr_ptr::SharedPtr{ std::move(oldChildren[indexNum])}; ++curCompressedIndex; }); @@ -492,7 +495,7 @@ inline TaggedPointer::TaggedPointer( for (int i = curCompressedIndex; i < newNumAllocated; ++i) { new (&newHashes[i]) SHAMapHash{}; - new (&newChildren[i]) std::shared_ptr{}; + new (&newChildren[i]) intr_ptr::SharedPtr{}; } } @@ -506,7 +509,7 @@ inline TaggedPointer::TaggedPointer(std::uint8_t numChildren) for (std::size_t i = 0; i < numAllocated; ++i) { new (&hashes[i]) SHAMapHash{}; - new (&children[i]) std::shared_ptr{}; + new (&children[i]) intr_ptr::SharedPtr{}; } } @@ -545,14 +548,15 @@ TaggedPointer::isDense() const } [[nodiscard]] inline std:: - tuple*> + tuple*> TaggedPointer::getHashesAndChildren() const { auto const [tag, ptr] = decode(); auto const hashes = reinterpret_cast(ptr); std::uint8_t numAllocated = boundaries[tag]; - auto const children = reinterpret_cast*>( - hashes + numAllocated); + auto const children = + reinterpret_cast*>( + hashes + numAllocated); return {numAllocated, hashes, children}; }; @@ -562,7 +566,7 @@ TaggedPointer::getHashes() const return reinterpret_cast(tp_ & ptrMask); }; -[[nodiscard]] inline std::shared_ptr* +[[nodiscard]] inline intr_ptr::SharedPtr* TaggedPointer::getChildren() const { auto [unused1, unused2, result] = getHashesAndChildren(); From 136db4fac7d1ed503775917609bc5a46725b66c6 Mon Sep 17 00:00:00 2001 From: Scott Determan Date: Mon, 20 Nov 2023 21:16:45 -0500 Subject: [PATCH 4/7] [fold] Replace jthread with thread (mac does not have jthread yet) --- src/test/basics/IntrusiveShared_test.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/test/basics/IntrusiveShared_test.cpp b/src/test/basics/IntrusiveShared_test.cpp index ad62b14f498..5fadd63d6e7 100644 --- a/src/test/basics/IntrusiveShared_test.cpp +++ b/src/test/basics/IntrusiveShared_test.cpp @@ -314,12 +314,12 @@ class IntrusiveShared_test : public beast::unit_test::suite destructorRan = true; } }; - std::jthread t1{[&] { + std::thread t1{[&] { partialDeleteStartedSyncPoint.arrive_and_wait(); weak.reset(); // Trigger a full delete as soon as the partial // delete starts }}; - std::jthread t2{[&] { + std::thread t2{[&] { strong.reset(); // Trigger a partial delete }}; t1.join(); @@ -364,11 +364,11 @@ class IntrusiveShared_test : public beast::unit_test::suite destructorRan = true; } }; - std::jthread t1{[&] { + std::thread t1{[&] { weak.reset(); weakResetSyncPoint.arrive_and_wait(); }}; - std::jthread t2{[&] { + std::thread t2{[&] { weakResetSyncPoint.arrive_and_wait(); strong.reset(); // Trigger a partial delete }}; @@ -501,7 +501,7 @@ class IntrusiveShared_test : public beast::unit_test::suite v.clear(); } }; - std::vector threads; + std::vector threads; for (int i = 0; i < numThreads; ++i) { threads.emplace_back(cloneAndDestroy, i); @@ -649,7 +649,7 @@ class IntrusiveShared_test : public beast::unit_test::suite v.clear(); } }; - std::vector threads; + std::vector threads; for (int i = 0; i < numThreads; ++i) { threads.emplace_back(cloneAndDestroy, i); @@ -759,7 +759,7 @@ class IntrusiveShared_test : public beast::unit_test::suite toLock[threadId].reset(); } }; - std::vector threads; + std::vector threads; for (int i = 0; i < numThreads; ++i) { threads.emplace_back(lockAndDestroy, i); From 19464a0c6b31e670e565ae0f385dc4e3fa7738e8 Mon Sep 17 00:00:00 2001 From: Valentin Balaschenko <13349202+vlntb@users.noreply.github.com> Date: Mon, 14 Oct 2024 12:34:01 +0100 Subject: [PATCH 5/7] adding missing headers --- src/test/basics/IntrusiveShared_test.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/test/basics/IntrusiveShared_test.cpp b/src/test/basics/IntrusiveShared_test.cpp index 5fadd63d6e7..7ad3f8666e9 100644 --- a/src/test/basics/IntrusiveShared_test.cpp +++ b/src/test/basics/IntrusiveShared_test.cpp @@ -1,17 +1,19 @@ +#include #include #include #include #include -#include #include #include #include #include #include +#include #include #include #include +#include namespace ripple { namespace tests { From c0659ff3aa229017da6e7d5da10282363f8c4891 Mon Sep 17 00:00:00 2001 From: Valentin Balaschenko <13349202+vlntb@users.noreply.github.com> Date: Thu, 17 Oct 2024 18:33:44 +0100 Subject: [PATCH 6/7] [remove] Temporary change to measure maximum strong and weak reference counts --- include/xrpl/basics/IntrusiveRefCounts.h | 37 ++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/include/xrpl/basics/IntrusiveRefCounts.h b/include/xrpl/basics/IntrusiveRefCounts.h index 07a3c301e2f..59b7f5427c5 100644 --- a/include/xrpl/basics/IntrusiveRefCounts.h +++ b/include/xrpl/basics/IntrusiveRefCounts.h @@ -23,6 +23,7 @@ #include #include #include +#include namespace ripple { @@ -148,6 +149,12 @@ struct IntrusiveRefCounts mutable std::atomic refCounts{strongDelta}; + // TODO: temporary change. Remove after test is done + static std::atomic maxStrongRefCount; + + // TODO: temporary change. Remove after test is done + static std::atomic maxWeakRefCount; + /** Amount to change the strong count when adding or releasing a reference Note: The strong count is stored in the low `StrongCountNumBits` bits @@ -236,16 +243,46 @@ struct IntrusiveRefCounts }; }; +// TODO: temporary change. Remove after test is done +inline std::atomic IntrusiveRefCounts::maxStrongRefCount = 0; + +// TODO: temporary change. Remove after test is done +inline std::atomic IntrusiveRefCounts::maxWeakRefCount = 0; + inline void IntrusiveRefCounts::addStrongRef() const noexcept { refCounts.fetch_add(strongDelta, std::memory_order_acq_rel); + RefCountPair const val = refCounts.load(std::memory_order_acquire); + FieldType newStrongRefCount = val.strong; + + // TODO: remove after test + FieldType currentMax = IntrusiveRefCounts::maxStrongRefCount.load(); + while (currentMax < newStrongRefCount && + IntrusiveRefCounts::maxStrongRefCount.compare_exchange_weak( + currentMax, newStrongRefCount)) + { + std::cout << "Maximum strong ref count updated: " << newStrongRefCount + << std::endl; + } } inline void IntrusiveRefCounts::addWeakRef() const noexcept { refCounts.fetch_add(weakDelta, std::memory_order_acq_rel); + RefCountPair const val = refCounts.load(std::memory_order_acquire); + FieldType newWeakRefCount = val.weak; + + // TODO: remove after test + FieldType currentMax = IntrusiveRefCounts::maxWeakRefCount.load(); + while (currentMax < newWeakRefCount && + IntrusiveRefCounts::maxWeakRefCount.compare_exchange_weak( + currentMax, newWeakRefCount)) + { + std::cout << "Maximum weak ref count updated: " << newWeakRefCount + << std::endl; + } } inline ReleaseRefAction From c20d442b6a1ae762c60a3a833abe8131acdc92c2 Mon Sep 17 00:00:00 2001 From: Valentin Balaschenko <13349202+vlntb@users.noreply.github.com> Date: Tue, 5 Nov 2024 11:43:48 +0000 Subject: [PATCH 7/7] [remove] Temporary change to measure max ref counts when switching from strong to weak --- include/xrpl/basics/IntrusiveRefCounts.h | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/include/xrpl/basics/IntrusiveRefCounts.h b/include/xrpl/basics/IntrusiveRefCounts.h index 59b7f5427c5..71306fbda89 100644 --- a/include/xrpl/basics/IntrusiveRefCounts.h +++ b/include/xrpl/basics/IntrusiveRefCounts.h @@ -371,6 +371,18 @@ IntrusiveRefCounts::addWeakReleaseStrongRef() const if (refCounts.compare_exchange_weak( prevIntVal, nextIntVal, std::memory_order_release)) { + // TODO: remove after test + RefCountPair const val = refCounts.load(std::memory_order_acquire); + FieldType currentMax = IntrusiveRefCounts::maxWeakRefCount.load(); + while (currentMax < val.weak && + IntrusiveRefCounts::maxWeakRefCount.compare_exchange_weak( + currentMax, val.weak)) + { + std::cout << "Maximum weak ref count updated from " + "addWeakReleaseStrongRef: " + << val.weak << std::endl; + } + assert(!(prevIntVal & partialDestroyStartedMask)); return action; }