Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

behaviour of std::async(std::launch::deferred) + std::future::then

The idea behind a deferred future (achieved only by calling std::async with std::launch::deferred flag) is that the callback is called only when someone tries to wait or to pull the futuristic value or exception of the future. by then the callback isn't executed.

What happens if I attach a continuation to a deferred future with std::future::then? the deferred future is lost (then invalidates the future) and a new future is returned instead.

In this case, according to the standard, what should happen? is the new future is a deferred future as well? would it just deadlock? this question is not addressed in the latest documentation.

like image 671
David Haim Avatar asked Jul 21 '18 09:07

David Haim


1 Answers

This, in my opinion, seems to be a bug in the TS. Or at least an underdocumented trap.

Here is the text from the TS:

2.3 [futures.unique_future]/6-10

template <class F>
see below then(F&& func);

Requires:

INVOKE(DECAY_COPY (std::forward<F>(func)), std::move(*this)) shall be a valid expression.

Effects:

The function creates a shared state that is associated with the returned future object. Additionally,

When the object's shared state is ready, the continuation INVOKE(DECAY_COPY(std::forward<F>(func)), std::move(*this)) is called on an unspecified thread of execution with the call to DECAY_COPY() being evaluated in the thread that called then.

Any value returned from the continuation is stored as the result in the shared state of the resulting future. Any exception propagated from the execution of the continuation is stored as the exceptional result in the shared state of the resulting future.

Returns:

When result_of_t<decay_t<F>(future<R>)> is future<R2>, for some type R2, the function returns future<R2>. Otherwise, the function returns future<result_of_t<decay_t<F>(future<R>)>>. [ Note: The rule above is referred to as implicit unwrapping. Without this rule, the return type of then taking a callable returning a future<R> would have been future<future<R>>. This rule avoids such nested future objects. The type of f2 below is future<int> and not future<future<int>>:

[ Example:

future<int> f1 = g();
future<int> f2 = f1.then([](future<int> f) {
                    future<int> f3 = h();
                    return f3;
                 });

— end example ]

— end note ]

Postconditions:

valid() == false on the original future. valid() == true on the future returned from then. [ Note: In case of implicit unwrapping, the validity of the future returned from thenfunc cannot be established until after the completion of the continuation. If it is not valid, the resulting future becomes ready with an exception of type std::future_error, with an error condition of std::future_errc::broken_promise. — end note ]

There is no special case for a deferred future task. If that deferred future task is not ready before calling .then, there is no way for it to become ready, so there is no way for the decayed copy of func to be invoked.

The text for shared_future is similar; there, you can still cause the shared_future to become ready after calling .then however.

If this is intended; that .then on a not-ready deferred unique future will result in a return value of a future that can never be made ready -- this should be explicit in the TS/standard. If this is not intended, the standard text needs to be changed.

Note that these changes do not appear in the N4762 draft standard published in 2018.

I am not sure how the standard should fix this; the .then semantics are reasonable for a shared_future but not for a future, and differing semantics would be surprising.

like image 76
Yakk - Adam Nevraumont Avatar answered Nov 13 '22 06:11

Yakk - Adam Nevraumont