I am using boost::asio::ip::udp::resolver in an SNMPV2 implementation to determine wheather a host is reachable or not.
using Resolver = boost::asio::ip::udp::resolver;
Resolver resolver(ioService);
Resolver::query query(connectOptions.getHost(),
connectOptions.getPort());
Resolver::iterator endpointIterator;
BOOST_LOG_SEV(logger, Severity::debug) << "Waiting for async resolve";
endpointIterator = resolver.async_resolve(query, yield);
BOOST_LOG_SEV(logger, Severity::debug) << "Async resolve done";
if (endpointIterator == Resolver::iterator{}) { // unreachable host
using namespace boost::system;
throw system_error{error_code{SnmpWrapperError::BadHostname}};
}
I have one test case where I test what is happening when a non existent hostname and an exitent hostname is queried paralell:
2013-09-16 10:45:28.687001: [DEBUG ] 0x88baf8 SnmpConnection: connect
2013-09-16 10:45:28.687396: [DEBUG ] 0x88baf8 SnmpConnection: host: non_existent_host_name_
2013-09-16 10:45:28.687434: [DEBUG ] 0x88baf8 SnmpConnection: port: 1611
2013-09-16 10:45:28.687456: [DEBUG ] 0x88baf8 SnmpConnection: Waiting for async resolve
2013-09-16 10:45:28.687675: [DEBUG ] 0x88c608 SnmpConnection: connect
2013-09-16 10:45:28.687853: [DEBUG ] 0x88c608 SnmpConnection: host: 127.0.0.1
2013-09-16 10:45:28.687883: [DEBUG ] 0x88c608 SnmpConnection: port: 1611
2013-09-16 10:45:28.687904: [DEBUG ] 0x88c608 SnmpConnection: Waiting for async resolve
2013-09-16 10:45:31.113527: [ERROR ] 0x88baf8 SnmpConnection: Host not found (authoritative)
2013-09-16 10:45:31.113708: [DEBUG ] 0x88c608 SnmpConnection: Async resolve done
2013-09-16 10:45:31.113738: [DEBUG ] 0x88c608 SnmpConnection: Connecting to 127.0.0.1:1611
...
From the logs it can be seen that the object with the reachable address is blocked until the other one's resolve is finished with the error (3 seconds).
My assumption is that Asio resolver service uses one thread, so one query towards one unreachable host might block the processing of upcoming resolve requests.
Workaround would be to run the resolver service on more threads, is that possible? Or would it be possible to have a resolver service which works on sockets like the udp service does (instead of using ::getaddrinfo)?
What you need are two io_service ioService
, because each one is run by a thread. By that I mean that you block the normal execution of a thread by calling io_service::run
.
I think that the code itself is correct.
As noted in the documentation, Boost.Asio will create an an additional thread per io_service
to emulate asynchronous host resolution on the first call to resolver::async_resolve()
.
Creating multiple io_service
objects will allow for concurrent host resolution only when initiating asynchronous resolution operations on resolver
s associated with different io_service
s. For example, the following code will not perform concurrent host resolution, as both resolvers use the same service:
boost::asio::io_service service1;
boost::asio::ip::udp::resolver resolver1(service1); // using service1
boost::asio::ip::udp::resolver resolver2(service1); // using service1
resolver1.async_resolve(...);
resolver2.async_resolve(...);
On the other hand, the following will perform concurrent host resolution, as each resolver is using a different service:
boost::asio::io_service service1;
boost::asio::io_service service2;
boost::asio::ip::udp::resolver resolver1(service1); // using service1
boost::asio::ip::udp::resolver resolver2(service2); // using service2
resolver1.async_resolve(...);
resolver2.async_resolve(...);
Assuming a resolver
per io_service
, to obtain concurrency, it becomes the applications responsibility to dispatch resolution operations to different resolvers. A simple work distribution strategy, such as round-robin, may suffice.
On the other hand, one can delegate this responsibility to an io_service
, allowing it to distribute work that will emulate asynchronous host resolution in a similar manner to what Boost.Asio does internally. The synchronous resolver::resolve()
member function performs work in the calling thread. Thus, an application could create an io_service
that is serviced by a thread pool. When asynchronous host resolution needs to occur, a job is posted into the io_service
that will create create a resolver
and perform synchronous resolution, invoking the user handler with the results. Below is a complete basic example where a resolver
class emulates asynchronous host resolution with a thread pool:
#include <iostream>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/optional.hpp>
#include <boost/thread.hpp>
/// @brief Type used to emulate asynchronous host resolution with a
/// dedicated thread pool.
class resolver
{
public:
resolver(const std::size_t pool_size)
: work_(boost::ref(io_service_))
{
// Create pool.
for (std::size_t i = 0; i < pool_size; ++i)
threads_.create_thread(
boost::bind(&boost::asio::io_service::run, &io_service_));
}
~resolver()
{
work_ = boost::none;
threads_.join_all();
}
template <typename QueryOrEndpoint, typename Handler>
void async_resolve(QueryOrEndpoint query, Handler handler)
{
io_service_.post(boost::bind(
&resolver::do_async_resolve<QueryOrEndpoint, Handler>, this,
query, handler));
}
private:
/// @brief Resolve address and invoke continuation handler.
template <typename QueryOrEndpoint, typename Handler>
void do_async_resolve(const QueryOrEndpoint& query, Handler handler)
{
typedef typename QueryOrEndpoint::protocol_type protocol_type;
typedef typename protocol_type::resolver resolver_type;
// Resolve synchronously, as synchronous resolution will perform work
// in the calling thread. Thus, it will not use Boost.Asio's internal
// thread that is used for asynchronous resolution.
boost::system::error_code error;
resolver_type resolver(io_service_);
typename resolver_type::iterator result = resolver.resolve(query, error);
// Invoke user handler.
handler(error, result);
}
private:
boost::asio::io_service io_service_;
boost::optional<boost::asio::io_service::work> work_;
boost::thread_group threads_;
};
template <typename ProtocolType>
void handle_resolve(
const boost::system::error_code& error,
typename ProtocolType::resolver::iterator iterator)
{
std::stringstream stream;
stream << "handle_resolve:\n"
" " << error.message() << "\n";
if (!error)
stream << " " << iterator->endpoint() << "\n";
std::cout << stream.str();
std::cout.flush();
}
int main()
{
// Resolver will emulate asynchronous host resolution with a pool of 5
// threads.
resolver resolver(5);
namespace ip = boost::asio::ip;
resolver.async_resolve(
ip::udp::resolver::query("localhost", "12345"),
&handle_resolve<ip::udp>);
resolver.async_resolve(
ip::tcp::resolver::query("www.google.com", "80"),
&handle_resolve<ip::tcp>);
resolver.async_resolve(
ip::udp::resolver::query("www.stackoverflow.com", "80"),
&handle_resolve<ip::udp>);
resolver.async_resolve(
ip::icmp::resolver::query("some.other.address", "54321"),
&handle_resolve<ip::icmp>);
}
And annotated output:
handle_resolve:
Success
127.0.0.1:12345 // localhost
handle_resolve:
Service not found // bogus
handle_resolve:
Success
173.194.77.147:80 // google
handle_resolve:
Success
198.252.206.16:80 // stackoverflow