VP.
VP.

Reputation: 16725

Waiting boost asio's future lasts forever after io_service.stop()

I try to wait on a std::future object which is returned from any boost::asio::async_ function (with using use_future). For example:

auto endpoint_future = resolver.async_resolve(query, boost::asio::use_future);
endpoint_future.wait(); // lasts forever after io_service.stop();

This is done in one thread. I have also another thread which is started before this async_resolve call:

runner_ = std::thread([this]() {
    boost::asio::io_service::work work(io_service_);
    io_service_.run();
});

Everything works pretty fine but later I also added a boost::asio::deadline_timer to stop any work with io_service:

void deadlineTimeout() {
    deadline_.cancel();
    io_service_.stop();
    // closing socket here does not work too
}

However, in deadlineTimeout() when deadline reaches it's timeout and it performs io_service_.stop() the future is not getting released so the endpointer_future.wait() still blocks. How can I stop waiting on the future in this case?

Upvotes: 3

Views: 1807

Answers (3)

Tanner Sansbury
Tanner Sansbury

Reputation: 51891

The call to io_sevice.stop() will cause all all invocations of run() and run_one() to return as soon as possible. When invoked from within a handler, the caller will return from run() without invoking any additional handlers. In your case, the async_resolve's completion handler will set the promise associated with endpoint_future; however, by stopping the io_service, the completion handler will not be invoked. Consider either:

  • cancel() the I/O objects associated with the future, then continue running the io_service to completion so that the promise's value gets set
  • destroying all I/O objects, then destroying the io_service so that the handler is deleted and a broken promise is detected by the future
  • loop performing a timed wait on the future, and exit the loop if the future is ready or if the io_service has been stopped. For example, the following function returns a boost::optional with the value of the future or boost::none if the future will not be set.

    template <typename T>
    boost::optional<T> get(
      boost::asio::io_service& io_service,
      std::future<T>& future)
    {
      for (;;)
      {
        // If the future is ready, get the value.
        if (future.wait_for(std::chrono::seconds(1)) == std::future_status::ready)
        {
          return {future.get()};
        }
        // Otherwise, if the future is never going to be set, return none.
        if (io_service.stopped())
        {
          return {boost::none};
        }
      }
    }
    
    ...
    if (auto endpoint_iterator = get(io_service, endpoint_future))
    {
      // use *endpoint_iterator...
    }
    

Below is an example demonstrating how to safely wait on a future while stopping the io_service:

#include <chrono>
#include <iostream>
#include <thread>
#include <boost/asio.hpp>
#include <boost/asio/use_future.hpp>
#include <boost/optional.hpp>

template <typename T>
boost::optional<T> get(
  boost::asio::io_service& io_service,
  std::future<T>& future)
{
  for (;;)
  {
    // If the future is ready, get the value.
    if (future.wait_for(std::chrono::seconds(1)) == std::future_status::ready)
    {
      return {future.get()};
    }
    // Otherwise, if the future is never going to be set, return none.
    if (io_service.stopped())
    {
      std::cout << "io_service stopped, future will not be set" << std::endl;
      return {boost::none};
    }
    std::cout << "timeout waiting for future" << std::endl;
  }
}

int main()
{
  boost::asio::io_service io_service;

  // Create I/O objects.
  boost::asio::ip::udp::socket socket(io_service,
    boost::asio::ip::udp::v4());
  boost::asio::deadline_timer timer(io_service);

  // Process the io_service in the runner thread.
  auto runner = std::thread([&]() {
    boost::asio::io_service::work work(io_service);
    io_service.run();
  });

  // Start an operation that will not complete.
  auto bytes_transferred_future = socket.async_receive(
    boost::asio::null_buffers(), boost::asio::use_future);

  // Arm the timer.
  timer.expires_from_now(boost::posix_time::seconds(2));
  timer.async_wait([&](const boost::system::error_code&) {
    timer.cancel();
    socket.close();
    io_service.stop();
  });

  // bytes_transferred's promise will never be set as the io_service
  // is not running.
  auto bytes_transferred = get(io_service, bytes_transferred_future);
  assert(!bytes_transferred);

  runner.join();
}

Upvotes: 2

Dale Wilson
Dale Wilson

Reputation: 9434

Make the work visible to the deadline timeout and allow the deadline timeout to delete it.

boost::scoped_ptr<boost::asio::io_service::work work;

You can still create the work here, but heap allocate it.

runner_ = std::thread([this]() {
    work = new boost::asio::io_service::work(io_service_);
    io_service_.run();
});

And shut down like this:

void deadlineTimeout() {
    deadline_.cancel();
    socket_.close(); // socket_ is a socket on io_service_
    work.reset();

    // The io_service::run() will exit when there are no active requests 
    // (i.e. no sockeet, no deadline, and no work)
    // so you do not need to call: io_service_.stop();
}

Upvotes: 0

VP.
VP.

Reputation: 16725

Just found a solution by myself: we need not to stop() the io_service but reset() it and before this we need to close the socket, thus the correct timeout callback will be:

void deadlineTimeout() {
    deadline_.cancel();
    socket_.close(); // socket_ is a socket on io_service_
    io_service_.reset();
}

After this change all the futures are released.

Upvotes: 2

Related Questions