Json::Value doPathFind (RPC::Context& context) { auto lpLedger = context.ledgerMaster.getClosedLedger(); if (!context.params.isMember (jss::subcommand) || !context.params[jss::subcommand].isString ()) { return rpcError (rpcINVALID_PARAMS); } if (!context.infoSub) return rpcError (rpcNO_EVENTS); auto sSubCommand = context.params[jss::subcommand].asString (); if (sSubCommand == "create") { context.loadType = Resource::feeHighBurdenRPC; context.infoSub->clearPathRequest (); return context.app.getPathRequests().makePathRequest ( context.infoSub, lpLedger, context.params); } if (sSubCommand == "close") { PathRequest::pointer request = context.infoSub->getPathRequest (); if (!request) return rpcError (rpcNO_PF_REQUEST); context.infoSub->clearPathRequest (); return request->doClose (context.params); } if (sSubCommand == "status") { PathRequest::pointer request = context.infoSub->getPathRequest (); if (!request) return rpcError (rpcNO_PF_REQUEST); return request->doStatus (context.params); } return rpcError (rpcINVALID_PARAMS); }
Json::Value PathRequests::makePathRequest( boost::shared_ptr <InfoSub> const& subscriber, const boost::shared_ptr<Ledger>& inLedger, const Json::Value& requestJson) { PathRequest::pointer req = boost::make_shared<PathRequest> ( subscriber, ++mLastIdentifier, *this, mJournal); Ledger::pointer ledger = inLedger; RippleLineCache::pointer cache; { ScopedLockType sl (mLock); cache = getLineCache (ledger, false); } bool valid = false; Json::Value result = req->doCreate (ledger, cache, requestJson, valid); if (valid) { { ScopedLockType sl (mLock); // Insert after any older unserviced requests but before any serviced requests std::vector<PathRequest::wptr>::iterator it = mRequests.begin (); while (it != mRequests.end ()) { PathRequest::pointer req = it->lock (); if (req && !req->isNew ()) break; // This request has been handled, we come before it // This is a newer request, we come after it ++it; } mRequests.insert (it, PathRequest::wptr (req)); } subscriber->setPathRequest (req); getApp().getLedgerMaster().newPathRequest(); } return result; }
void PathRequests::updateAll (Ledger::ref inLedger, CancelCallback shouldCancel) { std::vector<PathRequest::wptr> requests; LoadEvent::autoptr event (getApp().getJobQueue().getLoadEventAP(jtPATH_FIND, "PathRequest::updateAll")); // Get the ledger and cache we should be using Ledger::pointer ledger = inLedger; RippleLineCache::pointer cache; { ScopedLockType sl (mLock); requests = mRequests; cache = getLineCache (ledger, true); } bool newRequests = getApp().getLedgerMaster().isNewPathRequest(); bool mustBreak = false; mJournal.trace << "updateAll seq=" << ledger->getLedgerSeq() << ", " << requests.size() << " requests"; int processed = 0, removed = 0; do { BOOST_FOREACH (PathRequest::wref wRequest, requests) { if (shouldCancel()) break; bool remove = true; PathRequest::pointer pRequest = wRequest.lock (); if (pRequest) { if (!pRequest->needsUpdate (newRequests, ledger->getLedgerSeq ())) remove = false; else { InfoSub::pointer ipSub = pRequest->getSubscriber (); if (ipSub) { ipSub->getConsumer ().charge (Resource::feePathFindUpdate); if (!ipSub->getConsumer ().warn ()) { Json::Value update = pRequest->doUpdate (cache, false); pRequest->updateComplete (); update["type"] = "path_find"; ipSub->send (update, false); remove = false; ++processed; } } } } if (remove) { PathRequest::pointer pRequest = wRequest.lock (); ScopedLockType sl (mLock); // Remove any dangling weak pointers or weak pointers that refer to this path request. std::vector<PathRequest::wptr>::iterator it = mRequests.begin(); while (it != mRequests.end()) { PathRequest::pointer itRequest = it->lock (); if (!itRequest || (itRequest == pRequest)) { ++removed; it = mRequests.erase (it); } else ++it; } } mustBreak = !newRequests && getApp().getLedgerMaster().isNewPathRequest(); if (mustBreak) // We weren't handling new requests and then there was a new request break; } if (mustBreak) { // a new request came in while we were working newRequests = true; } else if (newRequests) { // we only did new requests, so we always need a last pass newRequests = getApp().getLedgerMaster().isNewPathRequest(); } else { // check if there are any new requests, otherwise we are done newRequests = getApp().getLedgerMaster().isNewPathRequest(); if (!newRequests) // We did a full pass and there are no new requests return; } { // Get the latest requests, cache, and ledger for next pass ScopedLockType sl (mLock); if (mRequests.empty()) break; requests = mRequests; cache = getLineCache (ledger, false); } } while (!shouldCancel ()); mJournal.debug << "updateAll complete " << processed << " process and " << removed << " removed"; }
// This interface is deprecated. Json::Value doRipplePathFind (RPC::Context& context) { RPC::LegacyPathFind lpf (context.role == Role::ADMIN); if (!lpf.isOk ()) return rpcError (rpcTOO_BUSY); context.loadType = Resource::feeHighBurdenRPC; AccountID raSrc; AccountID raDst; STAmount saDstAmount; Ledger::pointer lpLedger; Json::Value jvResult; if (! context.suspend || getConfig().RUN_STANDALONE || context.params.isMember(jss::ledger) || context.params.isMember(jss::ledger_index) || context.params.isMember(jss::ledger_hash)) { // The caller specified a ledger jvResult = RPC::lookupLedgerDeprecated (lpLedger, context); if (!lpLedger) return jvResult; } else { if (getApp().getLedgerMaster().getValidatedLedgerAge() > RPC::Tuning::maxValidatedLedgerAge) { return rpcError (rpcNO_NETWORK); } context.loadType = Resource::feeHighBurdenRPC; lpLedger = context.ledgerMaster.getClosedLedger(); PathRequest::pointer request; suspend(context, [&request, &context, &jvResult, &lpLedger] (RPC::Callback const& callback) { jvResult = getApp().getPathRequests().makeLegacyPathRequest ( request, callback, lpLedger, context.params); assert(callback); if (! request && callback) callback(); }); if (request) jvResult = request->doStatus (context.params); return jvResult; } if (!context.params.isMember (jss::source_account)) { jvResult = rpcError (rpcSRC_ACT_MISSING); } else if (! deprecatedParseBase58(raSrc, context.params[jss::source_account])) { jvResult = rpcError (rpcSRC_ACT_MALFORMED); } else if (!context.params.isMember (jss::destination_account)) { jvResult = rpcError (rpcDST_ACT_MISSING); } else if (! deprecatedParseBase58 (raDst, context.params[jss::destination_account])) { jvResult = rpcError (rpcDST_ACT_MALFORMED); } else if ( // Parse saDstAmount. !context.params.isMember (jss::destination_amount) || ! amountFromJsonNoThrow(saDstAmount, context.params[jss::destination_amount]) || saDstAmount <= zero || (!isXRP(saDstAmount.getCurrency ()) && (!saDstAmount.getIssuer () || noAccount() == saDstAmount.getIssuer ()))) { WriteLog (lsINFO, RPCHandler) << "Bad destination_amount."; jvResult = rpcError (rpcINVALID_PARAMS); } else if ( // Checks on source_currencies. context.params.isMember (jss::source_currencies) && (!context.params[jss::source_currencies].isArray () || !context.params[jss::source_currencies].size ()) // Don't allow empty currencies. ) { WriteLog (lsINFO, RPCHandler) << "Bad source_currencies."; jvResult = rpcError (rpcINVALID_PARAMS); } else { context.loadType = Resource::feeHighBurdenRPC; RippleLineCache::pointer cache; if (lpLedger) { // The caller specified a ledger lpLedger = std::make_shared<Ledger> (std::ref (*lpLedger), false); cache = std::make_shared<RippleLineCache>(lpLedger); } else { // The closed ledger is recent and any nodes made resident // have the best chance to persist lpLedger = context.ledgerMaster.getClosedLedger(); cache = getApp().getPathRequests().getLineCache(lpLedger, false); } Json::Value jvSrcCurrencies; if (context.params.isMember (jss::source_currencies)) { jvSrcCurrencies = context.params[jss::source_currencies]; } else { jvSrcCurrencies = buildSrcCurrencies(raSrc, cache); } // Fill in currencies destination will accept Json::Value jvDestCur (Json::arrayValue); // TODO(tom): this could be optimized the same way that // PathRequest::doUpdate() is - if we don't obsolete this code first. auto usDestCurrID = accountDestCurrencies (raDst, cache, true); for (auto const& uCurrency: usDestCurrID) jvDestCur.append (to_string (uCurrency)); jvResult[jss::destination_currencies] = jvDestCur; jvResult[jss::destination_account] = getApp().accountIDCache().toBase58(raDst); int level = getConfig().PATH_SEARCH_OLD; if ((getConfig().PATH_SEARCH_MAX > level) && !getApp().getFeeTrack().isLoadedLocal()) { ++level; } if (context.params.isMember(jss::search_depth) && context.params[jss::search_depth].isIntegral()) { int rLev = context.params[jss::search_depth].asInt (); if ((rLev < level) || (context.role == Role::ADMIN)) level = rLev; } auto contextPaths = context.params.isMember(jss::paths) ? boost::optional<Json::Value>(context.params[jss::paths]) : boost::optional<Json::Value>(boost::none); auto pathFindResult = ripplePathFind(cache, raSrc, raDst, saDstAmount, jvSrcCurrencies, contextPaths, level); if (!pathFindResult.first) return pathFindResult.second; // Each alternative differs by source currency. jvResult[jss::alternatives] = pathFindResult.second; } WriteLog (lsDEBUG, RPCHandler) << "ripple_path_find< %s" << jvResult; return jvResult; }
// This interface is deprecated. Json::Value doRipplePathFind (RPC::Context& context) { if (context.app.config().PATH_SEARCH_MAX == 0) return rpcError (rpcNOT_SUPPORTED); context.loadType = Resource::feeHighBurdenRPC; std::shared_ptr <ReadView const> lpLedger; Json::Value jvResult; if (! context.app.config().standalone() && ! context.params.isMember(jss::ledger) && ! context.params.isMember(jss::ledger_index) && ! context.params.isMember(jss::ledger_hash)) { // No ledger specified, use pathfinding defaults // and dispatch to pathfinding engine if (context.app.getLedgerMaster().getValidatedLedgerAge() > RPC::Tuning::maxValidatedLedgerAge) { return rpcError (rpcNO_NETWORK); } PathRequest::pointer request; lpLedger = context.ledgerMaster.getClosedLedger(); // It doesn't look like there's much odd happening here, but you should // be aware this code runs in a JobQueue::Coro, which is a coroutine. // And we may be flipping around between threads. Here's an overview: // // 1. We're running doRipplePathFind() due to a call to // ripple_path_find. doRipplePathFind() is currently running // inside of a JobQueue::Coro using a JobQueue thread. // // 2. doRipplePathFind's call to makeLegacyPathRequest() enqueues the // path-finding request. That request will (probably) run at some // indeterminate future time on a (probably different) JobQueue // thread. // // 3. As a continuation from that path-finding JobQueue thread, the // coroutine we're currently running in (!) is posted to the // JobQueue. Because it is a continuation, that post won't // happen until the path-finding request completes. // // 4. Once the continuation is enqueued, and we have reason to think // the path-finding job is likely to run, then the coroutine we're // running in yield()s. That means it surrenders its thread in // the JobQueue. The coroutine is suspended, but ready to run, // because it is kept resident by a shared_ptr in the // path-finding continuation. // // 5. If all goes well then path-finding runs on a JobQueue thread // and executes its continuation. The continuation posts this // same coroutine (!) to the JobQueue. // // 6. When the JobQueue calls this coroutine, this coroutine resumes // from the line below the coro->yield() and returns the // path-finding result. // // With so many moving parts, what could go wrong? // // Just in terms of the JobQueue refusing to add jobs at shutdown // there are two specific things that can go wrong. // // 1. The path-finding Job queued by makeLegacyPathRequest() might be // rejected (because we're shutting down). // // Fortunately this problem can be addressed by looking at the // return value of makeLegacyPathRequest(). If // makeLegacyPathRequest() cannot get a thread to run the path-find // on, then it returns an empty request. // // 2. The path-finding job might run, but the Coro::post() might be // rejected by the JobQueue (because we're shutting down). // // We handle this case by resuming (not posting) the Coro. // By resuming the Coro, we allow the Coro to run to completion // on the current thread instead of requiring that it run on a // new thread from the JobQueue. // // Both of these failure modes are hard to recreate in a unit test // because they are so dependent on inter-thread timing. However // the failure modes can be observed by synchronously (inside the // rippled source code) shutting down the application. The code to // do so looks like this: // // context.app.signalStop(); // while (! context.app.getJobQueue().jobCounter().joined()) { } // // The first line starts the process of shutting down the app. // The second line waits until no more jobs can be added to the // JobQueue before letting the thread continue. // // May 2017 jvResult = context.app.getPathRequests().makeLegacyPathRequest ( request, [&context] () { // Copying the shared_ptr keeps the coroutine alive up // through the return. Otherwise the storage under the // captured reference could evaporate when we return from // coroCopy->resume(). This is not strictly necessary, but // will make maintenance easier. std::shared_ptr<JobQueue::Coro> coroCopy {context.coro}; if (!coroCopy->post()) { // The post() failed, so we won't get a thread to let // the Coro finish. We'll call Coro::resume() so the // Coro can finish on our thread. Otherwise the // application will hang on shutdown. coroCopy->resume(); } }, context.consumer, lpLedger, context.params); if (request) { context.coro->yield(); jvResult = request->doStatus (context.params); } return jvResult; } // The caller specified a ledger jvResult = RPC::lookupLedger (lpLedger, context); if (! lpLedger) return jvResult; RPC::LegacyPathFind lpf (isUnlimited (context.role), context.app); if (! lpf.isOk ()) return rpcError (rpcTOO_BUSY); auto result = context.app.getPathRequests().doLegacyPathRequest ( context.consumer, lpLedger, context.params); for (auto &fieldName : jvResult.getMemberNames ()) result[fieldName] = std::move (jvResult[fieldName]); return result; }