2

When I call boost::asio::ip::tcp::resolver::async_resolve, my handler receives an ip::tcp::resolver::iterator that iterates through one or more ip::tcp::resolver::entries. What is their lifetime, and what is the handle that keeps them alive?

For example, if I get the first entry and launch a tcp::async_connect to it, then in the async_connect handler, can I iterate to the next entry and launch another async_connect to the next entry (as long as I pass the iterator to the async_connect handler, of course)?

When do the resolver::iterator and resolver::entries get cleaned up? Do I have to do anything special, or just let them go out of scope and not held by any callback closure?

(I understand that I could run through all the resolver::entries in my async_resolve handler and store them in a smart-pointed structure or whatever, so that I control their lifetime, but if asio::ip::tcp::resolver is already handling it, my code will be simpler if I just let it do its job.)

Dave M.
  • 1,496
  • 1
  • 12
  • 30
  • Interesting that you should ask. I answered another question [earlier today](https://stackoverflow.com/a/47046232/85371) where I avoided passing the iterator to another function, out of an abundance of caution. I now know this wasn't required :) – sehe Nov 01 '17 at 08:41

1 Answers1

2

Deconstructing The Question

Iterators have value semantics. So their lifetime is always bound to the lifetime of the surrounding object, or their storage duration (stack for automatic, heap for dynamic and sometimes even others for e.g. static).

I think you want to know about the validity of the iterators instead.

Well, the documentation shows that the iterator category is forward iterator. Forward iterators have the "Multipass guarantee", allowing repeated dereference of copies of iterators, yielding the same result¹.

So, we're halfway: it could still be ok to keep the iterators. However, of course, like with any other [forward] iterator, we have to think of iterator invalidation.

So, the real question boils down to: when are resolver iterators invalidated.

What Do We Know?

The usecase the resolve function fulfills is connection. For connection, the first endpoint that works is enough, so there is no need for it to actually retain a list.

In the spirit of pay-for-what-you-need² it would not make sense for resolver to keep the state around longer than required. On the other hand it would not make sense for the iterator to be in the ForwardIterator category if Multipass weren't supported.

The docs say nothing. We have only one recourse: dive into the code

Dive Into The Code

Some steps below the surface we find: asio/detail/resolver_service.hpp:73

  // Asynchronously resolve a query to a list of entries.
  template <typename Handler>
  void async_resolve(implementation_type& impl,
      const query_type& query, Handler& handler)
  {
    // Allocate and construct an operation to wrap the handler.
    typedef resolve_op<Protocol, Handler> op;
    typename op::ptr p = { boost::asio::detail::addressof(handler),
      boost_asio_handler_alloc_helpers::allocate(
        sizeof(op), handler), 0 };

resolve_op shows that the iterator is created using basic_resolver_iterator.hpp::create

And this leads us to the answer: in line 251

  typedef std::vector<basic_resolver_entry<InternetProtocol> > values_type;
  boost::asio::detail::shared_ptr<values_type> values_;
  std::size_t index_;

So, as long as you a keep a copy of a valid the iterator (not the end iterator) you can keep dereferencing it. It will even keep a copy of the query parameters (host_name and service_name) with each resolver entry. This seems slightly wasteful but I suppose could come in handy when devising a caching scheme.

SUMMARIZING:

  • Q. When are resolver iterators invalidated?
  • A. When the last copy of a valid iterator is destructed.

Which translates "they always remain valid" (if they were ever valid).


¹ as opposed to e.g. input iterators

² In general, C++ implementations obey the zero-overhead principle: What you don’t use, you don’t pay for [The Design and Evolution of C++, 1994]

sehe
  • 374,641
  • 47
  • 450
  • 633
  • 1
    Ah, you found it (`basic_resolver_iterator.hpp:251`)...I had gotten lost in the headers, IPP files & such. And a `shared_ptr` was what I was going to use to keep all the information around as long as I needed it, so clearly I was poised to basically duplicate effort. Chasing a little further, I see that memory is allocated for the information returned by `gethostbyname`, so I assume it is freed properly, and that probably happens when all the valid iterators go away and the `smart_ptr` notices. Thanks! – Dave M. Nov 01 '17 at 16:20