rippled
Loading...
Searching...
No Matches
RipplePathFind.cpp
1#include <xrpld/app/ledger/LedgerMaster.h>
2#include <xrpld/app/paths/PathRequests.h>
3#include <xrpld/rpc/Context.h>
4#include <xrpld/rpc/detail/LegacyPathFind.h>
5#include <xrpld/rpc/detail/RPCLedgerHelpers.h>
6
7#include <xrpl/protocol/RPCErr.h>
8#include <xrpl/resource/Fees.h>
9
10namespace ripple {
11
12// This interface is deprecated.
15{
16 if (context.app.config().PATH_SEARCH_MAX == 0)
18
20
22 Json::Value jvResult;
23
24 if (!context.app.config().standalone() &&
25 !context.params.isMember(jss::ledger) &&
26 !context.params.isMember(jss::ledger_index) &&
27 !context.params.isMember(jss::ledger_hash))
28 {
29 // No ledger specified, use pathfinding defaults
30 // and dispatch to pathfinding engine
33 {
34 if (context.apiVersion == 1)
35 return rpcError(rpcNO_NETWORK);
36 return rpcError(rpcNOT_SYNCED);
37 }
38
40 lpLedger = context.ledgerMaster.getClosedLedger();
41
42 // It doesn't look like there's much odd happening here, but you should
43 // be aware this code runs in a JobQueue::Coro, which is a coroutine.
44 // And we may be flipping around between threads. Here's an overview:
45 //
46 // 1. We're running doRipplePathFind() due to a call to
47 // ripple_path_find. doRipplePathFind() is currently running
48 // inside of a JobQueue::Coro using a JobQueue thread.
49 //
50 // 2. doRipplePathFind's call to makeLegacyPathRequest() enqueues the
51 // path-finding request. That request will (probably) run at some
52 // indeterminate future time on a (probably different) JobQueue
53 // thread.
54 //
55 // 3. As a continuation from that path-finding JobQueue thread, the
56 // coroutine we're currently running in (!) is posted to the
57 // JobQueue. Because it is a continuation, that post won't
58 // happen until the path-finding request completes.
59 //
60 // 4. Once the continuation is enqueued, and we have reason to think
61 // the path-finding job is likely to run, then the coroutine we're
62 // running in yield()s. That means it surrenders its thread in
63 // the JobQueue. The coroutine is suspended, but ready to run,
64 // because it is kept resident by a shared_ptr in the
65 // path-finding continuation.
66 //
67 // 5. If all goes well then path-finding runs on a JobQueue thread
68 // and executes its continuation. The continuation posts this
69 // same coroutine (!) to the JobQueue.
70 //
71 // 6. When the JobQueue calls this coroutine, this coroutine resumes
72 // from the line below the coro->yield() and returns the
73 // path-finding result.
74 //
75 // With so many moving parts, what could go wrong?
76 //
77 // Just in terms of the JobQueue refusing to add jobs at shutdown
78 // there are two specific things that can go wrong.
79 //
80 // 1. The path-finding Job queued by makeLegacyPathRequest() might be
81 // rejected (because we're shutting down).
82 //
83 // Fortunately this problem can be addressed by looking at the
84 // return value of makeLegacyPathRequest(). If
85 // makeLegacyPathRequest() cannot get a thread to run the path-find
86 // on, then it returns an empty request.
87 //
88 // 2. The path-finding job might run, but the Coro::post() might be
89 // rejected by the JobQueue (because we're shutting down).
90 //
91 // We handle this case by resuming (not posting) the Coro.
92 // By resuming the Coro, we allow the Coro to run to completion
93 // on the current thread instead of requiring that it run on a
94 // new thread from the JobQueue.
95 //
96 // Both of these failure modes are hard to recreate in a unit test
97 // because they are so dependent on inter-thread timing. However
98 // the failure modes can be observed by synchronously (inside the
99 // rippled source code) shutting down the application. The code to
100 // do so looks like this:
101 //
102 // context.app.signalStop();
103 // while (! context.app.getJobQueue().jobCounter().joined()) { }
104 //
105 // The first line starts the process of shutting down the app.
106 // The second line waits until no more jobs can be added to the
107 // JobQueue before letting the thread continue.
108 //
109 // May 2017
110 jvResult = context.app.getPathRequests().makeLegacyPathRequest(
111 request,
112 [&context]() {
113 // Copying the shared_ptr keeps the coroutine alive up
114 // through the return. Otherwise the storage under the
115 // captured reference could evaporate when we return from
116 // coroCopy->resume(). This is not strictly necessary, but
117 // will make maintenance easier.
118 std::shared_ptr<JobQueue::Coro> coroCopy{context.coro};
119 if (!coroCopy->post())
120 {
121 // The post() failed, so we won't get a thread to let
122 // the Coro finish. We'll call Coro::resume() so the
123 // Coro can finish on our thread. Otherwise the
124 // application will hang on shutdown.
125 coroCopy->resume();
126 }
127 },
128 context.consumer,
129 lpLedger,
130 context.params);
131 if (request)
132 {
133 context.coro->yield();
134 jvResult = request->doStatus(context.params);
135 }
136
137 return jvResult;
138 }
139
140 // The caller specified a ledger
141 jvResult = RPC::lookupLedger(lpLedger, context);
142 if (!lpLedger)
143 return jvResult;
144
145 RPC::LegacyPathFind lpf(isUnlimited(context.role), context.app);
146 if (!lpf.isOk())
147 return rpcError(rpcTOO_BUSY);
148
149 auto result = context.app.getPathRequests().doLegacyPathRequest(
150 context.consumer, lpLedger, context.params);
151
152 for (auto& fieldName : jvResult.getMemberNames())
153 result[fieldName] = std::move(jvResult[fieldName]);
154
155 return result;
156}
157
158} // namespace ripple
Represents a JSON value.
Definition json_value.h:131
Members getMemberNames() const
Return a list of the member names.
bool isMember(char const *key) const
Return true if the object has a member named key.
virtual Config & config()=0
virtual LedgerMaster & getLedgerMaster()=0
virtual PathRequests & getPathRequests()=0
bool standalone() const
Definition Config.h:317
int PATH_SEARCH_MAX
Definition Config.h:179
std::shared_ptr< Ledger const > getClosedLedger()
std::chrono::seconds getValidatedLedgerAge()
Json::Value makeLegacyPathRequest(PathRequest::pointer &req, std::function< void(void)> completion, Resource::Consumer &consumer, std::shared_ptr< ReadView const > const &inLedger, Json::Value const &request)
Json::Value doLegacyPathRequest(Resource::Consumer &consumer, std::shared_ptr< ReadView const > const &inLedger, Json::Value const &request)
Status lookupLedger(std::shared_ptr< ReadView const > &ledger, JsonContext &context, Json::Value &result)
Look up a ledger from a request and fill a Json::Result with the data representing a ledger.
Charge const feeHeavyBurdenRPC
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
@ rpcNOT_SUPPORTED
Definition ErrorCodes.h:113
@ rpcNO_NETWORK
Definition ErrorCodes.h:47
@ rpcTOO_BUSY
Definition ErrorCodes.h:37
@ rpcNOT_SYNCED
Definition ErrorCodes.h:48
Json::Value rpcError(int iError)
Definition RPCErr.cpp:12
bool isUnlimited(Role const &role)
ADMIN and IDENTIFIED roles shall have unlimited resources.
Definition Role.cpp:106
Json::Value doRipplePathFind(RPC::JsonContext &)
unsigned int apiVersion
Definition Context.h:30
std::shared_ptr< JobQueue::Coro > coro
Definition Context.h:28
Resource::Charge & loadType
Definition Context.h:23
Application & app
Definition Context.h:22
LedgerMaster & ledgerMaster
Definition Context.h:25
Resource::Consumer & consumer
Definition Context.h:26