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/Resolver.h>
22#include <xrpl/basics/ResolverAsio.h>
23#include <xrpl/beast/net/IPAddressConversion.h>
24#include <xrpl/beast/net/IPEndpoint.h>
25#include <xrpl/beast/utility/Journal.h>
26#include <xrpl/beast/utility/instrumentation.h>
27
28#include <boost/asio/bind_executor.hpp>
29#include <boost/asio/error.hpp>
30#include <boost/asio/io_context.hpp>
31#include <boost/asio/ip/tcp.hpp>
32#include <boost/system/detail/error_code.hpp>
33
34#include <algorithm>
35#include <atomic>
36#include <cctype>
37#include <condition_variable>
38#include <deque>
39#include <functional>
40#include <iterator>
41#include <locale>
42#include <memory>
43#include <mutex>
44#include <string>
45#include <utility>
46#include <vector>
47
48namespace ripple {
49
54template <class Derived>
56{
57protected:
59 {
60 }
61
62public:
64 {
65 // Destroying the object with I/O pending? Not a clean exit!
66 XRPL_ASSERT(
67 m_pending.load() == 0,
68 "ripple::AsyncObject::~AsyncObject : nothing pending");
69 }
70
76 {
77 public:
78 explicit CompletionCounter(Derived* owner) : m_owner(owner)
79 {
80 ++m_owner->m_pending;
81 }
82
84 : m_owner(other.m_owner)
85 {
86 ++m_owner->m_pending;
87 }
88
90 {
91 if (--m_owner->m_pending == 0)
92 m_owner->asyncHandlersComplete();
93 }
94
96 operator=(CompletionCounter const&) = delete;
97
98 private:
99 Derived* m_owner;
100 };
101
102 void
104 {
105 ++m_pending;
106 }
107
108 void
110 {
111 if (--m_pending == 0)
112 (static_cast<Derived*>(this))->asyncHandlersComplete();
113 }
114
115private:
116 // The number of handlers pending.
118};
119
121 public AsyncObject<ResolverAsioImpl>
122{
123public:
125
127
128 boost::asio::io_context& m_io_context;
129 boost::asio::strand<boost::asio::io_context::executor_type> m_strand;
130 boost::asio::ip::tcp::resolver m_resolver;
131
135
138
139 // Represents a unit of work for the resolver to do
140 struct Work
141 {
144
145 template <class StringSequence>
146 Work(StringSequence const& names_, HandlerType const& handler_)
147 : handler(handler_)
148 {
149 names.reserve(names_.size());
150
152 names_.begin(), names_.end(), std::back_inserter(names));
153 }
154 };
155
157
159 boost::asio::io_context& io_context,
160 beast::Journal journal)
161 : m_journal(journal)
162 , m_io_context(io_context)
163 , m_strand(boost::asio::make_strand(io_context))
164 , m_resolver(io_context)
166 , m_stop_called(false)
167 , m_stopped(true)
168 {
169 }
170
172 {
173 XRPL_ASSERT(
174 m_work.empty(),
175 "ripple::ResolverAsioImpl::~ResolverAsioImpl : no pending work");
176 XRPL_ASSERT(
177 m_stopped, "ripple::ResolverAsioImpl::~ResolverAsioImpl : stopped");
178 }
179
180 //-------------------------------------------------------------------------
181 // AsyncObject
182 void
189
190 //--------------------------------------------------------------------------
191 //
192 // Resolver
193 //
194 //--------------------------------------------------------------------------
195
196 void
197 start() override
198 {
199 XRPL_ASSERT(
200 m_stopped == true, "ripple::ResolverAsioImpl::start : stopped");
201 XRPL_ASSERT(
202 m_stop_called == false,
203 "ripple::ResolverAsioImpl::start : not stopping");
204
205 if (m_stopped.exchange(false) == true)
206 {
207 {
210 }
211 addReference();
212 }
213 }
214
215 void
216 stop_async() override
217 {
218 if (m_stop_called.exchange(true) == false)
219 {
220 boost::asio::dispatch(
222 boost::asio::bind_executor(
223 m_strand,
224 std::bind(
226 this,
227 CompletionCounter(this))));
228
229 JLOG(m_journal.debug()) << "Queued a stop request";
230 }
231 }
232
233 void
234 stop() override
235 {
236 stop_async();
237
238 JLOG(m_journal.debug()) << "Waiting to stop";
240 m_cv.wait(lk, [this] { return m_asyncHandlersCompleted; });
241 lk.unlock();
242 JLOG(m_journal.debug()) << "Stopped";
243 }
244
245 void
246 resolve(std::vector<std::string> const& names, HandlerType const& handler)
247 override
248 {
249 XRPL_ASSERT(
250 m_stop_called == false,
251 "ripple::ResolverAsioImpl::resolve : not stopping");
252 XRPL_ASSERT(
253 !names.empty(),
254 "ripple::ResolverAsioImpl::resolve : names non-empty");
255
256 // TODO NIKB use rvalue references to construct and move
257 // reducing cost.
258 boost::asio::dispatch(
260 boost::asio::bind_executor(
261 m_strand,
262 std::bind(
264 this,
265 names,
266 handler,
267 CompletionCounter(this))));
268 }
269
270 //-------------------------------------------------------------------------
271 // Resolver
272 void
273 do_stop(CompletionCounter)
274 {
275 XRPL_ASSERT(
276 m_stop_called == true,
277 "ripple::ResolverAsioImpl::do_stop : stopping");
278
279 if (m_stopped.exchange(true) == false)
280 {
281 m_work.clear();
282 m_resolver.cancel();
283
285 }
286 }
287
288 void
290 std::string name,
291 boost::system::error_code const& ec,
292 HandlerType handler,
293 boost::asio::ip::tcp::resolver::results_type results,
294 CompletionCounter)
295 {
296 if (ec == boost::asio::error::operation_aborted)
297 return;
298
300 auto iter = results.begin();
301
302 // If we get an error message back, we don't return any
303 // results that we may have gotten.
304 if (!ec)
305 {
306 while (iter != results.end())
307 {
308 addresses.push_back(
310 ++iter;
311 }
312 }
313
314 handler(name, addresses);
315
316 boost::asio::post(
318 boost::asio::bind_executor(
319 m_strand,
320 std::bind(
322 this,
323 CompletionCounter(this))));
324 }
325
328 {
329 // first attempt to parse as an endpoint (IP addr + port).
330 // If that doesn't succeed, fall back to generic name + port parsing
331
332 if (auto const result = beast::IP::Endpoint::from_string_checked(str))
333 {
334 return make_pair(
335 result->address().to_string(), std::to_string(result->port()));
336 }
337
338 // generic name/port parsing, which doesn't work for
339 // IPv6 addresses in particular because it considers a colon
340 // a port separator
341
342 // Attempt to find the first and last non-whitespace
343 auto const find_whitespace = std::bind(
345 std::placeholders::_1,
346 std::locale());
347
348 auto host_first =
349 std::find_if_not(str.begin(), str.end(), find_whitespace);
350
351 auto port_last =
352 std::find_if_not(str.rbegin(), str.rend(), find_whitespace).base();
353
354 // This should only happen for all-whitespace strings
355 if (host_first >= port_last)
357
358 // Attempt to find the first and last valid port separators
359 auto const find_port_separator = [](char const c) -> bool {
360 if (std::isspace(static_cast<unsigned char>(c)))
361 return true;
362
363 if (c == ':')
364 return true;
365
366 return false;
367 };
368
369 auto host_last =
370 std::find_if(host_first, port_last, find_port_separator);
371
372 auto port_first =
373 std::find_if_not(host_last, port_last, find_port_separator);
374
375 return make_pair(
376 std::string(host_first, host_last),
377 std::string(port_first, port_last));
378 }
379
380 void
381 do_work(CompletionCounter)
382 {
383 if (m_stop_called == true)
384 return;
385
386 // We don't have any work to do at this time
387 if (m_work.empty())
388 return;
389
390 std::string const name(m_work.front().names.back());
391 HandlerType handler(m_work.front().handler);
392
393 m_work.front().names.pop_back();
394
395 if (m_work.front().names.empty())
396 m_work.pop_front();
397
398 auto const [host, port] = parseName(name);
399
400 if (host.empty())
401 {
402 JLOG(m_journal.error()) << "Unable to parse '" << name << "'";
403
404 boost::asio::post(
406 boost::asio::bind_executor(
407 m_strand,
408 std::bind(
410 this,
411 CompletionCounter(this))));
412
413 return;
414 }
415
416 m_resolver.async_resolve(
417 host,
418 port,
419 std::bind(
421 this,
422 name,
423 std::placeholders::_1,
424 handler,
425 std::placeholders::_2,
426 CompletionCounter(this)));
427 }
428
429 void
431 std::vector<std::string> const& names,
432 HandlerType const& handler,
433 CompletionCounter)
434 {
435 XRPL_ASSERT(
436 !names.empty(),
437 "ripple::ResolverAsioImpl::do_resolve : names non-empty");
438
439 if (m_stop_called == false)
440 {
441 m_work.emplace_back(names, handler);
442
443 JLOG(m_journal.debug())
444 << "Queued new job with " << names.size() << " tasks. "
445 << m_work.size() << " jobs outstanding.";
446
447 if (m_work.size() > 0)
448 {
449 boost::asio::post(
451 boost::asio::bind_executor(
452 m_strand,
453 std::bind(
455 this,
456 CompletionCounter(this))));
457 }
458 }
459 }
460};
461
462//-----------------------------------------------------------------------------
463
465ResolverAsio::New(boost::asio::io_context& io_context, beast::Journal journal)
466{
467 return std::make_unique<ResolverAsioImpl>(io_context, journal);
468}
469
470//-----------------------------------------------------------------------------
471Resolver::~Resolver() = default;
472} // 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.
A generic endpoint for log messages.
Definition Journal.h:60
Stream error() const
Definition Journal.h:346
Stream debug() const
Definition Journal.h:328
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
std::atomic< bool > m_stopped
boost::asio::strand< boost::asio::io_context::executor_type > m_strand
void do_work(CompletionCounter)
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
ResolverAsioImpl(boost::asio::io_context &io_context, beast::Journal journal)
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.
HostAndPort parseName(std::string const &str)
boost::asio::io_context & m_io_context
void do_finish(std::string name, boost::system::error_code const &ec, HandlerType handler, boost::asio::ip::tcp::resolver::results_type results, CompletionCounter)
void do_resolve(std::vector< std::string > const &names, HandlerType const &handler, CompletionCounter)
static std::unique_ptr< ResolverAsio > New(boost::asio::io_context &, beast::Journal)
virtual ~Resolver()=0
T empty(T... args)
T end(T... args)
T exchange(T... args)
T find_if_not(T... args)
T is_same_v
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:25
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)