rippled
Loading...
Searching...
No Matches
ResolverAsio.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2012, 2013 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 <xrpl/basics/Log.h>
21#include <xrpl/basics/ResolverAsio.h>
22#include <xrpl/beast/net/IPAddressConversion.h>
23#include <xrpl/beast/net/IPEndpoint.h>
24#include <xrpl/beast/utility/instrumentation.h>
25#include <boost/asio.hpp>
26#include <atomic>
27#include <condition_variable>
28#include <deque>
29#include <locale>
30#include <memory>
31#include <mutex>
32
33namespace ripple {
34
39template <class Derived>
41{
42protected:
44 {
45 }
46
47public:
49 {
50 // Destroying the object with I/O pending? Not a clean exit!
51 XRPL_ASSERT(
52 m_pending.load() == 0,
53 "ripple::AsyncObject::~AsyncObject : nothing pending");
54 }
55
61 {
62 public:
63 explicit CompletionCounter(Derived* owner) : m_owner(owner)
64 {
65 ++m_owner->m_pending;
66 }
67
69 : m_owner(other.m_owner)
70 {
71 ++m_owner->m_pending;
72 }
73
75 {
76 if (--m_owner->m_pending == 0)
77 m_owner->asyncHandlersComplete();
78 }
79
81 operator=(CompletionCounter const&) = delete;
82
83 private:
84 Derived* m_owner;
85 };
86
87 void
89 {
90 ++m_pending;
91 }
92
93 void
95 {
96 if (--m_pending == 0)
97 (static_cast<Derived*>(this))->asyncHandlersComplete();
98 }
99
100private:
101 // The number of handlers pending.
103};
104
106 public AsyncObject<ResolverAsioImpl>
107{
108public:
110
112
113 boost::asio::io_service& m_io_service;
114 boost::asio::io_service::strand m_strand;
115 boost::asio::ip::tcp::resolver m_resolver;
116
120
123
124 // Represents a unit of work for the resolver to do
125 struct Work
126 {
129
130 template <class StringSequence>
131 Work(StringSequence const& names_, HandlerType const& handler_)
132 : handler(handler_)
133 {
134 names.reserve(names_.size());
135
137 names_.begin(), names_.end(), std::back_inserter(names));
138 }
139 };
140
142
144 boost::asio::io_service& io_service,
145 beast::Journal journal)
146 : m_journal(journal)
147 , m_io_service(io_service)
148 , m_strand(io_service)
149 , m_resolver(io_service)
151 , m_stop_called(false)
152 , m_stopped(true)
153 {
154 }
155
157 {
158 XRPL_ASSERT(
159 m_work.empty(),
160 "ripple::ResolverAsioImpl::~ResolverAsioImpl : no pending work");
161 XRPL_ASSERT(
162 m_stopped, "ripple::ResolverAsioImpl::~ResolverAsioImpl : stopped");
163 }
164
165 //-------------------------------------------------------------------------
166 // AsyncObject
167 void
169 {
173 }
174
175 //--------------------------------------------------------------------------
176 //
177 // Resolver
178 //
179 //--------------------------------------------------------------------------
180
181 void
182 start() override
183 {
184 XRPL_ASSERT(
185 m_stopped == true, "ripple::ResolverAsioImpl::start : stopped");
186 XRPL_ASSERT(
187 m_stop_called == false,
188 "ripple::ResolverAsioImpl::start : not stopping");
189
190 if (m_stopped.exchange(false) == true)
191 {
192 {
195 }
196 addReference();
197 }
198 }
199
200 void
201 stop_async() override
202 {
203 if (m_stop_called.exchange(true) == false)
204 {
205 m_io_service.dispatch(m_strand.wrap(std::bind(
206 &ResolverAsioImpl::do_stop, this, CompletionCounter(this))));
207
208 JLOG(m_journal.debug()) << "Queued a stop request";
209 }
210 }
211
212 void
213 stop() override
214 {
215 stop_async();
216
217 JLOG(m_journal.debug()) << "Waiting to stop";
219 m_cv.wait(lk, [this] { return m_asyncHandlersCompleted; });
220 lk.unlock();
221 JLOG(m_journal.debug()) << "Stopped";
222 }
223
224 void
225 resolve(std::vector<std::string> const& names, HandlerType const& handler)
226 override
227 {
228 XRPL_ASSERT(
229 m_stop_called == false,
230 "ripple::ResolverAsioImpl::resolve : not stopping");
231 XRPL_ASSERT(
232 !names.empty(),
233 "ripple::ResolverAsioImpl::resolve : names non-empty");
234
235 // TODO NIKB use rvalue references to construct and move
236 // reducing cost.
237 m_io_service.dispatch(m_strand.wrap(std::bind(
239 this,
240 names,
241 handler,
242 CompletionCounter(this))));
243 }
244
245 //-------------------------------------------------------------------------
246 // Resolver
247 void
248 do_stop(CompletionCounter)
249 {
250 XRPL_ASSERT(
251 m_stop_called == true,
252 "ripple::ResolverAsioImpl::do_stop : stopping");
253
254 if (m_stopped.exchange(true) == false)
255 {
256 m_work.clear();
257 m_resolver.cancel();
258
260 }
261 }
262
263 void
265 std::string name,
266 boost::system::error_code const& ec,
267 HandlerType handler,
268 boost::asio::ip::tcp::resolver::iterator iter,
269 CompletionCounter)
270 {
271 if (ec == boost::asio::error::operation_aborted)
272 return;
273
275
276 // If we get an error message back, we don't return any
277 // results that we may have gotten.
278 if (!ec)
279 {
280 while (iter != boost::asio::ip::tcp::resolver::iterator())
281 {
282 addresses.push_back(
284 ++iter;
285 }
286 }
287
288 handler(name, addresses);
289
291 &ResolverAsioImpl::do_work, this, CompletionCounter(this))));
292 }
293
296 {
297 // first attempt to parse as an endpoint (IP addr + port).
298 // If that doesn't succeed, fall back to generic name + port parsing
299
300 if (auto const result = beast::IP::Endpoint::from_string_checked(str))
301 {
302 return make_pair(
303 result->address().to_string(), std::to_string(result->port()));
304 }
305
306 // generic name/port parsing, which doesn't work for
307 // IPv6 addresses in particular because it considers a colon
308 // a port separator
309
310 // Attempt to find the first and last non-whitespace
311 auto const find_whitespace = std::bind(
312 &std::isspace<std::string::value_type>,
313 std::placeholders::_1,
314 std::locale());
315
316 auto host_first =
317 std::find_if_not(str.begin(), str.end(), find_whitespace);
318
319 auto port_last =
320 std::find_if_not(str.rbegin(), str.rend(), find_whitespace).base();
321
322 // This should only happen for all-whitespace strings
323 if (host_first >= port_last)
325
326 // Attempt to find the first and last valid port separators
327 auto const find_port_separator = [](char const c) -> bool {
328 if (std::isspace(static_cast<unsigned char>(c)))
329 return true;
330
331 if (c == ':')
332 return true;
333
334 return false;
335 };
336
337 auto host_last =
338 std::find_if(host_first, port_last, find_port_separator);
339
340 auto port_first =
341 std::find_if_not(host_last, port_last, find_port_separator);
342
343 return make_pair(
344 std::string(host_first, host_last),
345 std::string(port_first, port_last));
346 }
347
348 void
349 do_work(CompletionCounter)
350 {
351 if (m_stop_called == true)
352 return;
353
354 // We don't have any work to do at this time
355 if (m_work.empty())
356 return;
357
358 std::string const name(m_work.front().names.back());
359 HandlerType handler(m_work.front().handler);
360
361 m_work.front().names.pop_back();
362
363 if (m_work.front().names.empty())
364 m_work.pop_front();
365
366 auto const [host, port] = parseName(name);
367
368 if (host.empty())
369 {
370 JLOG(m_journal.error()) << "Unable to parse '" << name << "'";
371
373 &ResolverAsioImpl::do_work, this, CompletionCounter(this))));
374
375 return;
376 }
377
378 boost::asio::ip::tcp::resolver::query query(host, port);
379
380 m_resolver.async_resolve(
381 query,
382 std::bind(
384 this,
385 name,
386 std::placeholders::_1,
387 handler,
388 std::placeholders::_2,
389 CompletionCounter(this)));
390 }
391
392 void
394 std::vector<std::string> const& names,
395 HandlerType const& handler,
396 CompletionCounter)
397 {
398 XRPL_ASSERT(
399 !names.empty(),
400 "ripple::ResolverAsioImpl::do_resolve : names non-empty");
401
402 if (m_stop_called == false)
403 {
404 m_work.emplace_back(names, handler);
405
406 JLOG(m_journal.debug())
407 << "Queued new job with " << names.size() << " tasks. "
408 << m_work.size() << " jobs outstanding.";
409
410 if (m_work.size() > 0)
411 {
414 this,
415 CompletionCounter(this))));
416 }
417 }
418 }
419};
420
421//-----------------------------------------------------------------------------
422
424ResolverAsio::New(boost::asio::io_service& io_service, beast::Journal journal)
425{
426 return std::make_unique<ResolverAsioImpl>(io_service, journal);
427}
428
429//-----------------------------------------------------------------------------
430Resolver::~Resolver() = default;
431} // namespace ripple
T back_inserter(T... args)
T begin(T... args)
T bind(T... args)
static std::optional< Endpoint > from_string_checked(std::string const &s)
Create an Endpoint from a string.
Definition: IPEndpoint.cpp:35
A generic endpoint for log messages.
Definition: Journal.h:59
Stream error() const
Definition: Journal.h:335
Stream debug() const
Definition: Journal.h:317
RAII container that maintains the count of pending I/O.
CompletionCounter(CompletionCounter const &other)
CompletionCounter & operator=(CompletionCounter const &)=delete
Mix-in to track when all pending I/O is complete.
std::atomic< int > m_pending
ResolverAsioImpl(boost::asio::io_service &io_service, beast::Journal journal)
std::atomic< bool > m_stopped
void do_work(CompletionCounter)
boost::asio::io_service & m_io_service
void resolve(std::vector< std::string > const &names, HandlerType const &handler) override
void stop() override
Issue a synchronous stop request.
std::condition_variable m_cv
boost::asio::ip::tcp::resolver m_resolver
std::pair< std::string, std::string > HostAndPort
void do_stop(CompletionCounter)
std::deque< Work > m_work
void start() override
Issue a synchronous start request.
std::atomic< bool > m_stop_called
void stop_async() override
Issue an asynchronous stop request.
boost::asio::io_service::strand m_strand
void do_finish(std::string name, boost::system::error_code const &ec, HandlerType handler, boost::asio::ip::tcp::resolver::iterator iter, CompletionCounter)
HostAndPort parseName(std::string const &str)
void do_resolve(std::vector< std::string > const &names, HandlerType const &handler, CompletionCounter)
static std::unique_ptr< ResolverAsio > New(boost::asio::io_service &, beast::Journal)
virtual ~Resolver()=0
T empty(T... args)
T end(T... args)
T exchange(T... args)
T find_if_not(T... args)
T load(T... args)
T make_pair(T... args)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
T push_back(T... args)
T rbegin(T... args)
T rend(T... args)
T reserve(T... args)
T reverse_copy(T... args)
T size(T... args)
static IP::Endpoint from_asio(boost::asio::ip::address const &address)
std::vector< std::string > names
Work(StringSequence const &names_, HandlerType const &handler_)
T to_string(T... args)