Reputation: 16725
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
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 setio_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
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
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