2

I am using std::future with boost::asio::async_connect in order to cancel the operation when a timeout occurs, as suggested here: https://stackoverflow.com/a/30428941

However, std::future::wait_for() returns std::future_status::deferred immediately, i.e. the operation hasn't been started yet. conn_result.get() later blocks until a connection error is thrown because the remote host isn't listening. I don't want to rely on that because it could last very long until a socket error is thrown.

How do I wait properly on futures created by boost::asio?

EDIT: SSCCE

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

using boost::asio::ip::tcp;

int main(int argc, char* argv[]) {
    boost::asio::io_service ioservice;
    boost::asio::io_service::work work(ioservice);

    std::thread t([&](){ioservice.run();});

    tcp::resolver resolver(ioservice);
    tcp::resolver::query query("127.0.0.1","27015"); // random unused adress

    tcp::socket socket(ioservice);

    std::future<tcp::resolver::iterator> conn_result = boost::asio::async_connect(socket,resolver.resolve(query),boost::asio::use_future);

    std::cout << "IO Service running: " << (!ioservice.stopped() ? "y":"n") << std::endl;
    auto status = conn_result.wait_for(std::chrono::milliseconds(500));
    if (status == std::future_status::timeout) {
        socket.cancel();
        std::cout << "Timeout" << std::endl;
        return 0;
    } else if(status == std::future_status::deferred) {
        std::cout << "Deferred" << std::endl;
    }
    // If the operation failed, then conn_result.get() will throw a
    // boost::system::system_error.
    try {
        conn_result.get();
    } catch(const boost::system::system_error& e) {
        std::cerr << e.what() << std::endl;
    }


    ioservice.stop();
    t.join();

    return 0;
}
  • Compiler: MSVC2012
  • Boost 1.58
Community
  • 1
  • 1
TheBender
  • 331
  • 3
  • 13
  • 1
    `std::future_status::deferred` doesn't mean the task hasn't started yet, it means it's not going to start until you explicitly call `get()` or `wait()` and then it will run synchronously on the current thread. If the task is supposed to be running asynchronously (as `async_connect` suggests) then Asio should be calling `std::async()` with the `std::launch::async` policy to prevent it being deferred. – Jonathan Wakely Jul 21 '15 at 10:50
  • 1
    @sehe good point, edited. – TheBender Jul 21 '15 at 10:56
  • 1
    @JonathanWakely tasks are normally executed by the `io_service::run()` method, which is running in a separate thread, so I would expect this is being handled. It should be more clear after the edit. Examples with `boost::asio` and `std::future work` in the same way as mine, so I'm kinda confused why above code isn't working. – TheBender Jul 21 '15 at 11:25
  • 2
    Running the code in GDB (after compiling it with GCC) it works perfectly and I see that Asio uses `std::promise not `std::async` so it should be impossible to get a `deferred` status (that can only come from a future created by `std::async()`) so sehe's answer looks correct, it's a bug in the implementation. – Jonathan Wakely Jul 21 '15 at 14:13

1 Answers1

3

This appears to be a bug as answered here by Stephan Lavavej.

I wasn't able to find the original bug, but it's fixed in "the RTM version" (assuming VS2013).

This is affected by internal bug number DevDiv#255669 ": wait_for()/wait_until() don't block". Fortunately, I've received a fix for this from one of our Concurrency Runtime developers, Hong Hong. With my current build of VC11, this works:

With my current build of VC11, this works:

C:\Temp>type meow.cpp
#include <stdio.h>
#include <chrono>
#include <future>
#include <thread>
#include <windows.h>
using namespace std;

long long counter() {
    LARGE_INTEGER li;
    QueryPerformanceCounter(&li);
    return li.QuadPart;
}

long long frequency() {
    LARGE_INTEGER li;
    QueryPerformanceFrequency(&li);
    return li.QuadPart;
}

int main() {
    printf("%02d.%02d.%05d.%02d\n", _MSC_VER / 100, _MSC_VER % 100, _MSC_FULL_VER % 100000, _MSC_BUILD);

    future<int> f = async(launch::async, []() -> int {
        this_thread::sleep_for(chrono::milliseconds(250));

        for (int i = 0; i < 5; ++i) {
            printf("Lambda: %d\n", i);
            this_thread::sleep_for(chrono::seconds(2));
        }

        puts("Lambda: Returning.");
        return 1729;
    });

    for (;;) {
        const auto fs = f.wait_for(chrono::seconds(0));

        if (fs == future_status::deferred) {
            puts("Main thread: future_status::deferred (shouldn't happen, we used launch::async)");
        } else if (fs == future_status::ready) {
            puts("Main thread: future_status::ready");
            break;
        } else if (fs == future_status::timeout) {
            puts("Main thread: future_status::timeout");
        } else {
            puts("Main thread: unknown future_status (UH OH)");
        }

        this_thread::sleep_for(chrono::milliseconds(500));
    }

    const long long start = counter();

    const int n = f.get();

    const long long finish = counter();

    printf("Main thread: f.get() took %f microseconds to return %d.\n",
        (finish - start) * 1000000.0 / frequency(), n);
}


C:\Temp>cl /EHsc /nologo /W4 /MTd meow.cpp
meow.cpp

C:\Temp>meow
17.00.50419.00
Main thread: future_status::timeout
Lambda: 0
Main thread: future_status::timeout
Main thread: future_status::timeout
Main thread: future_status::timeout
Main thread: future_status::timeout
Lambda: 1
Main thread: future_status::timeout
Main thread: future_status::timeout
Main thread: future_status::timeout
Main thread: future_status::timeout
Lambda: 2
Main thread: future_status::timeout
Main thread: future_status::timeout
Main thread: future_status::timeout
Main thread: future_status::timeout
Lambda: 3
Main thread: future_status::timeout
Main thread: future_status::timeout
Main thread: future_status::timeout
Main thread: future_status::timeout
Lambda: 4
Main thread: future_status::timeout
Main thread: future_status::timeout
Main thread: future_status::timeout
Main thread: future_status::timeout
Lambda: Returning.
Main thread: future_status::ready
Main thread: f.get() took 2.303971 microseconds to return 1729.

I inserted timing code to prove that when wait_for() returns ready, f.get() returns instantly without blocking.

Basically, the workaround is to loop while it reports deferred

sehe
  • 374,641
  • 47
  • 450
  • 633