4

If I want to integrate stuff from boost::asio into an eventloop that is based on file descriptors (select/poll), how can I achieve it? Other libraries with asynchronous functions offer to hand out a file descriptor that becomes readable as soon as there is work to be done, so that you can integrate it into the select/poll of the eventloop and let it call a processing callback of the library (like a single shot event processing).

A great example would be an asynchronous name resolver in a thread pool, like discussed in this question.

Community
  • 1
  • 1
ansiwen
  • 1,061
  • 8
  • 13

2 Answers2

2

Based on the example in this answer I came up with this solution that uses a generic handler, which writes into a wake-up pipe and then posts the handler call into another io_service. The read end of the pipe can be used in a file descriptor based event loop and the callback run_handler() is called from there, which clears the pipe and runs pending handlers in the main thread.

#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(resolver_service_)) {
    // Create wake-up pipe
    pipe(pipe_);
    fcntl(pipe_[0], F_SETFL, O_NONBLOCK);
    // Create pool.
    for (std::size_t i = 0; i < pool_size; ++i)
      threads_.create_thread(boost::bind(&boost::asio::io_service::run,
        &resolver_service_));
  }

  ~resolver() {
    work_ = boost::none;
    threads_.join_all();
  }

  template <typename QueryOrEndpoint, typename Handler>
  void async_resolve(QueryOrEndpoint query, Handler handler) {
    resolver_service_.post(boost::bind(
        &resolver::do_async_resolve<QueryOrEndpoint, Handler>, this,
        query, handler));
  }

  // callback for eventloop in main thread
  void run_handler() {
    char c;
    // clear wake-up pipe
    while (read(pipe_[0], &c, 1) > 0);
    // run handler posted from resolver threads
    handler_service_.poll();
    handler_service_.reset();
  }

  // get read end of wake up pipe for polling in eventloop
  int fd() {
    return pipe_[0]; 
  }

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(resolver_service_);
    typename resolver_type::iterator result = resolver.resolve(query, error);

    // post handler callback to service running in main thread
    handler_service_.post(boost::bind(handler, error, result));
    // wake up eventloop in main thread
    write(pipe_[1], "*", 1);
  }

private:
  boost::asio::io_service resolver_service_;
  boost::asio::io_service handler_service_;
  boost::optional<boost::asio::io_service::work> work_;
  boost::thread_group threads_;
  int pipe_[2];
};

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>);

  pollfd fds;
  fds.fd = resolver.fd();
  fds.events = POLLIN;

  // simple eventloop
  while (true) {
    if (poll(&fds, 1, 2000)) // waiting for wakeup call
      resolver.run_handler(); // call resolve handler
    else
      break;
  }
}
Community
  • 1
  • 1
ansiwen
  • 1,061
  • 8
  • 13
0

Several of the objects in the Boost ASIO library expose a native_handle for scenarios like this.

Sam Miller
  • 23,808
  • 4
  • 67
  • 87
  • But what if there is no or not only a single original file descriptor involved? Like in the example with the pool of resolver threads. – ansiwen Jun 29 '14 at 22:49
  • In the meantime I found out, that you can use another io_service to post the handler-callbacks back to the main thread and use a pipe to "wake up" the fd based eventloop. Will post a solution later. – ansiwen Jun 30 '14 at 08:06