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