Title
Unclear where std::async exceptions are handled
Status
new
Section
[futures.async]
Submitter
Jonathan Wakely

Created on 2021-08-23.00:00:00 last changed 39 months ago

Messages

Date: 2021-09-30.15:57:29

Proposed resolution:

This wording is relative to N4892.

  1. Modify [futures.async] as indicated:

    template<class F, class... Args>
      [[nodiscard]] future<invoke_result_t<decay_t<F>, decay_t<Args>...>>
        async(F&& f, Args&&... args);
    template<class F, class... Args>
      [[nodiscard]] future<invoke_result_t<decay_t<F>, decay_t<Args>...>>
        async(launch policy, F&& f, Args&&... args);
    

    -2- Mandates: […]

    -3- Effects: The first function behaves the same as a call to the second function with a policy argument of launch::async | launch::deferred and the same arguments for F and Args. The second function creates a shared state that is associated with the returned future object. The further behavior of the second function depends on the policy argument as follows (if more than one of these conditions applies, the implementation may choose any of the corresponding policies):

    1. (3.1) — If launch::async is set in policy, calls invoke(decay-copy(std::forward<F>(f)), decay-copy(std::forward<Args>(args))...) ([func.require], [thread.thread.constr]) as if in a new thread of execution represented by a thread object with the calls to decay-copy being evaluated in the thread that called async. Any return value is stored as the result in the shared state. Any exception propagated from the execution of invoke(decay-copy(std::forward<F>(f)), decay-copy(std::forward<Args>(args)...)std::move(g), std::move(xyz)) is stored as the exceptional result in the shared state, where g is the result of decay-copy(std::forward<F>(f)) and xyz is the result of decay-copy(std::forward<Args>(args))... . [Note ?: Exceptions from the decay-copy calls are propagated to the caller. — end note] The thread object is stored in the shared state and affects the behavior of any asynchronous return objects that reference that state.

    2. […]

    […]

    -6- Throws: system_error if policy == launch::async and the implementation is unable to start a new thread; std::bad_alloc if memory for the internal data structures cannot be allocated; or any exception thrown by the initialization of the objects returned by the decay-copy calls.

Date: 2021-09-15.00:00:00

[ 2021-09-20; Jonathan updates wording to change the Throws: and attempt to align the Effects: with the deferred function case. ]

Previous resolution [SUPERSEDED]:

This wording is relative to N4892.

  1. Modify [futures.async] as indicated:

    template<class F, class... Args>
      [[nodiscard]] future<invoke_result_t<decay_t<F>, decay_t<Args>...>>
        async(F&& f, Args&&... args);
    template<class F, class... Args>
      [[nodiscard]] future<invoke_result_t<decay_t<F>, decay_t<Args>...>>
        async(launch policy, F&& f, Args&&... args);
    

    -2- Mandates: […]

    -3- Effects: The first function behaves the same as a call to the second function with a policy argument of launch::async | launch::deferred and the same arguments for F and Args. The second function creates a shared state that is associated with the returned future object. The further behavior of the second function depends on the policy argument as follows (if more than one of these conditions applies, the implementation may choose any of the corresponding policies):

    1. (3.1) — If launch::async is set in policy, calls invoke(decay-copy(std::forward<F>(f)), decay-copy(std::forward<Args>(args))...) ([func.require], [thread.thread.constr]) as if in a new thread of execution represented by a thread object with the calls to decay-copy being evaluated in the thread that called async. Any return value is stored as the result in the shared state. Any exception propagated from the execution of invoke(decay-copy(std::forward<F>(f)), decay-copy(std::forward<Args>(args))...)call to invoke is stored as the exceptional result in the shared state. [Note ?: Exceptions from the decay-copy calls are propagated to the caller. — end note] The thread object is stored in the shared state and affects the behavior of any asynchronous return objects that reference that state.

    2. […]

Date: 2021-09-15.00:00:00

[ 2021-09-20; Reflector poll ]

Set priority to 3 after reflector poll.

Date: 2021-08-23.00:00:00

[futures.async] (3.1) says:

Any exception propagated from the execution of invoke(decay-copy(std::forward<F>(f)), decay-copy(std::forward<Args>(args))...) is stored as the exceptional result in the shared state.

It's not clear whether this includes the evaluation of the decay-copy calls in the calling thread, or only the invocation of invoke with the results of those decay-copy calls.

A literal reading suggests that any exceptions from any part of that expression should be stored in the shared state. All of libstdc++, libc++ and MSVC only store exceptions from the call to invoke, not the calls to decay-copy. Exceptions from the decay-copy calls are propagated to the caller of std::async. We should clarify that that's what the standard means.

History
Date User Action Args
2021-09-30 15:57:29adminsetmessages: + msg12079
2021-09-20 11:22:03adminsetmessages: + msg12049
2021-08-27 17:37:45adminsetmessages: + msg12017
2021-08-23 00:00:00admincreate