Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

std::future still valid after calling get() (which throws an exception)

According to cppreference, after calling std::future::get:

valid() is false after a call to this method.

Additionally, from cplusplus.com:

Once the shared state is ready, the function unblocks and returns (or throws) releasing its shared state. This makes the future object no longer valid: this member function shall be called once at most for every future shared state.

And under Exception safety:

The function throws the exception stored in the shared state when the provider makes it ready by setting it to an exception. Note that in this case a basic guarantee is offered, with the future object being modified to no longer be a valid future (which is itself a valid state for object of this type, despite its name).

Neither description makes a distinction between a call to get which returns a value versus one that throws an exception regarding the invalidation of the future object.

However, the described behavior is not what I'm seeing with this example code:

#include <chrono>
#include <future>
#include <iostream>
#include <stdexcept>


int foo()
{
    throw std::runtime_error("foo exception");
}


int main()
{
    std::future<int> futInt;

    futInt = std::async(std::launch::async, []() { return foo(); });

    while( !(futInt.valid() && futInt.wait_for(std::chrono::milliseconds(0)) == std::future_status::ready) )
        ;

    if( futInt.valid() )
    {
        int val;
        try
        {
            val = futInt.get();
        }
        catch( const std::exception& e )
        {
            std::cout << e.what() << std::endl;
        }
    }

    if( futInt.valid() )
    {
        std::cout << "This is TOTALLY UNEXPECTED!!!" << std::endl;
    }
    else
    {
        std::cout << "This is expected." << std::endl;
    }

    return 0;
}

The output I see is:

foo exception
This is TOTALLY UNEXPECTED!!!

I'm using Visual Studio Premium 2013, Version 12.0.30501.00 Update 2. Is this a bug with the compiler or, in fact, correct behavior in the case of an exception? I've been unable to find any bug reports concerning this, so wasn't sure if it was expected behavior.

Edit - <future> implementation investigation

Digging into the std::future implementation a bit, the _Associated_state object is marked _Retrieved = true; AFTER checking and throwing the associated exception (if any):

virtual _Ty& _Get_value(bool _Get_only_once)
    {   // return the stored result or throw stored exception
    unique_lock<mutex> _Lock(_Mtx);
    if (_Get_only_once && _Retrieved)
        _Throw_future_error(
            make_error_code(future_errc::future_already_retrieved));
    if (_Exception)
        _Rethrow_future_exception(_Exception);
    _Retrieved = true;
    _Maybe_run_deferred_function(_Lock);
    while (!_Ready)
        _Cond.wait(_Lock);
    if (_Exception)
        _Rethrow_future_exception(_Exception);
    return (_Result);
    }

My guess is that the exception check and _Retrieved = true; should be swapped - the object should immediately be set as retrieved (after the _Get_only_once check) and then all other logic should follow. Ergo, compiler bug.

Edit - workaround

I think the following should suffice in lieu of directly calling get until a fix is implemented:

template<typename T>
T getFromFuture(std::future<T>& fut)
{
    try
    {
        return fut.get();
    }
    catch( ... )
    {
        fut = {};
        throw;
    }
}
like image 829
AlphaXerion Avatar asked Nov 24 '15 17:11

AlphaXerion


1 Answers

I compiled on Linux with gcc 5.2.0 and clang 3.7.0 -- both times with 64 Bit. Running the program always results in

foo exception
This is expected.

It looks to me like Visual 2013 handles this incorrectly. See also:

C++ §30.6.6/16-17

Throws: the stored exception, if an exception was stored in the shared state.

Postcondition: valid() == false.

The postcondition is mentioned after the throws and thus has to always hold, even if an exception is thrown. At least that is my interpretation, though I do not speak standardese.

I guess you should probably try also with Visual Studio 2015 and report a bug if that shows the same handling.

like image 110
mfuchs Avatar answered Oct 28 '22 10:10

mfuchs