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