rippled
RipplePathFind.cpp
1 //------------------------------------------------------------------------------
2 /*
3  This file is part of rippled: https://github.com/ripple/rippled
4  Copyright (c) 2012-2014 Ripple Labs Inc.
5 
6  Permission to use, copy, modify, and/or distribute this software for any
7  purpose with or without fee is hereby granted, provided that the above
8  copyright notice and this permission notice appear in all copies.
9 
10  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18 //==============================================================================
19 
20 #include <ripple/app/ledger/LedgerMaster.h>
21 #include <ripple/app/paths/PathRequests.h>
22 #include <ripple/net/RPCErr.h>
23 #include <ripple/resource/Fees.h>
24 #include <ripple/rpc/Context.h>
25 #include <ripple/rpc/impl/LegacyPathFind.h>
26 #include <ripple/rpc/impl/RPCHelpers.h>
27 
28 namespace ripple {
29 
30 // This interface is deprecated.
33 {
34  if (context.app.config().PATH_SEARCH_MAX == 0)
35  return rpcError(rpcNOT_SUPPORTED);
36 
38 
40  Json::Value jvResult;
41 
42  if (!context.app.config().standalone() &&
43  !context.params.isMember(jss::ledger) &&
44  !context.params.isMember(jss::ledger_index) &&
45  !context.params.isMember(jss::ledger_hash))
46  {
47  // No ledger specified, use pathfinding defaults
48  // and dispatch to pathfinding engine
49  if (context.app.getLedgerMaster().getValidatedLedgerAge() >
51  {
52  return rpcError(rpcNO_NETWORK);
53  }
54 
55  PathRequest::pointer request;
56  lpLedger = context.ledgerMaster.getClosedLedger();
57 
58  // It doesn't look like there's much odd happening here, but you should
59  // be aware this code runs in a JobQueue::Coro, which is a coroutine.
60  // And we may be flipping around between threads. Here's an overview:
61  //
62  // 1. We're running doRipplePathFind() due to a call to
63  // ripple_path_find. doRipplePathFind() is currently running
64  // inside of a JobQueue::Coro using a JobQueue thread.
65  //
66  // 2. doRipplePathFind's call to makeLegacyPathRequest() enqueues the
67  // path-finding request. That request will (probably) run at some
68  // indeterminate future time on a (probably different) JobQueue
69  // thread.
70  //
71  // 3. As a continuation from that path-finding JobQueue thread, the
72  // coroutine we're currently running in (!) is posted to the
73  // JobQueue. Because it is a continuation, that post won't
74  // happen until the path-finding request completes.
75  //
76  // 4. Once the continuation is enqueued, and we have reason to think
77  // the path-finding job is likely to run, then the coroutine we're
78  // running in yield()s. That means it surrenders its thread in
79  // the JobQueue. The coroutine is suspended, but ready to run,
80  // because it is kept resident by a shared_ptr in the
81  // path-finding continuation.
82  //
83  // 5. If all goes well then path-finding runs on a JobQueue thread
84  // and executes its continuation. The continuation posts this
85  // same coroutine (!) to the JobQueue.
86  //
87  // 6. When the JobQueue calls this coroutine, this coroutine resumes
88  // from the line below the coro->yield() and returns the
89  // path-finding result.
90  //
91  // With so many moving parts, what could go wrong?
92  //
93  // Just in terms of the JobQueue refusing to add jobs at shutdown
94  // there are two specific things that can go wrong.
95  //
96  // 1. The path-finding Job queued by makeLegacyPathRequest() might be
97  // rejected (because we're shutting down).
98  //
99  // Fortunately this problem can be addressed by looking at the
100  // return value of makeLegacyPathRequest(). If
101  // makeLegacyPathRequest() cannot get a thread to run the path-find
102  // on, then it returns an empty request.
103  //
104  // 2. The path-finding job might run, but the Coro::post() might be
105  // rejected by the JobQueue (because we're shutting down).
106  //
107  // We handle this case by resuming (not posting) the Coro.
108  // By resuming the Coro, we allow the Coro to run to completion
109  // on the current thread instead of requiring that it run on a
110  // new thread from the JobQueue.
111  //
112  // Both of these failure modes are hard to recreate in a unit test
113  // because they are so dependent on inter-thread timing. However
114  // the failure modes can be observed by synchronously (inside the
115  // rippled source code) shutting down the application. The code to
116  // do so looks like this:
117  //
118  // context.app.signalStop();
119  // while (! context.app.getJobQueue().jobCounter().joined()) { }
120  //
121  // The first line starts the process of shutting down the app.
122  // The second line waits until no more jobs can be added to the
123  // JobQueue before letting the thread continue.
124  //
125  // May 2017
126  jvResult = context.app.getPathRequests().makeLegacyPathRequest(
127  request,
128  [&context]() {
129  // Copying the shared_ptr keeps the coroutine alive up
130  // through the return. Otherwise the storage under the
131  // captured reference could evaporate when we return from
132  // coroCopy->resume(). This is not strictly necessary, but
133  // will make maintenance easier.
134  std::shared_ptr<JobQueue::Coro> coroCopy{context.coro};
135  if (!coroCopy->post())
136  {
137  // The post() failed, so we won't get a thread to let
138  // the Coro finish. We'll call Coro::resume() so the
139  // Coro can finish on our thread. Otherwise the
140  // application will hang on shutdown.
141  coroCopy->resume();
142  }
143  },
144  context.consumer,
145  lpLedger,
146  context.params);
147  if (request)
148  {
149  context.coro->yield();
150  jvResult = request->doStatus(context.params);
151  }
152 
153  return jvResult;
154  }
155 
156  // The caller specified a ledger
157  jvResult = RPC::lookupLedger(lpLedger, context);
158  if (!lpLedger)
159  return jvResult;
160 
161  RPC::LegacyPathFind lpf(isUnlimited(context.role), context.app);
162  if (!lpf.isOk())
163  return rpcError(rpcTOO_BUSY);
164 
165  auto result = context.app.getPathRequests().doLegacyPathRequest(
166  context.consumer, lpLedger, context.params);
167 
168  for (auto& fieldName : jvResult.getMemberNames())
169  result[fieldName] = std::move(jvResult[fieldName]);
170 
171  return result;
172 }
173 
174 } // namespace ripple
ripple::rpcNO_NETWORK
@ rpcNO_NETWORK
Definition: ErrorCodes.h:66
ripple::PathRequests::doLegacyPathRequest
Json::Value doLegacyPathRequest(Resource::Consumer &consumer, std::shared_ptr< ReadView const > const &inLedger, Json::Value const &request)
Definition: PathRequests.cpp:258
ripple::RPC::JsonContext
Definition: Context.h:52
ripple::rpcNOT_SUPPORTED
@ rpcNOT_SUPPORTED
Definition: ErrorCodes.h:132
std::shared_ptr
STL class.
ripple::doRipplePathFind
Json::Value doRipplePathFind(RPC::JsonContext &)
Definition: RipplePathFind.cpp:32
ripple::RPC::Context::loadType
Resource::Charge & loadType
Definition: Context.h:43
ripple::RPC::Context::ledgerMaster
LedgerMaster & ledgerMaster
Definition: Context.h:45
ripple::PathRequests::makeLegacyPathRequest
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)
Definition: PathRequests.cpp:225
ripple::rpcTOO_BUSY
@ rpcTOO_BUSY
Definition: ErrorCodes.h:56
ripple::RPC::Context::role
Role role
Definition: Context.h:47
ripple::RPC::lookupLedger
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.
Definition: RPCHelpers.cpp:481
ripple::Config::PATH_SEARCH_MAX
int PATH_SEARCH_MAX
Definition: Config.h:150
ripple::RPC::Context::consumer
Resource::Consumer & consumer
Definition: Context.h:46
ripple::Application::getLedgerMaster
virtual LedgerMaster & getLedgerMaster()=0
ripple::Application::config
virtual Config & config()=0
ripple::Config::standalone
bool standalone() const
Definition: Config.h:216
ripple::RPC::Context::app
Application & app
Definition: Context.h:42
ripple::RPC::Context::coro
std::shared_ptr< JobQueue::Coro > coro
Definition: Context.h:48
Json::Value::isMember
bool isMember(const char *key) const
Return true if the object has a member named key.
Definition: json_value.cpp:932
ripple::Application::getPathRequests
virtual PathRequests & getPathRequests()=0
ripple::rpcError
Json::Value rpcError(int iError, Json::Value jvResult)
Definition: RPCErr.cpp:29
ripple::isUnlimited
bool isUnlimited(Role const &role)
ADMIN and IDENTIFIED roles shall have unlimited resources.
Definition: Role.cpp:94
ripple::LedgerMaster::getValidatedLedgerAge
std::chrono::seconds getValidatedLedgerAge()
Definition: LedgerMaster.cpp:283
ripple::LedgerMaster::getClosedLedger
std::shared_ptr< Ledger const > getClosedLedger()
Definition: LedgerMaster.h:83
Json::Value::getMemberNames
Members getMemberNames() const
Return a list of the member names.
Definition: json_value.cpp:948
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::RPC::LegacyPathFind::isOk
bool isOk() const
Definition: LegacyPathFind.h:38
ripple::RPC::JsonContext::params
Json::Value params
Definition: Context.h:63
ripple::Resource::feeHighBurdenRPC
const Charge feeHighBurdenRPC
ripple::RPC::Tuning::maxValidatedLedgerAge
constexpr auto maxValidatedLedgerAge
Definition: rpc/impl/Tuning.h:59
ripple::RPC::LegacyPathFind
Definition: LegacyPathFind.h:31
Json::Value
Represents a JSON value.
Definition: json_value.h:145