Incremental improvements to path finding memory usage:

* Abort background path finding when closed or disconnected
* Exit pathfinding job thread if there are no requests left
* Don't bother creating the path find job if there are no requests
* Refactor to remove circular dependency between InfoSub and PathRequest
This commit is contained in:
Edward Hennis
2022-01-26 19:41:36 -05:00
committed by Nik Bougalis
parent 4d5459d041
commit e7e672c3f8
26 changed files with 550 additions and 108 deletions

View File

@@ -733,6 +733,7 @@ if (tests)
src/test/basics/contract_test.cpp src/test/basics/contract_test.cpp
src/test/basics/FeeUnits_test.cpp src/test/basics/FeeUnits_test.cpp
src/test/basics/hardened_hash_test.cpp src/test/basics/hardened_hash_test.cpp
src/test/basics/join_test.cpp
src/test/basics/mulDiv_test.cpp src/test/basics/mulDiv_test.cpp
src/test/basics/tagged_integer_test.cpp src/test/basics/tagged_integer_test.cpp
#[===============================[ #[===============================[

View File

@@ -20,6 +20,7 @@
#ifndef RIPPLE_APP_LEDGER_LEDGERHOLDER_H_INCLUDED #ifndef RIPPLE_APP_LEDGER_LEDGERHOLDER_H_INCLUDED
#define RIPPLE_APP_LEDGER_LEDGERHOLDER_H_INCLUDED #define RIPPLE_APP_LEDGER_LEDGERHOLDER_H_INCLUDED
#include <ripple/basics/CountedObject.h>
#include <ripple/basics/contract.h> #include <ripple/basics/contract.h>
#include <mutex> #include <mutex>
@@ -35,7 +36,7 @@ namespace ripple {
way the object always holds a value. We can use the way the object always holds a value. We can use the
genesis ledger in all cases. genesis ledger in all cases.
*/ */
class LedgerHolder class LedgerHolder : public CountedObject<LedgerHolder>
{ {
public: public:
// Update the held ledger // Update the held ledger

View File

@@ -20,6 +20,7 @@
#ifndef RIPPLE_APP_LEDGER_LEDGERREPLAY_H_INCLUDED #ifndef RIPPLE_APP_LEDGER_LEDGERREPLAY_H_INCLUDED
#define RIPPLE_APP_LEDGER_LEDGERREPLAY_H_INCLUDED #define RIPPLE_APP_LEDGER_LEDGERREPLAY_H_INCLUDED
#include <ripple/basics/CountedObject.h>
#include <cstdint> #include <cstdint>
#include <map> #include <map>
#include <memory> #include <memory>
@@ -29,7 +30,7 @@ namespace ripple {
class Ledger; class Ledger;
class STTx; class STTx;
class LedgerReplay class LedgerReplay : public CountedObject<LedgerReplay>
{ {
std::shared_ptr<Ledger const> parent_; std::shared_ptr<Ledger const> parent_;
std::shared_ptr<Ledger const> replay_; std::shared_ptr<Ledger const> replay_;

View File

@@ -261,8 +261,13 @@ LedgerMaster::getPublishedLedgerAge()
std::chrono::seconds ret = app_.timeKeeper().closeTime().time_since_epoch(); std::chrono::seconds ret = app_.timeKeeper().closeTime().time_since_epoch();
ret -= pubClose; ret -= pubClose;
ret = (ret > 0s) ? ret : 0s; ret = (ret > 0s) ? ret : 0s;
static std::chrono::seconds lastRet = -1s;
JLOG(m_journal.trace()) << "Published ledger age is " << ret.count(); if (ret != lastRet)
{
JLOG(m_journal.trace()) << "Published ledger age is " << ret.count();
lastRet = ret;
}
return ret; return ret;
} }
@@ -287,8 +292,13 @@ LedgerMaster::getValidatedLedgerAge()
std::chrono::seconds ret = app_.timeKeeper().closeTime().time_since_epoch(); std::chrono::seconds ret = app_.timeKeeper().closeTime().time_since_epoch();
ret -= valClose; ret -= valClose;
ret = (ret > 0s) ? ret : 0s; ret = (ret > 0s) ? ret : 0s;
static std::chrono::seconds lastRet = -1s;
JLOG(m_journal.trace()) << "Validated ledger age is " << ret.count(); if (ret != lastRet)
{
JLOG(m_journal.trace()) << "Validated ledger age is " << ret.count();
lastRet = ret;
}
return ret; return ret;
} }
@@ -1483,12 +1493,14 @@ LedgerMaster::updatePaths()
if (app_.getOPs().isNeedNetworkLedger()) if (app_.getOPs().isNeedNetworkLedger())
{ {
--mPathFindThread; --mPathFindThread;
JLOG(m_journal.debug()) << "Need network ledger for updating paths";
return; return;
} }
} }
while (!app_.getJobQueue().isStopping()) while (!app_.getJobQueue().isStopping())
{ {
JLOG(m_journal.debug()) << "updatePaths running";
std::shared_ptr<ReadView const> lastLedger; std::shared_ptr<ReadView const> lastLedger;
{ {
std::lock_guard ml(m_mutex); std::lock_guard ml(m_mutex);
@@ -1506,6 +1518,7 @@ LedgerMaster::updatePaths()
else else
{ // Nothing to do { // Nothing to do
--mPathFindThread; --mPathFindThread;
JLOG(m_journal.debug()) << "Nothing to do for updating paths";
return; return;
} }
} }
@@ -1527,7 +1540,31 @@ LedgerMaster::updatePaths()
try try
{ {
app_.getPathRequests().updateAll(lastLedger); auto& pathRequests = app_.getPathRequests();
{
std::lock_guard ml(m_mutex);
if (!pathRequests.requestsPending())
{
--mPathFindThread;
JLOG(m_journal.debug())
<< "No path requests found. Nothing to do for updating "
"paths. "
<< mPathFindThread << " jobs remaining";
return;
}
}
JLOG(m_journal.debug()) << "Updating paths";
pathRequests.updateAll(lastLedger);
std::lock_guard ml(m_mutex);
if (!pathRequests.requestsPending())
{
JLOG(m_journal.debug())
<< "No path requests left. No need for further updating "
"paths";
--mPathFindThread;
return;
}
} }
catch (SHAMapMissingNode const& mn) catch (SHAMapMissingNode const& mn)
{ {
@@ -1587,8 +1624,11 @@ LedgerMaster::newPFWork(
const char* name, const char* name,
std::unique_lock<std::recursive_mutex>&) std::unique_lock<std::recursive_mutex>&)
{ {
if (mPathFindThread < 2) if (mPathFindThread < 2 && app_.getPathRequests().requestsPending())
{ {
JLOG(m_journal.debug())
<< "newPFWork: Creating job. path find threads: "
<< mPathFindThread;
if (app_.getJobQueue().addJob( if (app_.getJobQueue().addJob(
jtUPDATE_PF, name, [this]() { updatePaths(); })) jtUPDATE_PF, name, [this]() { updatePaths(); }))
{ {

View File

@@ -20,6 +20,7 @@
#ifndef RIPPLE_APP_MISC_CANONICALTXSET_H_INCLUDED #ifndef RIPPLE_APP_MISC_CANONICALTXSET_H_INCLUDED
#define RIPPLE_APP_MISC_CANONICALTXSET_H_INCLUDED #define RIPPLE_APP_MISC_CANONICALTXSET_H_INCLUDED
#include <ripple/basics/CountedObject.h>
#include <ripple/protocol/RippleLedgerHash.h> #include <ripple/protocol/RippleLedgerHash.h>
#include <ripple/protocol/STTx.h> #include <ripple/protocol/STTx.h>
#include <ripple/protocol/SeqProxy.h> #include <ripple/protocol/SeqProxy.h>
@@ -34,7 +35,7 @@ namespace ripple {
*/ */
// VFALCO TODO rename to SortedTxSet // VFALCO TODO rename to SortedTxSet
class CanonicalTXSet class CanonicalTXSet : public CountedObject<CanonicalTXSet>
{ {
private: private:
class Key class Key

View File

@@ -20,10 +20,12 @@
#ifndef RIPPLE_APP_MISC_ORDERBOOK_H_INCLUDED #ifndef RIPPLE_APP_MISC_ORDERBOOK_H_INCLUDED
#define RIPPLE_APP_MISC_ORDERBOOK_H_INCLUDED #define RIPPLE_APP_MISC_ORDERBOOK_H_INCLUDED
#include <ripple/basics/CountedObject.h>
namespace ripple { namespace ripple {
/** Describes a serialized ledger entry for an order book. */ /** Describes a serialized ledger entry for an order book. */
class OrderBook class OrderBook : public CountedObject<OrderBook>
{ {
public: public:
using pointer = std::shared_ptr<OrderBook>; using pointer = std::shared_ptr<OrderBook>;

View File

@@ -441,7 +441,7 @@ PathRequest::parseJson(Json::Value const& jvParams)
} }
Json::Value Json::Value
PathRequest::doClose(Json::Value const&) PathRequest::doClose()
{ {
JLOG(m_journal.debug()) << iIdentifier << " closed"; JLOG(m_journal.debug()) << iIdentifier << " closed";
std::lock_guard sl(mLock); std::lock_guard sl(mLock);
@@ -457,13 +457,20 @@ PathRequest::doStatus(Json::Value const&)
return jvStatus; return jvStatus;
} }
void
PathRequest::doAborting() const
{
JLOG(m_journal.info()) << iIdentifier << " aborting early";
}
std::unique_ptr<Pathfinder> const& std::unique_ptr<Pathfinder> const&
PathRequest::getPathFinder( PathRequest::getPathFinder(
std::shared_ptr<RippleLineCache> const& cache, std::shared_ptr<RippleLineCache> const& cache,
hash_map<Currency, std::unique_ptr<Pathfinder>>& currency_map, hash_map<Currency, std::unique_ptr<Pathfinder>>& currency_map,
Currency const& currency, Currency const& currency,
STAmount const& dst_amount, STAmount const& dst_amount,
int const level) int const level,
std::function<bool(void)> const& continueCallback)
{ {
auto i = currency_map.find(currency); auto i = currency_map.find(currency);
if (i != currency_map.end()) if (i != currency_map.end())
@@ -477,8 +484,8 @@ PathRequest::getPathFinder(
dst_amount, dst_amount,
saSendMax, saSendMax,
app_); app_);
if (pathfinder->findPaths(level)) if (pathfinder->findPaths(level, continueCallback))
pathfinder->computePathRanks(max_paths_); pathfinder->computePathRanks(max_paths_, continueCallback);
else else
pathfinder.reset(); // It's a bad request - clear it. pathfinder.reset(); // It's a bad request - clear it.
return currency_map[currency] = std::move(pathfinder); return currency_map[currency] = std::move(pathfinder);
@@ -488,7 +495,8 @@ bool
PathRequest::findPaths( PathRequest::findPaths(
std::shared_ptr<RippleLineCache> const& cache, std::shared_ptr<RippleLineCache> const& cache,
int const level, int const level,
Json::Value& jvArray) Json::Value& jvArray,
std::function<bool(void)> const& continueCallback)
{ {
auto sourceCurrencies = sciSourceCurrencies; auto sourceCurrencies = sciSourceCurrencies;
if (sourceCurrencies.empty() && saSendMax) if (sourceCurrencies.empty() && saSendMax)
@@ -515,22 +523,33 @@ PathRequest::findPaths(
hash_map<Currency, std::unique_ptr<Pathfinder>> currency_map; hash_map<Currency, std::unique_ptr<Pathfinder>> currency_map;
for (auto const& issue : sourceCurrencies) for (auto const& issue : sourceCurrencies)
{ {
if (continueCallback && !continueCallback())
break;
JLOG(m_journal.debug()) JLOG(m_journal.debug())
<< iIdentifier << iIdentifier
<< " Trying to find paths: " << STAmount(issue, 1).getFullText(); << " Trying to find paths: " << STAmount(issue, 1).getFullText();
auto& pathfinder = getPathFinder( auto& pathfinder = getPathFinder(
cache, currency_map, issue.currency, dst_amount, level); cache,
currency_map,
issue.currency,
dst_amount,
level,
continueCallback);
if (!pathfinder) if (!pathfinder)
{ {
assert(false); assert(continueCallback && !continueCallback());
JLOG(m_journal.debug()) << iIdentifier << " No paths found"; JLOG(m_journal.debug()) << iIdentifier << " No paths found";
continue; continue;
} }
STPath fullLiquidityPath; STPath fullLiquidityPath;
auto ps = pathfinder->getBestPaths( auto ps = pathfinder->getBestPaths(
max_paths_, fullLiquidityPath, mContext[issue], issue.account); max_paths_,
fullLiquidityPath,
mContext[issue],
issue.account,
continueCallback);
mContext[issue] = ps; mContext[issue] = ps;
auto& sourceAccount = !isXRP(issue.account) auto& sourceAccount = !isXRP(issue.account)
@@ -628,7 +647,10 @@ PathRequest::findPaths(
} }
Json::Value Json::Value
PathRequest::doUpdate(std::shared_ptr<RippleLineCache> const& cache, bool fast) PathRequest::doUpdate(
std::shared_ptr<RippleLineCache> const& cache,
bool fast,
std::function<bool(void)> const& continueCallback)
{ {
using namespace std::chrono; using namespace std::chrono;
JLOG(m_journal.debug()) JLOG(m_journal.debug())
@@ -699,7 +721,7 @@ PathRequest::doUpdate(std::shared_ptr<RippleLineCache> const& cache, bool fast)
JLOG(m_journal.debug()) << iIdentifier << " processing at level " << iLevel; JLOG(m_journal.debug()) << iIdentifier << " processing at level " << iLevel;
Json::Value jvArray = Json::arrayValue; Json::Value jvArray = Json::arrayValue;
if (findPaths(cache, iLevel, jvArray)) if (findPaths(cache, iLevel, jvArray, continueCallback))
{ {
bLastSuccess = jvArray.size() != 0; bLastSuccess = jvArray.size() != 0;
newStatus[jss::alternatives] = std::move(jvArray); newStatus[jss::alternatives] = std::move(jvArray);
@@ -730,7 +752,7 @@ PathRequest::doUpdate(std::shared_ptr<RippleLineCache> const& cache, bool fast)
} }
InfoSub::pointer InfoSub::pointer
PathRequest::getSubscriber() PathRequest::getSubscriber() const
{ {
return wpSubscriber.lock(); return wpSubscriber.lock();
} }

View File

@@ -43,10 +43,10 @@ class PathRequests;
// Return values from parseJson <0 = invalid, >0 = valid // Return values from parseJson <0 = invalid, >0 = valid
#define PFR_PJ_INVALID -1 #define PFR_PJ_INVALID -1
#define PFR_PJ_NOCHANGE 0 #define PFR_PJ_NOCHANGE 0
#define PFR_PJ_CHANGE 1
class PathRequest : public std::enable_shared_from_this<PathRequest>, class PathRequest final : public InfoSubRequest,
public CountedObject<PathRequest> public std::enable_shared_from_this<PathRequest>,
public CountedObject<PathRequest>
{ {
public: public:
using wptr = std::weak_ptr<PathRequest>; using wptr = std::weak_ptr<PathRequest>;
@@ -55,8 +55,6 @@ public:
using wref = const wptr&; using wref = const wptr&;
public: public:
// VFALCO TODO Break the cyclic dependency on InfoSub
// path_find semantics // path_find semantics
// Subscriber is updated // Subscriber is updated
PathRequest( PathRequest(
@@ -91,15 +89,20 @@ public:
doCreate(std::shared_ptr<RippleLineCache> const&, Json::Value const&); doCreate(std::shared_ptr<RippleLineCache> const&, Json::Value const&);
Json::Value Json::Value
doClose(Json::Value const&); doClose() override;
Json::Value Json::Value
doStatus(Json::Value const&); doStatus(Json::Value const&) override;
void
doAborting() const;
// update jvStatus // update jvStatus
Json::Value Json::Value
doUpdate(std::shared_ptr<RippleLineCache> const&, bool fast); doUpdate(
std::shared_ptr<RippleLineCache> const&,
bool fast,
std::function<bool(void)> const& continueCallback = {});
InfoSub::pointer InfoSub::pointer
getSubscriber(); getSubscriber() const;
bool bool
hasCompletion(); hasCompletion();
@@ -113,13 +116,18 @@ private:
hash_map<Currency, std::unique_ptr<Pathfinder>>&, hash_map<Currency, std::unique_ptr<Pathfinder>>&,
Currency const&, Currency const&,
STAmount const&, STAmount const&,
int const); int const,
std::function<bool(void)> const&);
/** Finds and sets a PathSet in the JSON argument. /** Finds and sets a PathSet in the JSON argument.
Returns false if the source currencies are inavlid. Returns false if the source currencies are inavlid.
*/ */
bool bool
findPaths(std::shared_ptr<RippleLineCache> const&, int const, Json::Value&); findPaths(
std::shared_ptr<RippleLineCache> const&,
int const,
Json::Value&,
std::function<bool(void)> const&);
int int
parseJson(Json::Value const&); parseJson(Json::Value const&);
@@ -156,7 +164,7 @@ private:
int iLevel; int iLevel;
bool bLastSuccess; bool bLastSuccess;
int iIdentifier; int const iIdentifier;
std::chrono::steady_clock::time_point const created_; std::chrono::steady_clock::time_point const created_;
std::chrono::steady_clock::time_point quick_reply_; std::chrono::steady_clock::time_point quick_reply_;

View File

@@ -40,8 +40,12 @@ PathRequests::getLineCache(
{ {
std::lock_guard sl(mLock); std::lock_guard sl(mLock);
std::uint32_t lineSeq = mLineCache ? mLineCache->getLedger()->seq() : 0; auto lineCache = lineCache_.lock();
std::uint32_t lgrSeq = ledger->seq();
std::uint32_t const lineSeq = lineCache ? lineCache->getLedger()->seq() : 0;
std::uint32_t const lgrSeq = ledger->seq();
JLOG(mJournal.debug()) << "getLineCache has cache for " << lineSeq
<< ", considering " << lgrSeq;
if ((lineSeq == 0) || // no ledger if ((lineSeq == 0) || // no ledger
(authoritative && (lgrSeq > lineSeq)) || // newer authoritative ledger (authoritative && (lgrSeq > lineSeq)) || // newer authoritative ledger
@@ -49,9 +53,15 @@ PathRequests::getLineCache(
((lgrSeq + 8) < lineSeq)) || // we jumped way back for some reason ((lgrSeq + 8) < lineSeq)) || // we jumped way back for some reason
(lgrSeq > (lineSeq + 8))) // we jumped way forward for some reason (lgrSeq > (lineSeq + 8))) // we jumped way forward for some reason
{ {
mLineCache = std::make_shared<RippleLineCache>(ledger); JLOG(mJournal.debug())
<< "getLineCache creating new cache for " << lgrSeq;
// Assign to the local before the member, because the member is a
// weak_ptr, and will immediately discard it if there are no other
// references.
lineCache_ = lineCache = std::make_shared<RippleLineCache>(
ledger, app_.journal("RippleLineCache"));
} }
return mLineCache; return lineCache;
} }
void void
@@ -78,8 +88,20 @@ PathRequests::updateAll(std::shared_ptr<ReadView const> const& inLedger)
int processed = 0, removed = 0; int processed = 0, removed = 0;
auto getSubscriber =
[](PathRequest::pointer const& request) -> InfoSub::pointer {
if (auto ipSub = request->getSubscriber();
ipSub && ipSub->getRequest() == request)
{
return ipSub;
}
request->doAborting();
return nullptr;
};
do do
{ {
JLOG(mJournal.trace()) << "updateAll looping";
for (auto const& wr : requests) for (auto const& wr : requests)
{ {
if (app_.getJobQueue().isStopping()) if (app_.getJobQueue().isStopping())
@@ -87,25 +109,40 @@ PathRequests::updateAll(std::shared_ptr<ReadView const> const& inLedger)
auto request = wr.lock(); auto request = wr.lock();
bool remove = true; bool remove = true;
JLOG(mJournal.trace())
<< "updateAll request " << (request ? "" : "not ") << "found";
if (request) if (request)
{ {
auto continueCallback = [&getSubscriber, &request]() {
// This callback is used by doUpdate to determine whether to
// continue working. If getSubscriber returns null, that
// indicates that this request is no longer relevant.
return (bool)getSubscriber(request);
};
if (!request->needsUpdate( if (!request->needsUpdate(
newRequests, cache->getLedger()->seq())) newRequests, cache->getLedger()->seq()))
remove = false; remove = false;
else else
{ {
if (auto ipSub = request->getSubscriber()) if (auto ipSub = getSubscriber(request))
{ {
if (!ipSub->getConsumer().warn()) if (!ipSub->getConsumer().warn())
{ {
Json::Value update = // Release the shared ptr to the subscriber so that
request->doUpdate(cache, false); // it can be freed if the client disconnects, and
// thus fail to lock later.
ipSub.reset();
Json::Value update = request->doUpdate(
cache, false, continueCallback);
request->updateComplete(); request->updateComplete();
update[jss::type] = "path_find"; update[jss::type] = "path_find";
ipSub->send(update, false); if ((ipSub = getSubscriber(request)))
remove = false; {
++processed; ipSub->send(update, false);
remove = false;
++processed;
}
} }
} }
else if (request->hasCompletion()) else if (request->hasCompletion())
@@ -178,6 +215,13 @@ PathRequests::updateAll(std::shared_ptr<ReadView const> const& inLedger)
<< " processed and " << removed << " removed"; << " processed and " << removed << " removed";
} }
bool
PathRequests::requestsPending() const
{
std::lock_guard sl(mLock);
return !requests_.empty();
}
void void
PathRequests::insertPathRequest(PathRequest::pointer const& req) PathRequests::insertPathRequest(PathRequest::pointer const& req)
{ {
@@ -211,7 +255,7 @@ PathRequests::makePathRequest(
if (valid) if (valid)
{ {
subscriber->setPathRequest(req); subscriber->setRequest(req);
insertPathRequest(req); insertPathRequest(req);
app_.getLedgerMaster().newPathRequest(); app_.getLedgerMaster().newPathRequest();
} }
@@ -258,7 +302,8 @@ PathRequests::doLegacyPathRequest(
std::shared_ptr<ReadView const> const& inLedger, std::shared_ptr<ReadView const> const& inLedger,
Json::Value const& request) Json::Value const& request)
{ {
auto cache = std::make_shared<RippleLineCache>(inLedger); auto cache = std::make_shared<RippleLineCache>(
inLedger, app_.journal("RippleLineCache"));
auto req = std::make_shared<PathRequest>( auto req = std::make_shared<PathRequest>(
app_, [] {}, consumer, ++mLastIdentifier, *this, mJournal); app_, [] {}, consumer, ++mLastIdentifier, *this, mJournal);

View File

@@ -51,6 +51,9 @@ public:
void void
updateAll(std::shared_ptr<ReadView const> const& ledger); updateAll(std::shared_ptr<ReadView const> const& ledger);
bool
requestsPending() const;
std::shared_ptr<RippleLineCache> std::shared_ptr<RippleLineCache>
getLineCache( getLineCache(
std::shared_ptr<ReadView const> const& ledger, std::shared_ptr<ReadView const> const& ledger,
@@ -109,11 +112,11 @@ private:
std::vector<PathRequest::wptr> requests_; std::vector<PathRequest::wptr> requests_;
// Use a RippleLineCache // Use a RippleLineCache
std::shared_ptr<RippleLineCache> mLineCache; std::weak_ptr<RippleLineCache> lineCache_;
std::atomic<int> mLastIdentifier; std::atomic<int> mLastIdentifier;
std::recursive_mutex mLock; std::recursive_mutex mutable mLock;
}; };
} // namespace ripple } // namespace ripple

View File

@@ -24,6 +24,7 @@
#include <ripple/app/paths/RippleLineCache.h> #include <ripple/app/paths/RippleLineCache.h>
#include <ripple/app/paths/impl/PathfinderUtils.h> #include <ripple/app/paths/impl/PathfinderUtils.h>
#include <ripple/basics/Log.h> #include <ripple/basics/Log.h>
#include <ripple/basics/join.h>
#include <ripple/core/Config.h> #include <ripple/core/Config.h>
#include <ripple/core/JobQueue.h> #include <ripple/core/JobQueue.h>
#include <ripple/json/to_string.h> #include <ripple/json/to_string.h>
@@ -191,8 +192,11 @@ Pathfinder::Pathfinder(
} }
bool bool
Pathfinder::findPaths(int searchLevel) Pathfinder::findPaths(
int searchLevel,
std::function<bool(void)> const& continueCallback)
{ {
JLOG(j_.trace()) << "findPaths start";
if (mDstAmount == beast::zero) if (mDstAmount == beast::zero)
{ {
// No need to send zero money. // No need to send zero money.
@@ -316,10 +320,13 @@ Pathfinder::findPaths(int searchLevel)
// Now iterate over all paths for that paymentType. // Now iterate over all paths for that paymentType.
for (auto const& costedPath : mPathTable[paymentType]) for (auto const& costedPath : mPathTable[paymentType])
{ {
if (continueCallback && !continueCallback())
return false;
// Only use paths with at most the current search level. // Only use paths with at most the current search level.
if (costedPath.searchLevel <= searchLevel) if (costedPath.searchLevel <= searchLevel)
{ {
addPathsForType(costedPath.type); JLOG(j_.trace()) << "findPaths trying payment type " << paymentType;
addPathsForType(costedPath.type, continueCallback);
if (mCompletePaths.size() > PATHFINDER_MAX_COMPLETE_PATHS) if (mCompletePaths.size() > PATHFINDER_MAX_COMPLETE_PATHS)
break; break;
@@ -401,7 +408,9 @@ Pathfinder::getPathLiquidity(
} }
void void
Pathfinder::computePathRanks(int maxPaths) Pathfinder::computePathRanks(
int maxPaths,
std::function<bool(void)> const& continueCallback)
{ {
mRemainingAmount = convertAmount(mDstAmount, convert_all_); mRemainingAmount = convertAmount(mDstAmount, convert_all_);
@@ -439,7 +448,7 @@ Pathfinder::computePathRanks(int maxPaths)
JLOG(j_.debug()) << "Default path causes exception"; JLOG(j_.debug()) << "Default path causes exception";
} }
rankPaths(maxPaths, mCompletePaths, mPathRanks); rankPaths(maxPaths, mCompletePaths, mPathRanks, continueCallback);
} }
static bool static bool
@@ -480,8 +489,11 @@ void
Pathfinder::rankPaths( Pathfinder::rankPaths(
int maxPaths, int maxPaths,
STPathSet const& paths, STPathSet const& paths,
std::vector<PathRank>& rankedPaths) std::vector<PathRank>& rankedPaths,
std::function<bool(void)> const& continueCallback)
{ {
JLOG(j_.trace()) << "rankPaths with " << paths.size() << " candidates, and "
<< maxPaths << " maximum";
rankedPaths.clear(); rankedPaths.clear();
rankedPaths.reserve(paths.size()); rankedPaths.reserve(paths.size());
@@ -499,6 +511,8 @@ Pathfinder::rankPaths(
for (int i = 0; i < paths.size(); ++i) for (int i = 0; i < paths.size(); ++i)
{ {
if (continueCallback && !continueCallback())
return;
auto const& currentPath = paths[i]; auto const& currentPath = paths[i];
if (!currentPath.empty()) if (!currentPath.empty())
{ {
@@ -554,7 +568,8 @@ Pathfinder::getBestPaths(
int maxPaths, int maxPaths,
STPath& fullLiquidityPath, STPath& fullLiquidityPath,
STPathSet const& extraPaths, STPathSet const& extraPaths,
AccountID const& srcIssuer) AccountID const& srcIssuer,
std::function<bool(void)> const& continueCallback)
{ {
JLOG(j_.debug()) << "findPaths: " << mCompletePaths.size() << " paths and " JLOG(j_.debug()) << "findPaths: " << mCompletePaths.size() << " paths and "
<< extraPaths.size() << " extras"; << extraPaths.size() << " extras";
@@ -567,7 +582,7 @@ Pathfinder::getBestPaths(
isXRP(mSrcCurrency) || (srcIssuer == mSrcAccount); isXRP(mSrcCurrency) || (srcIssuer == mSrcAccount);
std::vector<PathRank> extraPathRanks; std::vector<PathRank> extraPathRanks;
rankPaths(maxPaths, extraPaths, extraPathRanks); rankPaths(maxPaths, extraPaths, extraPathRanks, continueCallback);
STPathSet bestPaths; STPathSet bestPaths;
@@ -582,6 +597,8 @@ Pathfinder::getBestPaths(
while (pathsIterator != mPathRanks.end() || while (pathsIterator != mPathRanks.end() ||
extraPathsIterator != extraPathRanks.end()) extraPathsIterator != extraPathRanks.end())
{ {
if (continueCallback && !continueCallback())
break;
bool usePath = false; bool usePath = false;
bool useExtraPath = false; bool useExtraPath = false;
@@ -692,7 +709,8 @@ Pathfinder::getPathsOut(
Currency const& currency, Currency const& currency,
AccountID const& account, AccountID const& account,
bool isDstCurrency, bool isDstCurrency,
AccountID const& dstAccount) AccountID const& dstAccount,
std::function<bool(void)> const& continueCallback)
{ {
Issue const issue(currency, account); Issue const issue(currency, account);
@@ -755,17 +773,26 @@ void
Pathfinder::addLinks( Pathfinder::addLinks(
STPathSet const& currentPaths, // The paths to build from STPathSet const& currentPaths, // The paths to build from
STPathSet& incompletePaths, // The set of partial paths we add to STPathSet& incompletePaths, // The set of partial paths we add to
int addFlags) int addFlags,
std::function<bool(void)> const& continueCallback)
{ {
JLOG(j_.debug()) << "addLink< on " << currentPaths.size() JLOG(j_.debug()) << "addLink< on " << currentPaths.size()
<< " source(s), flags=" << addFlags; << " source(s), flags=" << addFlags;
for (auto const& path : currentPaths) for (auto const& path : currentPaths)
addLink(path, incompletePaths, addFlags); {
if (continueCallback && !continueCallback())
return;
addLink(path, incompletePaths, addFlags, continueCallback);
}
} }
STPathSet& STPathSet&
Pathfinder::addPathsForType(PathType const& pathType) Pathfinder::addPathsForType(
PathType const& pathType,
std::function<bool(void)> const& continueCallback)
{ {
JLOG(j_.warn()) << "addPathsForType "
<< CollectionAndDelimiter(pathType, ", ");
// See if the set of paths for this type already exists. // See if the set of paths for this type already exists.
auto it = mPaths.find(pathType); auto it = mPaths.find(pathType);
if (it != mPaths.end()) if (it != mPaths.end())
@@ -774,13 +801,16 @@ Pathfinder::addPathsForType(PathType const& pathType)
// Otherwise, if the type has no nodes, return the empty path. // Otherwise, if the type has no nodes, return the empty path.
if (pathType.empty()) if (pathType.empty())
return mPaths[pathType]; return mPaths[pathType];
if (continueCallback && !continueCallback())
return mPaths[{}];
// Otherwise, get the paths for the parent PathType by calling // Otherwise, get the paths for the parent PathType by calling
// addPathsForType recursively. // addPathsForType recursively.
PathType parentPathType = pathType; PathType parentPathType = pathType;
parentPathType.pop_back(); parentPathType.pop_back();
STPathSet const& parentPaths = addPathsForType(parentPathType); STPathSet const& parentPaths =
addPathsForType(parentPathType, continueCallback);
STPathSet& pathsOut = mPaths[pathType]; STPathSet& pathsOut = mPaths[pathType];
JLOG(j_.debug()) << "getPaths< adding onto '" JLOG(j_.debug()) << "getPaths< adding onto '"
@@ -800,26 +830,38 @@ Pathfinder::addPathsForType(PathType const& pathType)
break; break;
case nt_ACCOUNTS: case nt_ACCOUNTS:
addLinks(parentPaths, pathsOut, afADD_ACCOUNTS); addLinks(parentPaths, pathsOut, afADD_ACCOUNTS, continueCallback);
break; break;
case nt_BOOKS: case nt_BOOKS:
addLinks(parentPaths, pathsOut, afADD_BOOKS); addLinks(parentPaths, pathsOut, afADD_BOOKS, continueCallback);
break; break;
case nt_XRP_BOOK: case nt_XRP_BOOK:
addLinks(parentPaths, pathsOut, afADD_BOOKS | afOB_XRP); addLinks(
parentPaths,
pathsOut,
afADD_BOOKS | afOB_XRP,
continueCallback);
break; break;
case nt_DEST_BOOK: case nt_DEST_BOOK:
addLinks(parentPaths, pathsOut, afADD_BOOKS | afOB_LAST); addLinks(
parentPaths,
pathsOut,
afADD_BOOKS | afOB_LAST,
continueCallback);
break; break;
case nt_DESTINATION: case nt_DESTINATION:
// FIXME: What if a different issuer was specified on the // FIXME: What if a different issuer was specified on the
// destination amount? // destination amount?
// TODO(tom): what does this even mean? Should it be a JIRA? // TODO(tom): what does this even mean? Should it be a JIRA?
addLinks(parentPaths, pathsOut, afADD_ACCOUNTS | afAC_LAST); addLinks(
parentPaths,
pathsOut,
afADD_ACCOUNTS | afAC_LAST,
continueCallback);
break; break;
} }
@@ -890,7 +932,8 @@ void
Pathfinder::addLink( Pathfinder::addLink(
const STPath& currentPath, // The path to build from const STPath& currentPath, // The path to build from
STPathSet& incompletePaths, // The set of partial paths we add to STPathSet& incompletePaths, // The set of partial paths we add to
int addFlags) int addFlags,
std::function<bool(void)> const& continueCallback)
{ {
auto const& pathEnd = currentPath.empty() ? mSource : currentPath.back(); auto const& pathEnd = currentPath.empty() ? mSource : currentPath.back();
auto const& uEndCurrency = pathEnd.getCurrency(); auto const& uEndCurrency = pathEnd.getCurrency();
@@ -903,7 +946,8 @@ Pathfinder::addLink(
// rather than the ultimate destination? // rather than the ultimate destination?
bool const hasEffectiveDestination = mEffectiveDst != mDstAccount; bool const hasEffectiveDestination = mEffectiveDst != mDstAccount;
JLOG(j_.trace()) << "addLink< flags=" << addFlags << " onXRP=" << bOnXRP; JLOG(j_.trace()) << "addLink< flags=" << addFlags << " onXRP=" << bOnXRP
<< " completePaths size=" << mCompletePaths.size();
JLOG(j_.trace()) << currentPath.getJson(JsonOptions::none); JLOG(j_.trace()) << currentPath.getJson(JsonOptions::none);
if (addFlags & afADD_ACCOUNTS) if (addFlags & afADD_ACCOUNTS)
@@ -939,6 +983,8 @@ Pathfinder::addLink(
for (auto const& rs : rippleLines) for (auto const& rs : rippleLines)
{ {
if (continueCallback && !continueCallback())
return;
auto const& acct = rs.getAccountIDPeer(); auto const& acct = rs.getAccountIDPeer();
if (hasEffectiveDestination && (acct == mDstAccount)) if (hasEffectiveDestination && (acct == mDstAccount))
@@ -1002,7 +1048,8 @@ Pathfinder::addLink(
uEndCurrency, uEndCurrency,
acct, acct,
bIsEndCurrency, bIsEndCurrency,
mEffectiveDst); mEffectiveDst,
continueCallback);
if (out) if (out)
candidates.push_back({out, acct}); candidates.push_back({out, acct});
} }
@@ -1030,6 +1077,8 @@ Pathfinder::addLink(
auto it = candidates.begin(); auto it = candidates.begin();
while (count-- != 0) while (count-- != 0)
{ {
if (continueCallback && !continueCallback())
return;
// Add accounts to incompletePaths // Add accounts to incompletePaths
STPathElement pathElement( STPathElement pathElement(
STPathElement::typeAccount, STPathElement::typeAccount,
@@ -1074,6 +1123,8 @@ Pathfinder::addLink(
for (auto const& book : books) for (auto const& book : books)
{ {
if (continueCallback && !continueCallback())
return;
if (!currentPath.hasSeen( if (!currentPath.hasSeen(
xrpAccount(), xrpAccount(),
book->getCurrencyOut(), book->getCurrencyOut(),

View File

@@ -22,6 +22,7 @@
#include <ripple/app/ledger/Ledger.h> #include <ripple/app/ledger/Ledger.h>
#include <ripple/app/paths/RippleLineCache.h> #include <ripple/app/paths/RippleLineCache.h>
#include <ripple/basics/CountedObject.h>
#include <ripple/core/LoadEvent.h> #include <ripple/core/LoadEvent.h>
#include <ripple/protocol/STAmount.h> #include <ripple/protocol/STAmount.h>
#include <ripple/protocol/STPathSet.h> #include <ripple/protocol/STPathSet.h>
@@ -34,7 +35,7 @@ namespace ripple {
@see RippleCalc @see RippleCalc
*/ */
class Pathfinder class Pathfinder : public CountedObject<Pathfinder>
{ {
public: public:
/** Construct a pathfinder without an issuer.*/ /** Construct a pathfinder without an issuer.*/
@@ -56,11 +57,15 @@ public:
initPathTable(); initPathTable();
bool bool
findPaths(int searchLevel); findPaths(
int searchLevel,
std::function<bool(void)> const& continueCallback = {});
/** Compute the rankings of the paths. */ /** Compute the rankings of the paths. */
void void
computePathRanks(int maxPaths); computePathRanks(
int maxPaths,
std::function<bool(void)> const& continueCallback = {});
/* Get the best paths, up to maxPaths in number, from mCompletePaths. /* Get the best paths, up to maxPaths in number, from mCompletePaths.
@@ -72,7 +77,8 @@ public:
int maxPaths, int maxPaths,
STPath& fullLiquidityPath, STPath& fullLiquidityPath,
STPathSet const& extraPaths, STPathSet const& extraPaths,
AccountID const& srcIssuer); AccountID const& srcIssuer,
std::function<bool(void)> const& continueCallback = {});
enum NodeType { enum NodeType {
nt_SOURCE, // The source account: with an issuer account, if needed. nt_SOURCE, // The source account: with an issuer account, if needed.
@@ -127,7 +133,9 @@ private:
// Add all paths of one type to mCompletePaths. // Add all paths of one type to mCompletePaths.
STPathSet& STPathSet&
addPathsForType(PathType const& type); addPathsForType(
PathType const& type,
std::function<bool(void)> const& continueCallback);
bool bool
issueMatchesOrigin(Issue const&); issueMatchesOrigin(Issue const&);
@@ -137,20 +145,23 @@ private:
Currency const& currency, Currency const& currency,
AccountID const& account, AccountID const& account,
bool isDestCurrency, bool isDestCurrency,
AccountID const& dest); AccountID const& dest,
std::function<bool(void)> const& continueCallback);
void void
addLink( addLink(
STPath const& currentPath, STPath const& currentPath,
STPathSet& incompletePaths, STPathSet& incompletePaths,
int addFlags); int addFlags,
std::function<bool(void)> const& continueCallback);
// Call addLink() for each path in currentPaths. // Call addLink() for each path in currentPaths.
void void
addLinks( addLinks(
STPathSet const& currentPaths, STPathSet const& currentPaths,
STPathSet& incompletePaths, STPathSet& incompletePaths,
int addFlags); int addFlags,
std::function<bool(void)> const& continueCallback);
// Compute the liquidity for a path. Return tesSUCCESS if it has has enough // Compute the liquidity for a path. Return tesSUCCESS if it has has enough
// liquidity to be worth keeping, otherwise an error. // liquidity to be worth keeping, otherwise an error.
@@ -178,7 +189,8 @@ private:
rankPaths( rankPaths(
int maxPaths, int maxPaths,
STPathSet const& paths, STPathSet const& paths,
std::vector<PathRank>& rankedPaths); std::vector<PathRank>& rankedPaths,
std::function<bool(void)> const& continueCallback);
AccountID mSrcAccount; AccountID mSrcAccount;
AccountID mDstAccount; AccountID mDstAccount;

View File

@@ -23,12 +23,22 @@
namespace ripple { namespace ripple {
RippleLineCache::RippleLineCache(std::shared_ptr<ReadView const> const& ledger) RippleLineCache::RippleLineCache(
std::shared_ptr<ReadView const> const& ledger,
beast::Journal j)
: journal_(j)
{ {
// We want the caching that OpenView provides mLedger = ledger;
// And we need to own a shared_ptr to the input view
// VFALCO TODO This should be a CachedLedger JLOG(journal_.debug()) << "RippleLineCache created for ledger "
mLedger = std::make_shared<OpenView>(&*ledger, ledger); << mLedger->info().seq;
}
RippleLineCache::~RippleLineCache()
{
JLOG(journal_.debug()) << "~RippleLineCache destroyed for ledger "
<< mLedger->info().seq << " with " << lines_.size()
<< " accounts";
} }
std::vector<PathFindTrustLine> const& std::vector<PathFindTrustLine> const&
@@ -43,6 +53,13 @@ RippleLineCache::getRippleLines(AccountID const& accountID)
if (inserted) if (inserted)
it->second = PathFindTrustLine::getItems(accountID, *mLedger); it->second = PathFindTrustLine::getItems(accountID, *mLedger);
JLOG(journal_.debug()) << "RippleLineCache getRippleLines for ledger "
<< mLedger->info().seq << " found "
<< it->second.size() << " lines for "
<< (inserted ? "new " : "existing ") << accountID
<< " out of a total of " << lines_.size()
<< " accounts";
return it->second; return it->second;
} }

View File

@@ -33,10 +33,13 @@
namespace ripple { namespace ripple {
// Used by Pathfinder // Used by Pathfinder
class RippleLineCache : public CountedObject<RippleLineCache> class RippleLineCache final : public CountedObject<RippleLineCache>
{ {
public: public:
explicit RippleLineCache(std::shared_ptr<ReadView const> const& l); explicit RippleLineCache(
std::shared_ptr<ReadView const> const& l,
beast::Journal j);
~RippleLineCache();
std::shared_ptr<ReadView const> const& std::shared_ptr<ReadView const> const&
getLedger() const getLedger() const
@@ -53,7 +56,9 @@ private:
ripple::hardened_hash<> hasher_; ripple::hardened_hash<> hasher_;
std::shared_ptr<ReadView const> mLedger; std::shared_ptr<ReadView const> mLedger;
struct AccountKey beast::Journal journal_;
struct AccountKey final : public CountedObject<AccountKey>
{ {
AccountID account_; AccountID account_;
std::size_t hash_value_; std::size_t hash_value_;

108
src/ripple/basics/join.h Normal file
View File

@@ -0,0 +1,108 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2022 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 JOIN_H_INCLUDED
#define JOIN_H_INCLUDED
#include <string>
namespace ripple {
template <class Stream, class Iter>
Stream&
join(Stream& s, Iter iter, Iter end, std::string const& delimiter)
{
if (iter == end)
return s;
s << *iter;
for (++iter; iter != end; ++iter)
s << delimiter << *iter;
return s;
}
template <class Collection>
class CollectionAndDelimiter
{
public:
Collection const& collection;
std::string const delimiter;
explicit CollectionAndDelimiter(Collection const& c, std::string delim)
: collection(c), delimiter(std::move(delim))
{
}
template <class Stream>
friend Stream&
operator<<(Stream& s, CollectionAndDelimiter const& cd)
{
return join(
s,
std::begin(cd.collection),
std::end(cd.collection),
cd.delimiter);
}
};
template <class Collection, std::size_t N>
class CollectionAndDelimiter<Collection[N]>
{
public:
Collection const* collection;
std::string const delimiter;
explicit CollectionAndDelimiter(Collection const c[N], std::string delim)
: collection(c), delimiter(std::move(delim))
{
}
template <class Stream>
friend Stream&
operator<<(Stream& s, CollectionAndDelimiter const& cd)
{
return join(s, cd.collection, cd.collection + N, cd.delimiter);
}
};
// Specialization for const char* strings
template <std::size_t N>
class CollectionAndDelimiter<char[N]>
{
public:
char const* collection;
std::string const delimiter;
explicit CollectionAndDelimiter(char const c[N], std::string delim)
: collection(c), delimiter(std::move(delim))
{
}
template <class Stream>
friend Stream&
operator<<(Stream& s, CollectionAndDelimiter const& cd)
{
auto end = cd.collection + N;
if (N > 0 && *(end - 1) == '\0')
--end;
return join(s, cd.collection, end, cd.delimiter);
}
};
} // namespace ripple
#endif

View File

@@ -20,6 +20,7 @@
#ifndef RIPPLE_CORE_JOB_H_INCLUDED #ifndef RIPPLE_CORE_JOB_H_INCLUDED
#define RIPPLE_CORE_JOB_H_INCLUDED #define RIPPLE_CORE_JOB_H_INCLUDED
#include <ripple/basics/CountedObject.h>
#include <ripple/core/ClosureCounter.h> #include <ripple/core/ClosureCounter.h>
#include <ripple/core/LoadMonitor.h> #include <ripple/core/LoadMonitor.h>
#include <functional> #include <functional>
@@ -92,7 +93,7 @@ enum JobType {
jtNS_WRITE, jtNS_WRITE,
}; };
class Job class Job : public CountedObject<Job>
{ {
public: public:
using clock_type = std::chrono::steady_clock; using clock_type = std::chrono::steady_clock;

View File

@@ -33,7 +33,18 @@ namespace ripple {
// Operations that clients may wish to perform against the network // Operations that clients may wish to perform against the network
// Master operational handler, server sequencer, network tracker // Master operational handler, server sequencer, network tracker
class PathRequest; class InfoSubRequest
{
public:
using pointer = std::shared_ptr<InfoSubRequest>;
virtual ~InfoSubRequest() = default;
virtual Json::Value
doClose() = 0;
virtual Json::Value
doStatus(Json::Value const&) = 0;
};
/** Manages a client's subscription to data feeds. /** Manages a client's subscription to data feeds.
*/ */
@@ -205,13 +216,13 @@ public:
deleteSubAccountHistory(AccountID const& account); deleteSubAccountHistory(AccountID const& account);
void void
clearPathRequest(); clearRequest();
void void
setPathRequest(const std::shared_ptr<PathRequest>& req); setRequest(const std::shared_ptr<InfoSubRequest>& req);
std::shared_ptr<PathRequest> const& std::shared_ptr<InfoSubRequest> const&
getPathRequest(); getRequest();
protected: protected:
std::mutex mLock; std::mutex mLock;
@@ -221,7 +232,7 @@ private:
Source& m_source; Source& m_source;
hash_set<AccountID> realTimeSubscriptions_; hash_set<AccountID> realTimeSubscriptions_;
hash_set<AccountID> normalSubscriptions_; hash_set<AccountID> normalSubscriptions_;
std::shared_ptr<PathRequest> mPathRequest; std::shared_ptr<InfoSubRequest> request_;
std::uint64_t mSeq; std::uint64_t mSeq;
hash_set<AccountID> accountHistorySubscriptions_; hash_set<AccountID> accountHistorySubscriptions_;

View File

@@ -119,21 +119,21 @@ InfoSub::deleteSubAccountHistory(AccountID const& account)
} }
void void
InfoSub::clearPathRequest() InfoSub::clearRequest()
{ {
mPathRequest.reset(); request_.reset();
} }
void void
InfoSub::setPathRequest(const std::shared_ptr<PathRequest>& req) InfoSub::setRequest(const std::shared_ptr<InfoSubRequest>& req)
{ {
mPathRequest = req; request_ = req;
} }
const std::shared_ptr<PathRequest>& const std::shared_ptr<InfoSubRequest>&
InfoSub::getPathRequest() InfoSub::getRequest()
{ {
return mPathRequest; return request_;
} }
} // namespace ripple } // namespace ripple

View File

@@ -20,6 +20,7 @@
#ifndef RIPPLE_PROTOCOL_BOOK_H_INCLUDED #ifndef RIPPLE_PROTOCOL_BOOK_H_INCLUDED
#define RIPPLE_PROTOCOL_BOOK_H_INCLUDED #define RIPPLE_PROTOCOL_BOOK_H_INCLUDED
#include <ripple/basics/CountedObject.h>
#include <ripple/protocol/Issue.h> #include <ripple/protocol/Issue.h>
#include <boost/utility/base_from_member.hpp> #include <boost/utility/base_from_member.hpp>
@@ -29,7 +30,7 @@ namespace ripple {
The order book is a pair of Issues called in and out. The order book is a pair of Issues called in and out.
@see Issue. @see Issue.
*/ */
class Book class Book final : public CountedObject<Book>
{ {
public: public:
Issue in; Issue in;

View File

@@ -20,6 +20,7 @@
#ifndef RIPPLE_PROTOCOL_STAMOUNT_H_INCLUDED #ifndef RIPPLE_PROTOCOL_STAMOUNT_H_INCLUDED
#define RIPPLE_PROTOCOL_STAMOUNT_H_INCLUDED #define RIPPLE_PROTOCOL_STAMOUNT_H_INCLUDED
#include <ripple/basics/CountedObject.h>
#include <ripple/basics/IOUAmount.h> #include <ripple/basics/IOUAmount.h>
#include <ripple/basics/LocalValue.h> #include <ripple/basics/LocalValue.h>
#include <ripple/basics/XRPAmount.h> #include <ripple/basics/XRPAmount.h>
@@ -40,7 +41,7 @@ namespace ripple {
// Wire form: // Wire form:
// High 8 bits are (offset+142), legal range is, 80 to 22 inclusive // High 8 bits are (offset+142), legal range is, 80 to 22 inclusive
// Low 56 bits are value, legal range is 10^15 to (10^16 - 1) inclusive // Low 56 bits are value, legal range is 10^15 to (10^16 - 1) inclusive
class STAmount : public STBase class STAmount final : public STBase, public CountedObject<STAmount>
{ {
public: public:
using mantissa_type = std::uint64_t; using mantissa_type = std::uint64_t;

View File

@@ -20,6 +20,7 @@
#ifndef RIPPLE_PROTOCOL_STPATHSET_H_INCLUDED #ifndef RIPPLE_PROTOCOL_STPATHSET_H_INCLUDED
#define RIPPLE_PROTOCOL_STPATHSET_H_INCLUDED #define RIPPLE_PROTOCOL_STPATHSET_H_INCLUDED
#include <ripple/basics/CountedObject.h>
#include <ripple/json/json_value.h> #include <ripple/json/json_value.h>
#include <ripple/protocol/SField.h> #include <ripple/protocol/SField.h>
#include <ripple/protocol/STBase.h> #include <ripple/protocol/STBase.h>
@@ -30,7 +31,7 @@
namespace ripple { namespace ripple {
class STPathElement class STPathElement final : public CountedObject<STPathElement>
{ {
unsigned int mType; unsigned int mType;
AccountID mAccountID; AccountID mAccountID;
@@ -114,7 +115,7 @@ private:
get_hash(STPathElement const& element); get_hash(STPathElement const& element);
}; };
class STPath class STPath final : public CountedObject<STPath>
{ {
std::vector<STPathElement> mPath; std::vector<STPathElement> mPath;
@@ -172,7 +173,7 @@ public:
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// A set of zero or more payment paths // A set of zero or more payment paths
class STPathSet final : public STBase class STPathSet final : public STBase, public CountedObject<STPathSet>
{ {
std::vector<STPath> value; std::vector<STPath> value;

View File

@@ -51,25 +51,25 @@ doPathFind(RPC::JsonContext& context)
if (sSubCommand == "create") if (sSubCommand == "create")
{ {
context.loadType = Resource::feeHighBurdenRPC; context.loadType = Resource::feeHighBurdenRPC;
context.infoSub->clearPathRequest(); context.infoSub->clearRequest();
return context.app.getPathRequests().makePathRequest( return context.app.getPathRequests().makePathRequest(
context.infoSub, lpLedger, context.params); context.infoSub, lpLedger, context.params);
} }
if (sSubCommand == "close") if (sSubCommand == "close")
{ {
PathRequest::pointer request = context.infoSub->getPathRequest(); InfoSubRequest::pointer request = context.infoSub->getRequest();
if (!request) if (!request)
return rpcError(rpcNO_PF_REQUEST); return rpcError(rpcNO_PF_REQUEST);
context.infoSub->clearPathRequest(); context.infoSub->clearRequest();
return request->doClose(context.params); return request->doClose();
} }
if (sSubCommand == "status") if (sSubCommand == "status")
{ {
PathRequest::pointer request = context.infoSub->getPathRequest(); InfoSubRequest::pointer request = context.infoSub->getRequest();
if (!request) if (!request)
return rpcError(rpcNO_PF_REQUEST); return rpcError(rpcNO_PF_REQUEST);

View File

@@ -223,7 +223,8 @@ checkPayment(
if (auto ledger = app.openLedger().current()) if (auto ledger = app.openLedger().current())
{ {
Pathfinder pf( Pathfinder pf(
std::make_shared<RippleLineCache>(ledger), std::make_shared<RippleLineCache>(
ledger, app.journal("RippleLineCache")),
srcAddressID, srcAddressID,
*dstAccountID, *dstAccountID,
sendMax.issue().currency, sendMax.issue().currency,

View File

@@ -0,0 +1,105 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2022 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.
*/
//==============================================================================
#include <test/jtx/Account.h>
#include <ripple/basics/join.h>
#include <ripple/beast/unit_test.h>
namespace ripple {
namespace test {
struct join_test : beast::unit_test::suite
{
void
run() override
{
auto test = [this](auto collectionanddelimiter, std::string expected) {
std::stringstream ss;
// Put something else in the buffer before and after to ensure that
// the << operator returns the stream correctly.
ss << "(" << collectionanddelimiter << ")";
auto const str = ss.str();
BEAST_EXPECT(str.substr(1, str.length() - 2) == expected);
BEAST_EXPECT(str.front() == '(');
BEAST_EXPECT(str.back() == ')');
};
// C++ array
test(
CollectionAndDelimiter(std::array<int, 4>{2, -1, 5, 10}, "/"),
"2/-1/5/10");
// One item C++ array edge case
test(
CollectionAndDelimiter(std::array<std::string, 1>{"test"}, " & "),
"test");
// Empty C++ array edge case
test(CollectionAndDelimiter(std::array<int, 0>{}, ","), "");
{
// C-style array
char letters[4]{'w', 'a', 's', 'd'};
test(CollectionAndDelimiter(letters, std::to_string(0)), "w0a0s0d");
}
{
// Auto sized C-style array
std::string words[]{"one", "two", "three", "four"};
test(CollectionAndDelimiter(words, "\n"), "one\ntwo\nthree\nfour");
}
{
// One item C-style array edge case
std::string words[]{"thing"};
test(CollectionAndDelimiter(words, "\n"), "thing");
}
// Initializer list
test(
CollectionAndDelimiter(std::initializer_list<size_t>{19, 25}, "+"),
"19+25");
// vector
test(
CollectionAndDelimiter(std::vector<int>{0, 42}, std::to_string(99)),
"09942");
{
// vector with one item edge case
using namespace jtx;
test(
CollectionAndDelimiter(
std::vector<Account>{Account::master}, "xxx"),
Account::master.human());
}
// empty vector edge case
test(CollectionAndDelimiter(std::vector<uint256>{}, ","), "");
// C-style string
test(CollectionAndDelimiter("string", " "), "s t r i n g");
// Empty C-style string edge case
test(CollectionAndDelimiter("", "*"), "");
// Single char C-style string edge case
test(CollectionAndDelimiter("x", "*"), "x");
// std::string
test(CollectionAndDelimiter(std::string{"string"}, "-"), "s-t-r-i-n-g");
// Empty std::string edge case
test(CollectionAndDelimiter(std::string{""}, "*"), "");
// Single char std::string edge case
test(CollectionAndDelimiter(std::string{"y"}, "*"), "y");
}
}; // namespace test
BEAST_DEFINE_TESTSUITE(join, ripple_basics, ripple);
} // namespace test
} // namespace ripple

View File

@@ -33,7 +33,8 @@ paths::operator()(Env& env, JTx& jt) const
auto const to = env.lookup(jv[jss::Destination].asString()); auto const to = env.lookup(jv[jss::Destination].asString());
auto const amount = amountFromJson(sfAmount, jv[jss::Amount]); auto const amount = amountFromJson(sfAmount, jv[jss::Amount]);
Pathfinder pf( Pathfinder pf(
std::make_shared<RippleLineCache>(env.current()), std::make_shared<RippleLineCache>(
env.current(), env.app().journal("RippleLineCache")),
from, from,
to, to,
in_.currency, in_.currency,

View File

@@ -35,6 +35,9 @@ class GetCounts_test : public beast::unit_test::suite
Json::Value result; Json::Value result;
{ {
using namespace std::chrono_literals;
// Add a little delay so the App's "uptime" will have a value.
std::this_thread::sleep_for(1s);
// check counts with no transactions posted // check counts with no transactions posted
result = env.rpc("get_counts")[jss::result]; result = env.rpc("get_counts")[jss::result];
BEAST_EXPECT(result[jss::status] == "success"); BEAST_EXPECT(result[jss::status] == "success");